python-keystoneclient-4.0.0/0000775000175000017500000000000013644407143016122 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/LICENSE0000664000175000017500000002717413644407055017144 0ustar zuulzuul00000000000000Copyright (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 python-keystoneclient 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. python-keystoneclient-4.0.0/.stestr.conf0000664000175000017500000000011513644407055020372 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./keystoneclient/tests/unit} top_dir=./ python-keystoneclient-4.0.0/doc/0000775000175000017500000000000013644407143016667 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/doc/.gitignore0000664000175000017500000000000713644407055020656 0ustar zuulzuul00000000000000build/ python-keystoneclient-4.0.0/doc/Makefile0000664000175000017500000000617013644407055020335 0ustar zuulzuul00000000000000# 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/python-keystoneclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-keystoneclient.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." python-keystoneclient-4.0.0/doc/requirements.txt0000664000175000017500000000063213644407055022156 0ustar zuulzuul00000000000000# 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. # These are needed for docs generation openstackdocstheme>=1.20.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD reno>=2.5.0 # Apache-2.0 lxml!=3.7.0,>=3.4.1 # BSD fixtures>=3.0.0 # Apache-2.0/BSD python-keystoneclient-4.0.0/doc/source/0000775000175000017500000000000013644407143020167 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/doc/source/using-api-v2.rst0000664000175000017500000001121413644407055023143 0ustar zuulzuul00000000000000======================= Using the V2 client API ======================= Introduction ============ The main concepts in the Identity v2 API are: * tenants * users * roles * services * endpoints The V2 client API lets you query and make changes through managers. For example, to manipulate tenants, you interact with a ``keystoneclient.v2_0.tenants.TenantManager`` object. You obtain access to managers via attributes of the ``keystoneclient.v2_0.client.Client`` object. For example, the ``tenants`` attribute of the ``Client`` class is a tenant manager:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> keystone.tenants.list() # List tenants You create a valid ``keystoneclient.v2_0.client.Client`` object by passing a :class:`~keystoneauth1.session.Session` to the constructor. Authentication and examples of common tasks are provided below. You can generally expect that when the client needs to propagate an exception it will raise an instance of subclass of ``keystoneclient.exceptions.ClientException`` Authenticating ============== There are two ways to authenticate against keystone: * against the admin endpoint with the admin token * against the public endpoint with a username and password If you are an administrator, you can authenticate by connecting to the admin endpoint and using the admin token (sometimes referred to as the service token). The token is specified as the ``admin_token`` configuration option in your keystone.conf config file, which is typically in /etc/keystone:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> token = '012345SECRET99TOKEN012345' >>> endpoint = 'http://192.168.206.130:35357/v2.0' >>> auth = v2.Token(auth_url=endpoint, token=token) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) If you have a username and password, authentication is done against the public endpoint. You must also specify a tenant that is associated with the user:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> username='adminUser' >>> password='secretword' >>> tenant_name='openstackDemo' >>> auth_url='http://192.168.206.130:5000/v2.0' >>> auth = v2.Password(username=username, password=password, ... tenant_name=tenant_name, auth_url=auth_url) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) Creating tenants ================ This example will create a tenant named *openstackDemo*:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> keystone.tenants.create(tenant_name="openstackDemo", ... description="Default Tenant", enabled=True) Creating users ============== This example will create a user named *adminUser* with a password *secretword* in the openstackDemo tenant. We first need to retrieve the tenant:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> tenants = keystone.tenants.list() >>> my_tenant = [x for x in tenants if x.name=='openstackDemo'][0] >>> my_user = keystone.users.create(name="adminUser", ... password="secretword", ... tenant_id=my_tenant.id) Creating roles and adding users =============================== This example will create an admin role and add the *my_user* user to that role, but only for the *my_tenant* tenant: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> role = keystone.roles.create('admin') >>> my_tenant = ... >>> my_user = ... >>> keystone.roles.add_user_role(my_user, role, my_tenant) Creating services and endpoints =============================== This example will create the service and corresponding endpoint for the Compute service:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> service = keystone.services.create(name="nova", service_type="compute", ... description="Nova Compute Service") >>> keystone.endpoints.create( ... region="RegionOne", service_id=service.id, ... publicurl="http://192.168.206.130:8774/v2/%(tenant_id)s", ... adminurl="http://192.168.206.130:8774/v2/%(tenant_id)s", ... internalurl="http://192.168.206.130:8774/v2/%(tenant_id)s") python-keystoneclient-4.0.0/doc/source/using-sessions.rst0000664000175000017500000002027713644407055023724 0ustar zuulzuul00000000000000============== Using Sessions ============== Introduction ============ The :py:class:`keystoneauth1.session.Session` class was introduced into keystoneclient 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 discovery mechanisms Clients are not expected to have any knowledge of an identity token or any other form of identification credential. Service and endpoint discovery are handled by the Session and plugins. 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 from keystoneclient:: >>> 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_id='proj', ... user_domain_id='domain') >>> sess = session.Session(auth=auth, ... verify='/path/to/ca.cert') >>> ks = client.Client(session=sess) >>> users = ks.users.list() As clients adopt this means of operating they will be created in a similar fashion by passing the Session object to the client's constructor. Migrating keystoneclient to use a Session ----------------------------------------- By using a session with a keystoneclient Client we presume that you have opted in to new behavior defined by the session. For example authentication is now on-demand rather than on creation. To allow this change in behavior there are a number of functions that have changed behavior or are no longer available. For example the :py:meth:`keystoneclient.httpclient.HTTPClient.authenticate` method used to be able to always re-authenticate the current client and fetch a new token. As this is now controlled by the Session and not the client this has changed, however the function will still exist to provide compatibility with older clients. Likewise certain parameters such as ``user_id`` and ``auth_token`` that used to be available on the client object post authentication will remain uninitialized. When converting an application to use a session object with keystoneclient you should be aware of the possibility of changes to authentication and authentication parameters and make sure to test your code thoroughly. It should have no impact on the typical CRUD interaction with the client. 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. 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 OpenStack the URLs of available services are distributed to the user as a part of the token they receive called the Service Catalog. Clients are expected to use the URLs from the Service Catalog rather than have them provided. 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('/v3/users', endpoint_filter={'service_type': 'identity', 'interface': 'public', 'region_name': 'myregion'}) ``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. This will be one of: - ``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. 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. The session object determines the URL matching the filter and append to it the provided path and so create a valid request. 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 and a specific mechanism may not know how to interpret certain arguments and ignore them. For example the :py:class:`keystoneauth1.identity.generic.token.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. python-keystoneclient-4.0.0/doc/source/conf.py0000664000175000017500000001572213644407055021477 0ustar zuulzuul00000000000000# python-keystoneclient 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. import os import sys 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', ] 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. copyright = 'OpenStack Contributors' # 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 = ['keystoneclient.'] # 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 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 = 'python-keystoneclientdoc' # -- 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', 'doc-python-keystoneclient.tex', u'python-keystoneclient Documentation', u'OpenStack', '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 # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', 'maxlistdepth': 10, } keystoneauth_url = 'https://docs.openstack.org/keystoneauth/latest/' intersphinx_mapping = { 'python': ('https://docs.python.org/', None), 'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None), 'keystoneauth1': (keystoneauth_url, None), } # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-keystoneclient' bug_project = 'python-keystoneclient' bug_tag = '' python-keystoneclient-4.0.0/doc/source/index.rst0000664000175000017500000000245513644407055022040 0ustar zuulzuul00000000000000Python bindings to the OpenStack Identity API (Keystone) ======================================================== This is a client for OpenStack Identity API. There's a Python API for :doc:`Identity API v3 ` and :doc:`v2 ` (the :mod:`keystoneclient` modules). Contents: .. toctree:: :maxdepth: 1 using-api-v3 using-sessions using-api-v2 api/modules Related Identity Projects ========================= In addition to creating the Python client library, the Keystone team also provides `Identity Service`_, as well as `WSGI Middleware`_. .. _`Identity Service`: https://docs.openstack.org/keystone/latest/ .. _`WSGI Middleware`: https://docs.openstack.org/keystonemiddleware/latest/ 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/python-keystoneclient`` project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/python-keystoneclient .. _Launchpad: https://launchpad.net/python-keystoneclient .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox``. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-keystoneclient-4.0.0/doc/source/history.rst0000664000175000017500000000003513644407055022422 0ustar zuulzuul00000000000000.. include:: ../../ChangeLog python-keystoneclient-4.0.0/doc/source/using-api-v3.rst0000664000175000017500000001462113644407055023151 0ustar zuulzuul00000000000000======================= Using the V3 Client API ======================= Introduction ============ The main concepts in the Identity v3 API are: * :py:mod:`~keystoneclient.v3.credentials` * :py:mod:`~keystoneclient.v3.domain_configs` * :py:mod:`~keystoneclient.v3.domains` * :py:mod:`~keystoneclient.v3.endpoints` * :py:mod:`~keystoneclient.v3.groups` * :py:mod:`~keystoneclient.v3.policies` * :py:mod:`~keystoneclient.v3.projects` * :py:mod:`~keystoneclient.v3.regions` * :py:mod:`~keystoneclient.v3.role_assignments` * :py:mod:`~keystoneclient.v3.roles` * :py:mod:`~keystoneclient.v3.services` * :py:mod:`~keystoneclient.v3.tokens` * :py:mod:`~keystoneclient.v3.users` The :py:mod:`keystoneclient.v3.client` API lets you query and make changes through ``managers``. For example, to manipulate a project (formerly called tenant), you interact with a :py:class:`keystoneclient.v3.projects.ProjectManager` object. You obtain access to managers through attributes of a :py:class:`keystoneclient.v3.client.Client` object. For example, the ``projects`` attribute of a ``Client`` object is a projects manager:: >>> from keystoneclient.v3 import client >>> keystone = client.Client(...) >>> keystone.projects.list() # List projects While it is possible to instantiate a :py:class:`keystoneclient.v3.client.Client` object (as done above for clarity), the recommended approach is to use the discovery mechanism provided by the :py:class:`keystoneclient.client.Client` class. The appropriate class will be instantiated depending on the API versions available:: >>> from keystoneclient import client >>> keystone = ... client.Client(auth_url='http://localhost:5000', ...) >>> type(keystone) One can force the use of a specific version of the API, either by using the ``version`` keyword argument:: >>> from keystoneclient import client >>> keystone = client.Client(auth_url='http://localhost:5000', version=(2,), ...) >>> type(keystone) >>> keystone = client.Client(auth_url='http://localhost:5000', version=(3,), ...) >>> type(keystone) Or by specifying directly the specific API version authentication URL as the auth_url keyword argument:: >>> from keystoneclient import client >>> keystone = ... client.Client(auth_url='http://localhost:5000/v2.0', ...) >>> type(keystone) >>> keystone = ... client.Client(auth_url='http://localhost:5000/v3', ...) >>> type(keystone) Upon successful authentication, a :py:class:`keystoneclient.v3.client.Client` object is returned (when using the Identity v3 API). Authentication and examples of common tasks are provided below. You can generally expect that when the client needs to propagate an exception it will raise an instance of subclass of :class:`keystoneclient.exceptions.ClientException`. Authenticating Using Sessions ============================= Instantiate a :py:class:`keystoneclient.v3.client.Client` using a :py:class:`~keystoneauth1.session.Session` to provide the authentication plugin, SSL/TLS certificates, and other data:: >>> 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', ... user_id='myuserid', ... password='mypassword', ... project_id='myprojectid') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) For more information on Sessions refer to: `Using Sessions`_. .. _`Using Sessions`: using-sessions.html Getting Metadata Responses ========================== Instantiating :py:class:`keystoneclient.v3.client.Client` using `include_metadata=True` will cause manager response to return :py:class:`keystoneclient.base.Response` instead of just the data. The metadata property will be available directly to the :py:class:`keystoneclient.base.Response` and the response data will be available as property `data` to it. >>> 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', ... user_id='myuserid', ... password='mypassword', ... project_id='myprojectid') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess, include_metadata=True) >>> resp = keystone.projects.list() >>> resp.request_ids[0] req-1234-5678-... >>> resp.data [, , ...] Non-Session Authentication (deprecated) ======================================= The *deprecated* way to authenticate is to pass the username, the user's domain name (which will default to 'Default' if it is not specified), and a password:: >>> from keystoneclient import client >>> auth_url = 'http://localhost:5000' >>> username = 'adminUser' >>> user_domain_name = 'Default' >>> password = 'secreetword' >>> keystone = client.Client(auth_url=auth_url, version=(3,), ... username=username, password=password, ... user_domain_name=user_domain_name) A :py:class:`~keystoneauth1.session.Session` should be passed to the Client instead. Using a Session you're not limited to authentication using a username and password but can take advantage of other more secure authentication methods. You may optionally specify a domain or project (along with its project domain name), to obtain a scoped token:: >>> from keystoneclient import client >>> auth_url = 'http://localhost:5000' >>> username = 'adminUser' >>> user_domain_name = 'Default' >>> project_name = 'demo' >>> project_domain_name = 'Default' >>> password = 'secreetword' >>> keystone = client.Client(auth_url=auth_url, version=(3,), ... username=username, password=password, ... user_domain_name=user_domain_name, ... project_name=project_name, ... project_domain_name=project_domain_name) python-keystoneclient-4.0.0/test-requirements.txt0000664000175000017500000000144113644407055022365 0ustar zuulzuul00000000000000# 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>=3.0,<3.1.0 # Apache-2.0 flake8-docstrings==0.2.1.post1 # MIT coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD keyring>=5.5.1,<19.0.0;python_version=='2.7' # MIT/PSF keyring>=5.5.1;python_version>='3.4' # MIT/PSF lxml!=3.7.0,>=3.4.1 # BSD mock>=2.0.0 # BSD oauthlib>=0.6.2 # BSD oslotest>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # Bandit security code scanner bandit!=1.6.0,>=1.1.0 # Apache-2.0 python-keystoneclient-4.0.0/setup.py0000664000175000017500000000127113644407055017637 0ustar zuulzuul00000000000000# 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. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-keystoneclient-4.0.0/releasenotes/0000775000175000017500000000000013644407143020613 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/0000775000175000017500000000000013644407143022113 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/rocky.rst0000664000175000017500000000022113644407055023771 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky python-keystoneclient-4.0.0/releasenotes/source/queens.rst0000664000175000017500000000022313644407055024144 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens python-keystoneclient-4.0.0/releasenotes/source/stein.rst0000664000175000017500000000022113644407055023764 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein python-keystoneclient-4.0.0/releasenotes/source/_static/0000775000175000017500000000000013644407143023541 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013644407055026014 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/mitaka.rst0000664000175000017500000000023213644407055024112 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-keystoneclient-4.0.0/releasenotes/source/conf.py0000664000175000017500000002103113644407055023411 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # keystoneclient 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. copyright = u'2015, Keystone Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # 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 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 = 'KeystoneClientReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # 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', 'keystoneclientReleaseNotes.tex', u'keystoneclient 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', 'keystoneclientreleasenotes', u'keystoneclient 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', 'keystoneclientReleaseNotes', u'keystoneclient Release Notes Documentation', u'Keystone Developers', 'keystoneclientReleaseNotes', 'Python bindings 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/python-keystoneclient' bug_project = 'python-keystoneclient' bug_tag = '' python-keystoneclient-4.0.0/releasenotes/source/pike.rst0000664000175000017500000000021713644407055023577 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-keystoneclient-4.0.0/releasenotes/source/locale/0000775000175000017500000000000013644407143023352 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/locale/fr/0000775000175000017500000000000013644407143023761 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000013644407143025546 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000232713644407055030605 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: keystoneclient Release Notes 3.12.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-07-24 15:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:08+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "2.1.0" msgstr "2.1.0" msgid "2.2.0" msgstr "2.2.0" msgid "2.3.0" msgstr "2.3.0" msgid "3.0.0" msgstr "3.0.0" msgid "3.6.0" msgstr "3.6.0" msgid "Bug Fixes" msgstr "Corrections de bugs" msgid "Critical Issues" msgstr "Erreurs critiques" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Deprecation Notes" msgstr "Notes dépréciées " msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Other Notes" msgstr "Autres notes" msgid "keystoneclient Release Notes" msgstr "Note de release pour keystoneclient" python-keystoneclient-4.0.0/releasenotes/source/index.rst0000664000175000017500000000032313644407055023754 0ustar zuulzuul00000000000000============================== keystoneclient Release Notes ============================== .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike ocata newton mitaka python-keystoneclient-4.0.0/releasenotes/source/newton.rst0000664000175000017500000000021613644407055024160 0ustar zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton python-keystoneclient-4.0.0/releasenotes/source/_templates/0000775000175000017500000000000013644407143024250 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013644407055026523 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000016013644407055024773 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-keystoneclient-4.0.0/releasenotes/source/train.rst0000664000175000017500000000022113644407055023757 0ustar zuulzuul00000000000000=================================== Train Series Release Notes =================================== .. release-notes:: :branch: stable/train python-keystoneclient-4.0.0/releasenotes/source/ocata.rst0000664000175000017500000000021213644407055023731 0ustar zuulzuul00000000000000============================ Ocata Series Release Notes ============================ .. release-notes:: :branch: origin/stable/ocata python-keystoneclient-4.0.0/releasenotes/notes/0000775000175000017500000000000013644407143021743 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml0000664000175000017500000000020413644407055026563 0ustar zuulzuul00000000000000--- fixes: - | The ``X-Service-Token`` header value is now properly masked, and is displayed as a hash value, in the log. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-keystoneclient-4.0.0/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yamlpython-keystoneclient-4.0.0/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.y0000664000175000017500000000033213644407055032741 0ustar zuulzuul00000000000000--- features: - Added a ``allow_expired`` argument to ``validate`` and ``get_token_data`` in `keystoneclient.v3.tokens`. Setting this to ``True``, allos for a token validation query to fetch expired tokens. python-keystoneclient-4.0.0/releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml0000664000175000017500000000026513644407055026470 0ustar zuulzuul00000000000000--- deprecations: - | The region resource in Keystone never support or contain "enabled" property. Thus the property is deprecated and will be removed in future versions. python-keystoneclient-4.0.0/releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml0000664000175000017500000000044613644407055030137 0ustar zuulzuul00000000000000--- features: - Added support for ``domain configs``. A user can now upload domain specific configurations to keytone using the client. See ``client.domain_configs.create``, ``client.domain_configs.delete``, ``client.domain_configs.get`` and ``client.domain_configs.update``. ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000python-keystoneclient-4.0.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yamlpython-keystoneclient-4.0.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce0000664000175000017500000000023313644407055033324 0ustar zuulzuul00000000000000--- features: - | Adds support for creating access rules as an attribute of application credentials as well as for retrieving and deleting them. python-keystoneclient-4.0.0/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml0000664000175000017500000000055513644407055026546 0ustar zuulzuul00000000000000--- 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. python-keystoneclient-4.0.0/releasenotes/notes/remove_cli-d2c4435ba6a09b79.yaml0000664000175000017500000000035013644407055027166 0ustar zuulzuul00000000000000--- prelude: > The ``keystone`` CLI has been removed. other: - The ``keystone`` CLI has been removed, using the ``openstack`` CLI is recommended. This feature has been deprecated since the Liberty release of Keystone. python-keystoneclient-4.0.0/releasenotes/notes/implied_roles-ea39d3c3d998d482.yaml0000664000175000017500000000006713644407055027717 0ustar zuulzuul00000000000000--- features: - support for implied roles in v3 API. python-keystoneclient-4.0.0/releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml0000664000175000017500000000031613644407055027170 0ustar zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of python-keystoneclient to support python 2.7 is OpenStack Train. The minimum version of Python now supported is Python 3.6.python-keystoneclient-4.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000013644407055024216 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml0000664000175000017500000000045113644407055031355 0ustar zuulzuul00000000000000--- deprecations: - Deprecate the `keystoneclient.generic` client. This client used to be able to determine available API versions and some basics around installed extensions however the APIs were never upgraded for the v3 API. It doesn't seem to be used in the openstack ecosystem. python-keystoneclient-4.0.0/releasenotes/notes/list_role_assignment_names-7e1b7eb8c2d22d7c.yaml0000664000175000017500000000042313644407055032614 0ustar zuulzuul00000000000000--- features: - > [`bug 1479569 `_] With the ``include_names`` parameter set to True the names of the role assignments are returned with the entities IDs. (GET /role_assignments?include_names=True) python-keystoneclient-4.0.0/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml0000664000175000017500000000134713644407055030333 0ustar zuulzuul00000000000000--- deprecations: - > [`blueprint deprecate-to-ksa `_] Several modules related to authentication in keystoneclient have been deprecated in favor of [`keystoneauth `_] These modules include: ``keystoneclient.session``, ``keystoneclient.adapter``, ``keystoneclient.httpclient``, ``keystoneclient.auth.base``, ``keystoneclient.auth.cli``, ``keystoneclient.auth.conf``, ``keystoneclient.auth.identity.base``, and ``keystoneclient.auth.token_endpoint``. Tips for migrating to `keystoneauth` have been [`documented `_]. python-keystoneclient-4.0.0/releasenotes/notes/remove_apiclient_exceptions-0cd5c8d16aa09a22.yaml0000664000175000017500000000024513644407055032677 0ustar zuulzuul00000000000000--- other: - > Removed `keystoneclient.apiclient.exceptions`. This file was deprecated in v0.7.1 and has now been replaced by `keystoneclient.exceptions`. python-keystoneclient-4.0.0/releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml0000664000175000017500000000037613644407055031240 0ustar zuulzuul00000000000000--- features: - | Added support for managing project-specific limits. The ``POST`` API for limits in keystone supports batch creation, but the client implementation does not. Creation for limits using the client must be done one at a time. python-keystoneclient-4.0.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml0000664000175000017500000000056113644407055032266 0ustar zuulzuul00000000000000--- features: - | Adds support for creating, reading, and deleting application credentials. With application credentials, a user can grant their applications limited access to their cloud resources. Applications can use keystoneauth with the `v3applicationcredential` auth plugin to authenticate with keystone without needing the user's password. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000python-keystoneclient-4.0.0/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yamlpython-keystoneclient-4.0.0/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.ya0000664000175000017500000000042113644407055033202 0ustar zuulzuul00000000000000--- features: - | Added support for managing registered limits. The ``POST`` API for registered limits in keystone supports batch creation, but the client implementation does not. Creation of registered limits using the client must be done one at a time. ././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000python-keystoneclient-4.0.0/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yamlpython-keystoneclient-4.0.0/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf330000664000175000017500000000037513644407055033536 0ustar zuulzuul00000000000000--- features: - | Added ability to filter on multiple values with the same parameter key. For example, we can now filter on user names that contain both ``test`` and ``user`` using ``keystone.users.list(name__contains=['test', 'user'])``. python-keystoneclient-4.0.0/releasenotes/notes/bug-1641674-4862454115265e76.yaml0000664000175000017500000000045613644407055026111 0ustar zuulzuul00000000000000--- prelude: > Keystone Client now supports endpoint group filtering. features: - | Support for handling the relationship between endpoint groups and projects has been added. It is now possible to list, associate, check and disassociate endpoint groups that have access to a project. ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000python-keystoneclient-4.0.0/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yamlpython-keystoneclient-4.0.0/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f0000664000175000017500000000016513644407055034157 0ustar zuulzuul00000000000000--- features: - | Now keystone client supports to list projects which belongs to the given parent project. python-keystoneclient-4.0.0/releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml0000664000175000017500000000125113644407055032341 0ustar zuulzuul00000000000000--- features: - > [`blueprint return-request-id-to-caller `_] Instantiating client with ``include_metadata=True`` will cause manager response to return data along with request_ids for better tracing. Refer [`using-api-v3 `_] Added support to return "x-openstack-request-id" header in request_ids attribute if ``include_metadata=True``. Also, for APIs which return response as None, client will return request_ids as well if ``include_metadata`` is True.python-keystoneclient-4.0.0/releasenotes/notes/remove-credentials-data-46ab3c3c248047cf.yaml0000664000175000017500000000041013644407055031533 0ustar zuulzuul00000000000000--- prelude: > The ``data`` argument for creating and updating credentials has been removed. other: - The ``data`` argument for creating and updating credentials was deprecated in the 1.7.0 release. It has been replaced by the ``blob`` argument. python-keystoneclient-4.0.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml0000664000175000017500000000060113644407055027376 0ustar zuulzuul00000000000000--- features: - | [`blueprint project-tags `_] The keystoneclient now supports project tags feature in keystone. This allows operators to use the client to associate tags to a project, retrieve tags associated with a project, delete tags associated with a project, and filter projects based on tags. python-keystoneclient-4.0.0/releasenotes/notes/remove_apiclient_exceptions-6580003a885db286.yaml0000664000175000017500000000062113644407055032415 0ustar zuulzuul00000000000000--- prelude: > keystoneclient.apiclient has been removed. critical: - > [`bug 1526651 `_] The `keystoneclient.apiclient` module has been removed in favor of `keystoneclient.exceptions`. The aforementioned module has been deprecated since keystoneclient v0.7.1 which was inclued in the Juno release of OpenStack. python-keystoneclient-4.0.0/releasenotes/notes/ksc_2.1.0-739ded9c4c3f8aaa.yaml0000664000175000017500000000124213644407055026572 0ustar zuulzuul00000000000000--- fixes: - > [`bug 1462694 `_] Add support for `include_subtree` in list_role_assignments. - > [`bug 1526686 `_] Replace textwrap with faster code in cms functions. - > [`bug 1457702 `_] Change default endpoint to public for keystone v3. - > [`bug 1520244 `_] Support `truncated` flag returned from server. other: - > Support v2 parameters for the v3 service create method. python-keystoneclient-4.0.0/releasenotes/notes/remove-middleware-eef8c40117b465aa.yaml0000664000175000017500000000063013644407055030530 0ustar zuulzuul00000000000000--- prelude: > keystoneclient.middleware has been removed. critical: - > [`bug 1449066 `_] The `keystoneclient.middleware` module has been removed in favor of the keystonemiddleware library. The aforementioned module has been deprecated since keystoneclient v0.10.0 which was included in the Juno release of OpenStack. python-keystoneclient-4.0.0/.zuul.yaml0000664000175000017500000000154313644407055020070 0ustar zuulzuul00000000000000- job: name: keystoneclient-devstack-functional parent: devstack-minimal timeout: 4200 required-projects: - openstack/keystone - openstack/python-keystoneclient run: playbooks/run-ds-tox.yaml post-run: playbooks/tox-post.yaml vars: devstack_localrc: USE_PYTHON3: true devstack_services: key: true tox_envlist: functional zuul_work_dir: src/opendev.org/openstack/python-keystoneclient - project: templates: - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - publish-openstack-docs-pti - check-requirements - lib-forward-testing-python3 - release-notes-jobs-python3 check: jobs: - keystoneclient-devstack-functional gate: jobs: - keystoneclient-devstack-functional python-keystoneclient-4.0.0/setup.cfg0000664000175000017500000000353213644407143017746 0ustar zuulzuul00000000000000[metadata] name = python-keystoneclient summary = Client Library for OpenStack Identity description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-keystoneclient/latest/ python-requires = >=3.6 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 :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [files] packages = keystoneclient [entry_points] keystoneclient.auth.plugin = password = keystoneclient.auth.identity.generic:Password token = keystoneclient.auth.identity.generic:Token admin_token = keystoneclient.auth.token_endpoint:Token v2password = keystoneclient.auth.identity.v2:Password v2token = keystoneclient.auth.identity.v2:Token v3password = keystoneclient.auth.identity.v3:Password v3token = keystoneclient.auth.identity.v3:Token v3oidcpassword = keystoneclient.contrib.auth.v3.oidc:OidcPassword v3unscopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2UnscopedToken v3scopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2ScopedToken v3unscopedadfs = keystoneclient.contrib.auth.v3.saml2:ADFSUnscopedToken [pbr] autodoc_tree_index_modules = True autodoc_tree_excludes = setup.py keystoneclient/tests/ [compile_catalog] directory = keystoneclient/locale domain = keystoneclient [update_catalog] domain = keystoneclient output_dir = keystoneclient/locale input_file = keystoneclient/locale/keystoneclient.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = keystoneclient/locale/keystoneclient.pot [egg_info] tag_build = tag_date = 0 python-keystoneclient-4.0.0/AUTHORS0000664000175000017500000002607713644407143017206 0ustar zuulzuul00000000000000Aaron Rosen Adam Gandelman Adam Young Alan Pevec Alessio Ababilov Alessio Ababilov Alessio Ababilov Alex Gaynor Alex Meade Alexei Kornienko AmalaBasha Andre Naehring Andreas Jaeger Andreas Jaeger Andrey Kurilin Andy McCrae Anh Tran Ankit Agrawal Anthony Young Arthur Miranda Bernhard M. Wiedemann Bertrand Lallau Bhuvan Arumugam Boris Bobrov Boris Bobrov Boris Pavlovic Brad Pokorny Brant Knudson Brian Lamar Brian Waldon Bryan D. Payne Bryan Davidson Carlos D. Garza Cedric Brandily Chandan Kumar Chang Bo Guo ChangBo Guo(gcb) Charles V Bock Chen Chmouel Boudjnah Christian Berendt Christian Schwede Christophe Sauthier Christopher J Schaefer Chuck Short Chuck Thier Clark Boylan Claudiu Belu Clint Byrum Colleen Murphy Colleen Murphy Corey Bryant Cyril Roelandt Dan Prince Daniel Gonzalez Davanum Srinivas Davanum Srinivas Dave Chen David C Kennedy David Stanek Dazhao Dean Troyer Deepti Ramakrishna Derek Higgins DhritiShikhar Dinesh Bhor Dirk Mueller Divyesh Khandeshi Dolph Mathews Dominik Heidler Donagh McCabe Doug Hellmann Doug Hellmann Eduardo Magalhães Emilien Macchi Enrique Garcia Navalon Eric Brown Eric Guo Everett Toews Flavio Percoco Florent Flament Gabriel Hurley Gage Hugo George Tian Ghe Rivero Guang Yee Haneef Ali Harry Rybacki Hengqing Hu Henry Nash Hidekazu Nakamura Ian Cordasco Ian Cordasco Igor A. Lukyanenkov Ihar Hrachyshka Ilya Kharin Ionuț Arțăriși Ivan Udovichenko J. Matt Peterson Jaime Gil de Sagredo Luna Jakub Ruzicka James E. Blair Jamie Lennox Jamie Lennox Jamie Lennox Javeme Jay Pipes Jens Harbott Jeremy Stanley Jesse Andrews Joao Paulo Targino Joe Gordon Joe Gordon Joe Heck Joel Friedly JordanP Jose Castro Leon Joseph W. Breu Josh Kearney Joshua Harlow Juan Manuel Olle Jude Job Julien Danjou K Jonathan Harker Kannan Manickam Ken Thomas Ken'ichi Ohmichi Kevin L. Mitchell Kieran Spear Kristi Nikolla Kui Shi Kun Huang Ladquin Lance Bragstad Laurence Miao Lei Zhang Liem Nguyen Lin Hua Cheng Lorin Hochstein M V P Nitesh Maho Koshiya Marek Denis Mark McLoughlin MarounMaroun Masayuki Igawa Matt Fischer Matt Riedemann Matthew Treinish Matthieu Huin Michael J Fork Michael McCune Michael Simo Michael Solberg Mikhail Nikolaenko Monty Taylor Morgan Fainberg Mouad Benchchaoui Nachiappan Nachiappan VR N Nam Nguyen Hoai Navid Pustchi Neha Alhat Nicolas Simonds Nisha Yadav Ondřej Kobližek Ondřej Nový OpenStack Release Bot Paulo Ewerton Peng Yong Pete Zaitcev Peter Portante Pradeep Kilambi Qin Zhao QinglinCheng Qiu Yu Rakesh H S Rob Crittenden Rodrigo Duarte Rodrigo Duarte Sousa Rodrigo Duarte Sousa Rodrigo Duarte Sousa Rodrigo Duarte Sousa Roman Bogorodskiy Roxana Gherle Ruby Loo Rui Chen Rushi Agrawal Russell Bryant Sam Morrison Samuel Pilla Samuel de Medeiros Queiroz Santiago Baldassin Sascha Peilicke Sascha Peilicke Sean Dague Sergey Kraynev Sergio Cazzolato Steve Martinelli Steve Martinelli Steven Hardy Stuart McLaren Sushil Kumar THOMAS J. COCOZZELLO Takashi Kajinami Tang Chen Thiago Paiva Brito Thierry Carrez Thomas Goirand Thomas Goirand Thomas Herve Tihomir Trifonov Tin Lam Tin Lam Tobias Diaz Tom Cocozzello Tom Fifield Tony Breeds Tovin Seven Trevor McKay Tristan Cacqueray Van Hung Pham Vasyl Khomenko Victor Morales Victor Silva Victor Stinner Vieri <15050873171@163.com> Vincent Untz Vishakha Agarwal Vishvananda Ishaya Vu Cong Tuan Wu Wenxiang Xu (Simon) Chen Yaguang Tang Yaguang Tang YangLei Yatin Kumbhare Yukinori Sagara Yuuichi Fujioka Zhang Xin ZhengYue Zhenguo Niu ZhiQiang Fan ZhiQiang Fan ZhijunWei Zhongyue Luo Zhongyue Luo Ziad Sawalha ankitagrawal cao.yuan chenaidong1 chenxiao chenxing daniel-a-nguyen dineshbhor fujioka yuuichi guang-yee henriquetruta hgangwx howardlee huangtianhua jacky06 jakedahn ji-xuepeng jun xie lawrancejing lhinds lin-hua-cheng lin-hua-cheng liu-sheng liuqing llg8212 lrqrun lvxianguo mathrock melissaml nachiappan-veerappan-nachiappan openstack pawnesh.kumar pengyuesheng qingszhao rajiv root shu-mutou sonu.kumar sridhargaddam sunjia termie venkatamahesh wanghong wingwj wu.chunyang xingzhou yuyafei zhang-jinnan zhangyanxian zheng yin zhiyuan_cai zhubx007 zlyqqq python-keystoneclient-4.0.0/CONTRIBUTING.rst0000664000175000017500000000123413644407055020565 0ustar zuulzuul00000000000000If 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/python-keystoneclient python-keystoneclient-4.0.0/bindep.txt0000664000175000017500000000122113644407055020122 0ustar zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed by tests; # see https://docs.openstack.org/infra/bindep/ for additional information. gettext libssl-dev dbus-devel [platform:rpm] dbus-glib-devel [platform:rpm] libdbus-1-dev [platform:dpkg] libdbus-glib-1-dev [platform:dpkg] libffi-dev [platform:dpkg] libffi-devel [platform:rpm] libsasl2-dev [platform:dpkg] libxml2-dev [platform:dpkg] libxslt1-dev [platform:dpkg] python-all-dev [platform:dpkg] python3-all-dev [platform:dpkg] cyrus-sasl-devel [platform:rpm] libxml2-devel [platform:rpm] python-devel [platform:rpm] python3-devel [platform:fedora] python34-devel [platform:centos] python-keystoneclient-4.0.0/README.rst0000664000175000017500000000461313644407055017617 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() python-keystoneclient-4.0.0/babel.cfg0000664000175000017500000000002013644407055017642 0ustar zuulzuul00000000000000[python: **.py] python-keystoneclient-4.0.0/HACKING.rst0000664000175000017500000000121613644407055017722 0ustar zuulzuul00000000000000Keystone 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 ======= python-keystoneclient 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/ python-keystoneclient-4.0.0/ChangeLog0000664000175000017500000017031413644407143017702 0ustar zuulzuul00000000000000CHANGES ======= 4.0.0 ----- * Cleanup py27 support * Update hacking for Python3 * [ussuri][goal] Drop python 2.7 support and testing 3.22.0 ------ * Update master for stable/train 3.21.0 ------ * Fix unit tests broken by requests-mock * Generate pdf documentation * Add parent project filter for listing projects * Add support for app cred access rules * Bump the openstackdocstheme extension to 1.20 3.20.0 ------ * Blacklist bandit 1.6.0 & cap sphinx for 2.7 * Update the constraints url * Add Python 3 Train unit tests * Follow bandit B105: hardcoded\_password\_string * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Update the min version of tox * Update master for stable/stein * Drop py35 jobs * Make tests pass in 2020 * Update json module to jsonutils * Add support for app cred access rules header 3.19.0 ------ * add python 3.7 unit test job * Add return-request-id-to-caller function(v3/contrib) * Update hacking version * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Add release notes for return-request-id-to-caller * Fix keystoneclient-devstack-functional job * Use python3 for functional tests * Make the functional test voting * Add py36 tox environment * Convert functional tests to Zuulv3 * Add return-request-id-to-caller function(v3/contrib) 3.18.0 ------ * Don't quote {posargs} in tox.ini * Deprecate region enabled parameter * Import legacy keystoneclient-dsvm-functional * Use templates for cover and lower-constraints * add lib-forward-testing-python3 test job * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * fix misspelling of 'default' * Update reno for stable/rocky * refactor the getid method in keystoneclient/base.py * Switch to stestr * Add release note link in README 3.17.0 ------ * Update IdentityProviderManager docstring * Add support for project-specific limits * Add support for registered limits * Fix python3 test compat * fix tox python3 overrides * Remove PyPI downloads * fix a typo in docstring * Trivial: Update pypi url to new url 3.16.0 ------ * add lower-constraints job * Add return-request-id-to-caller function(v3) * Add Response class to return request-id to caller * Updated from global requirements * Updated from global requirements * Update links in README * Updated from global requirements * Override find function in project * Update reno for stable/queens 3.15.0 ------ * Add system role functionality * Add CRUD support for application credentials * Updated from global requirements * Add project tags to keystoneclient * Create doc/requirements.txt * Updated from global requirements 3.14.0 ------ * Updated from global requirements * Avoid tox\_install.sh for constraints support * Handle UTC+00:00 in datetime strings * Remove setting of version/release from releasenotes * Updated from global requirements * Remove functional tests for v2.0 API * Updated from global requirements * Use generic user for both zuul v2 and v3 * Updated from global requirements * Adds bandit nosec flag to hashlib.sha1 * Updated from global requirements * Remove use of positional decorator * Imported Translations from Zanata * Update reno for stable/pike * Updated from global requirements 3.13.0 ------ * Updated from global requirements * Updated from global requirements * Update URLs in documents according to document migration * Updated from global requirements * Bring back intersphinx reference to keystoneauth * Change locations of docs for intersphinx * Switch from oslosphinx to openstackdocstheme 3.12.0 ------ * Updated from global requirements * Add support for specifying role ids when creating trust * Updated from global requirements 3.11.0 ------ * Fix html\_last\_updated\_fmt for Python3 * Updated from global requirements * Moved release note to the correct path * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Stop using oslotest.mockpatch * Remove unused log * Remove pbr warnerrors in favor of sphinx check * Add support for endpoint group filtering * Replace six.iteritems() with .items() * Updated from global requirements * Fix failing PY2 and PY3 gate jobs * Remove log translations in python-keystoneclient * Updated from global requirements * Add support for endpoint group CRUD * Fix 12 warnings when building keystoneclient docs * Use https for \*.openstack.org references * Fix boto version strip regex * Update reno for stable/ocata 3.10.0 ------ * Allow Multiple Filters of the Same Key * Updated from global requirements * Fix response body being omitted in debug mode incorrectly * Updated from global requirements * Removes unnecessary utf-8 encoding 3.9.0 ----- * Only log application/json in session to start * X-Serivce-Token should be hashed in the log * Do not log binary data during request * remove hacking checks from keystoneclient * Remove references to Python 3.4 * Updated from global requirements * Prevent MemoryError when logging response bodies * Add Constraints support * re-work inference rule bindings * Updated from global requirements * Use assertIsNone(...) instead of assertEqual(None, ...) * Deprecate the generic client * Refactor test\_projects * Refactor test\_credentials * Updated from global requirements * Fix Failing tests with openssl >= 1.1.0 * skip failing functional test 3.8.0 ----- * Pass allow\_expired to token validate * Show team and repo badges on README * Replace 'assertFalse(a in b)' with 'assertNotIn(a, b)' * Fix some spelling mistaks in base.py & auth.py * Refactor test\_domain\_configs * Fix typo in access.py 3.7.0 ----- * Remove revocation event code * Enable code coverage report in console output * Do not add last\_request\_id * Updated from global requirements * Fix typo in httpclient.py * Updated from global requirements * Support domain-specific configuration management * Updated from global requirements * Updated from global requirements * Updated from global requirements * Increase readability of 'find()' method and small improvements * Updated from global requirements * [doc] remove auth plugin docs * Updated coverage configuration file * Updated from global requirements * Updated from global requirements * Remove redundant variable declaration * Enable release notes translation 3.6.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * TrivialFix: Fixed typo in some files * Import module instead of object * TrivialFix: Using assertIsNone() instead of assertEqual(None) * Updated from global requirements * Fix non-ascii attributes * Updated from global requirements * Correct output for Implied Roles * Revert "Add auth functional tests" * Import module instead of object * standardize release note page ordering * Update reno for stable/newton * Remove unauthenticated functions * Use exceptions from Keystoneauth * Use AUTH\_INTERFACE object from keystoneauth * Use fixtures from keystoneauth 3.5.0 ----- * Minor docstring fix in mappings.py * Updated from global requirements * Follow up patch for Improve docs for v3 policies * Follow up patch for Improve docs for v3 services * Follow up patch for Improve docs for v3 domains * Follow up patch for Add ec2 functional tests * Add auth functional tests * Reuse Domain and Project resouce definitions * Improve docs for v3 tokens * Fix no content return type doc * Follow up patch for Improve docs for v3 ec2 * Add ec2 functional tests * Improve docs for v3 auth * Improve docs for v3 ec2 * Add credential functional tests * Move other-requirements.txt to bindep.txt * Add \_\_ne\_\_ built-in function * Remove deprecated 'data' credential argument * Improve docs for v3 credentials * Add role functional tests * Fix missing service\_catalog parameter in Client object * Add Python 3.5 classifier * Follow up patch for Improve docs for v3 roles * Updated from global requirements * Improve docs for v3 roles * Correct test\_implied\_roles 3.4.0 ----- * Updated from global requirements * Do not send user ids as payload * Updated from global requirements * Add endpoint functional tests * Improve docs for v3 endpoints 3.3.0 ----- * Add region functional tests * Add project functional tests * Use assertEqual() instead of assertDictEqual() * Updated from global requirements * Improve implied-role functional tests * Fix other-requirements.txt for deb based distros * Updated from global requirements * Remove unused LOG * Updated from global requirements * Improve docs for v3 regions * Add policy functional tests * Improve docs for v3 policies * Add service functional tests * Improve docs for v3 services * Use the adapter instead of the client in tests * Remove print in tests.functional.v3.test\_implied\_roles 3.2.0 ----- * Updated from global requirements * Update other-requirements.txt for Xenial * Update README to comply with Identity V3 * List system dependencies for running common tests * Follow up patch for Improve docs for v3 projects * Updated from global requirements * Improve docs for v3 projects * Add group functional tests * Updated from global requirements * Improve docs for v3 groups * Follow up patch for add domain functional tests * Add domain functional tests * Improve docs for v3 domains * Updated from global requirements * Use /v3/auth/projects and /v3/auth/domains * Handle EmptyCatalog exception in list federated projects * Updated from global requirements * PEP257: Ignore D203 because it was deprecated * Updated from global requirements * import warnings in doc/source/conf.py * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Remove unused iso8601 requirement * map fixtures to keystoneauth 3.1.0 ----- * Updated from global requirements * Update the home-page with developer documentation * Add users functional tests * Improve docs for v3 users * Trivial: ignore openstack/common in flake8 exclude list * Updated from global requirements * Updated from global requirements * Fixing D105 PEP257 * Fixing D200 PEP257 violation * Fixing D202 and D203 PEP257 violation * Fixing D204, D205, and D207 PEP257 violation * Fixing D208 PEP257 violation * httpclient: remove unused debug kwargs * Fixing D211 PEP257 violation * Fixing D301 PEP257 violation * Add federation related tests * [Trivial] Remove unnecessary executable privilge of unit test file * Replace tempest-lib with tempest.lib * Fix identity\_providers docstring * Updated from global requirements * Fallback if Git repository is absent * Fix D400 PEP257 violation * Fix D401 PEP257 violation * Updated example in README * Removing bandit.yaml in favor of defaults * Updated from global requirements * Updated from global requirements 3.0.0 ----- * Updated from global requirements * Allow seeing full token response when debug enabled * Enhance functional class to provide default info * Remove keystone bash completion scripts for Keystone * Remove doc references to the keystone CLI * remove CLI from keystoneclient * remove oslo-incubator apiclient * Update reno for stable/mitaka * Fix reference to ClientException * Update Client examples to use sessions * Change tests to pass session to Client * Update developer docs for keystoneauth session * Correct test running instructions * Document session as an argument to v3.Client * Link to AccessInfoV3 returned from get\_raw\_token\_from\_identity\_service * Tests stop using deprecated HTTPClient.get() 2.3.1 ----- * Revert "Support \`truncated\` flag returned by identity service" 2.3.0 ----- * Updated from global requirements * Support \`truncated\` flag returned by identity service * Updated from global requirements * Support creation of domain specific roles * Get revocation list with only audit ids * Add back a bandit tox job * Implied Roles 2.2.0 ----- * add release notes for deprecated auth bits * Updated from global requirements * Updated from global requirements * Make pep8 \*the\* linting interface * Handle exception on UnicodeDecodError in logging of request * Updated from global requirements * Deprecate adapter * Deprecate auth plugins from keystoneclient * Deprecate Session * Remove python 2.5 workaround * Update keyring requirements * Update translation setup * Bandit profile updates * Missing defaults in the create() method in the v2 ServiceManager * Remove Babel from requirements.txt * use positional library instead of utils * Replace TestResponse with requests\_mock * Use positional library instead of local code * Remove argparse from requirements * Adds an option to include names in role assignment lists * Updated from global requirements * Remove bandit tox environment * Mark password/secret options as secret 2.1.2 ----- 2.1.1 ----- * Revert "Support \`truncated\` flag returned by keystone" * Revert "Change default endpoint for Keystone v3 to public" * Address hacking check H405 2.1.0 ----- * add release notes for ksc 2.1.0 * Updated from global requirements * Updated from global requirements * Fix for the deprecated library function * Implements base classes for functional tests * Wrong usage of "a/an" * Remove "deprecated" internal method * Cleanup release note * remove keystoneclient.apiclient.exceptions * Support \`truncated\` flag returned by keystone * Change default endpoint for Keystone v3 to public * Updated from global requirements * Make tests run against original client and sessions * Seperate Client base test class * Removes MANIFEST.in as it is not needed explicitely by PBR * Deprecate the baseclient.Client * Replace textwrap with fast standard code * Docstring: Mark optional parameter as optional * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * move hacking to tests folder * remove venv bits from tools * Add include\_subtree to role\_list\_assignments call * Updated from global requirements * remove the default arguments "{}" * Updated from global requirements * remove oslo-incubator's memorycache * WebOb not needed after auth\_token removal * Deprecated tox -downloadcache option removed * Remove keystoneclient.middleware * Updated from global requirements * Updated from global requirements * Put py34 first in the env order of tox * Accept v2 params to v3 service create * Delete python bytecode before every test run 2.0.0 ----- * Remove hardcoded endpoint filter for update password * Add release notes for keystoneclient * Updated from global requirements * remove unnecessary FakeLog class in test code * No keystone Endpoint now gives a valid Error Message * Removes py26 support * Removes discover from test-reqs * Fixes warning for positional arg in project create * Updated from global requirements * Swap the order of username deprecation * Map keystoneclient exceptions to keystoneauth * Last sync from oslo-incubator * Updated from global requirements * Add missing end single quote * update incorrect docstring for regions * Iterate over copy of session.adapters keys in Python2/3 * Add docstring validation * Silence most of the deprecation spam * Pull the endpoint from the Session 1.8.1 ----- * Updated from global requirements * Updated from global requirements 1.8.0 ----- * Updated from global requirements * Replace repeated assertion with the loss * Mark abstractmethod bodies with nocover * Docstring spelling and function-vs-method fixes * pass on @abc.abstractmethods * Updated from global requirements * Fix typo that says V3 token only works for v2 * auto-generate release history * Updated from global requirements * Updated from global requirements * Redirect on 303 in SAML plugin * Make \_\_all\_\_ immutable * HTTPClient/region\_name deprecation test updates * Add shields.io version/downloads links/badges into README.rst * List creation could be rewritten as a list literal * Use dictionary literal for dictionary creation * Change ignore-errors to ignore\_errors * Updated from global requirements * Updated from global requirements * Move pot file for traslation * Use region\_id filter for List Endpoints * Identity plugin thread safety * Avoid message concatenation in error path 1.7.1 ----- * Adding back exception mapping for ConnectionError 1.7.0 ----- * Update path to subunit2html in post\_test\_hook * Deprecate create Discover without session * Mask passwords when logging the HTTP response * Updated from global requirements * Update deprecation text for Session properties * Proper deprecation for httpclient.USER\_AGENT * Deprecate create HTTPClient without session * Fix Accept header in SAML2 requests * Fixes missing socket attribute error during init\_poolmanager * Updated from global requirements * Expose token\_endpoint.Token as admin\_token * Proper deprecation for UserManager project argument * Proper deprecation for CredentialManager data argument * Deprecate create v3 Client without session * Deprecate create v2\_0 Client without session * Proper deprecation for Session.get\_token() * Deprecate use of cert and key * Proper deprecation for Session.construct() * Deprecate ServiceCatalog.get\_urls() with no attr * Deprecate ServiceCatalog(region\_name) * Updated from global requirements * Updated from global requirements * Updated from global requirements * Stop using .keys() on dicts where not needed * Inhrerit roles project calls on keystoneclient v3 * Deprecate openstack.common.apiclient * Move apiclient.base.Resource into keystoneclient * oslo-incubator apiclient.exceptions to keystoneclient.exceptions * Proper deprecation for HTTPClient session and adapter properties * Proper deprecation for HTTPClient.request methods * Proper deprecation for HTTPClient.tenant\_id|name * Proper deprecation for HTTPClient tenant\_id, tenant\_name parameters * Updated from global requirements * Clarify setting socket\_options * Remove check for requests version * Updated from global requirements * Fix tests passing user, project, and token * Proper deprecation for httpclient.request() * Proper deprecation for Dicover.raw\_version\_data unstable parameter * Proper deprecation for Dicover.available\_versions() * Proper deprecation for is\_ans1\_token * Proper deprecation for client.HTTPClient * Proper deprecation for Manager.api * Stop using Manager.api * Proper deprecation for BaseIdentityPlugin trust\_id property * Proper deprecation for BaseIdentityPlugin username, password, token\_id properties * Proper deprecations for modules * Use UUID values in v3 test fixtures * Proper deprecation for AccessInfo management\_url property * Proper deprecation for AccessInfo auth\_url property * Stop using deprecated AccessInfo.auth\_url and management\_url * Proper deprecation for AccessInfo scoped property * Proper deprecation for AccessInfo region\_name parameter * Deprecations fixture support calling deprecated function * Set reasonable defaults for TCP Keep-Alive * Updated from global requirements * Remove unused time\_patcher * Make OAuth testcase use actual request headers * Prevent attempts to "filter" list() calls by globally unique IDs * Add get\_token\_data to token CRUD * Updated from global requirements * py34 not py33 is tested and supported * Updated from global requirements * Remove confusing deprecation comment from token\_to\_cms * Fixes modules index generated by Sphinx * Updated from global requirements * Unit tests catch deprecated function usage * Switch from deprecated oslo\_utils.timeutils.strtime * Switch from deprecated isotime * Remove keystoneclient CLI references in README * Update README.rst and remove ancient reference * Remove unused images from docs * Updated from global requirements * Add openid connect client support * Stop using tearDown * Use mock rather than mox * Remove unused setUp from ClientTest * Updated from global requirements * Iterate over copy of sys.modules keys in Python2/3 * Use random strings for test fixtures * Stop using function deprecated in Python 3 * Use python-six shim for assertRaisesRegex/p * tox env for Bandit 1.6.0 ----- * Add EC2 CRUD credential support to v3 API * Cleanup fixture imports * Removes unused debug logging code 1.5.0 ----- * A Default CLI plugin * Fixed grammatical errors in the V2 Client API doc * Fixe example code in Using Sessions page * Add get\_communication\_params interface to plugins * Fix auth required message translation * Revert "Remove keystoneclient.middleware" * Revert "Remove unused fixtures" * Add docstrings for \`\`protocol\`\` parameter * Typo in openstack client help * Pass OS\_\* env vars fix for tox 2.0 * Remove unused fixtures * Updated from global requirements * Use 'mapping\_id' instead of 'mapping' in federation protocol tests * Use 'id' instead of 'protocol\_id' in federation protocol tests * Drop use of 'oslo' namespace package * Don't autodoc the test suite * Sync from oslo incubator * Removes temporary fix for doc generation * Ensure that failing responses are logged * add --slowest flag to testr * Prompt for password on CLI if not provided * Adapter version is a tuple * Remove keystoneclient.middleware * Document non-standard encoding of the PKI token 1.4.0 ----- * Add endpoint and service ids to fixtures * Uncap library requirements for liberty * Provide a means to get all installed plugins * Fix s3\_token middleware parsing insecure option * Make process\_header private * Fix tests to work with requests<2.3 * Increase minimum token life required * Update sample data with audit ids * pep8 fix for CMS * Inherited role domain calls on keystoneclient v3 * Add support to create ECP assertion based on a token * Add support to create SAML assertion based on a token * Allow requesting an unscoped Token * Support /auth routes for list projects and domains * Support discovery on the AUTH\_INTERFACE * Expose audit\_id via AccessInfo * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements 1.3.0 ----- * Return None for missing trust\_id in fixture * Improve feedback message in SSL error * Add a FederatedBase v3 plugin * Deprecate keystone CLI * Clean arguments in test\_federation.\*.test\_create() * Rename requests mock object in testing * Provide a generic auth plugin loader * Make non-import packages lazy * Extract BaseAuth out of Auth Plugin * Split v3 authentication file into module * Federation Service Providers CRUD operations * Allow passing logger object to request * Crosslink to other sites that are owned by Keystone * Implements subtree\_as\_ids and parents\_as\_ids * Fix time issue in AccessInfo test * Don't autodoc the test suite * Add OS-SIMPLE-CERT support for v3 * Updated from global requirements * Allow handling multiple service\_types * Import functional CLI tests from tempest * Creating parameter to list inherited role assignments 1.2.0 ----- * Updated from global requirements * Make post\_test\_hook.sh executable * Add default body for non-abstract empty methods * Create functional test base * Ignore all failures removing catalog when logging token * Using correct keyword for region in v3 * Move tests to the unit subdirectory * Make remove\_service\_catalog private * Docs for v3 credentials * Change hacking check to verify all oslo imports * Change oslo.i18n to oslo\_i18n * Add data to example data 1.1.0 ----- * Remove 404 link to novaclient in README * Hierarchical multitenancy basic calls * Workflow documentation is now in infra-manual * use right resource\_class to create resource instance * Basic AccessInfo plugin * Enable hacking rule E122 and H304 * Add get\_headers interface to authentication plugins * Add name parameter to NoMatchingPlugin exception * Change oslo.config to oslo\_config * Change oslo.serialization to oslo\_serialization * Switch from oslo.utils to oslo\_utils * Add validate token for v3 * Tests use keep\_blank\_values when parse\_qs * Fix typo in Ec2Signer class docstring * Add validate token for v2.0 * handles keyboard interrupt * make req\_ref doesn't require id * Updated from global requirements * Surface the user\_id and project\_id beyond the plugin * Configure TCP Keep-Alive for certain Sessions * Correct failures for check H238 * fix enabled parameter of update doesn't default to None * Enable hacking rule F821 * Fixes bootstrap tests * Add auth plugin params to doc * Add generic auth plugin documentation * Correct failures for check W292 * Move to hacking 0.10 * Updated from global requirements * don't log service catalog in every token response * Updated from global requirements * Use a test fixture for mocking time * Docstring usability improvements * token signing support alternative message digest * Allow fetching user\_id/project\_id from auth * add clear definition of service list * Fix a comment error in cms.py * Add get certificates for v2.0 * Updated service name to be optional in CLI * Reference identity plugins from \_\_init\_\_.py * Use textwrap instead of home made implementation * Allow v3 plugins to opt out of service catalog 1.0.0 ----- * Document the auth plugins that are loadable by name * Updated from global requirements * Add fetch revocations for v3 * Add fetch revocations for v2.0 * Fix up types within API documentation * Update requests-mock syntax * Expose version matching functions to the public * Take plugin params from ENV rather than default * Project ID in OAuth headers was missing * get\_endpoint should return the override * Pass all adapter parameters through to adapter * Correct documenting constructor parameters * Correct Session docstring * Add missing user-id option to generic.Password * duplicate auth-url option returned by BaseGenericPlugin * Replace magic numbers with named symbols * Fix importing config module and classmethod params * Removes confusing \_uuid property * Curl statements to include globoff for IPv6 URLs * Cleanup exception logging * Make keystoneclient use an adapter * Remove middleware architecture doc * Remove useless log message * Updated from global requirements * Updated from global requirements * I18n * Correct use of noqa * Sync oslo-incubator to 1fc3cd47 * Log the CA cert with the debug statement * Prevent AttributeError if no authorization 0.11.2 ------ * Use oslo\_debug\_helper and remove our own version * Updated from global requirements * set close\_fds=True in Popen * Cleanup docs - raises class * Fix mappings.Mapping docstring * Actually test interactive password prompt * Docstring cleanup for return type * Correct typos in man page * Docstrings should have :returns: everywhere * Use oslo.utils and oslo.serialization * Remove warning about management token * Document session usage first * Doc cleanup, make concepts links * Rename the client API docs * Correct typos in using-sessions * Warn that keystone CLI is pending deprecation * Reorder index links * Explicit complaint about old OpenSSL when testing * Log token with sha1 * Redact x-subject-token from response headers * Extracting common code to private method * Change cms\_sign\_data to use sha256 message digest * Enumerate Projects with Unscoped Tokens 0.11.1 ------ * Fix auth\_token for old oslo.config * Do not iterate action.choices if it is none 0.11.0 ------ * Update hacking to 0.9.x * Updated from global requirements * Add support for endpoint policy * Fix a doc\_string error * Handle federated tokens * SAML2 federated authentication for ADFS * Fix the condition expression for ssl\_insecure * Allow retrying some failed requests * Versioned Endpoint hack for Sessions * Stop using intersphinx * Pass kwargs to auth plugins * Sync with latest oslo-incubator * Change 'secrete' to 'secret' * fix typos * Work toward Python 3.4 support and testing * warn against sorting requirements * Version independent plugins * Expose auth methods on the adapter * Add version parameter to adapter * Allow providing an endpoint\_override to requests * fix EC2 Signature Version 4 calculation, in the case of POST * Fix test mistake with requests-mock * Allow passing None for username in v2.Password * Hash for PKIZ * Distinguish between name not provided and incorrect * Move fake session to HTTPClient * Allow providing a default value to CLI loading * Allow unauthenticated discovery * Remove cruft from setup.cfg * Unsort pbr and hacking in requirements files * Add v3scopedsaml entry to the setup.cfg * Fix handling of deprecated opts in CLI * Updated from global requirements * Revert "Add oslo.utils requirement" * Revert "Use oslo.utils" * Remove lxml as a forced depend * Allow passing user\_id to v2Password plugin * Make auth plugins dest save to os\_ * Allow registering individual plugin CONF options * Standardize AccessInfo token setting * Convert shell tests to requests-mock * Change unscoped token fallback to be session aware * Individual plugin CLI registering * Remove intersphinx mappings * Mark auth plugin options as secret * move attributes of v3.client.Client into alphabetical order * Handle invalidate in identity plugins correctly * Isolate get\_discovery function * Fixes import grouping * expose the revoke token for V3 * Use oslo.utils * Add oslo.utils requirement * Mark the keystoneclient s3\_token middleware deprecated * Add docs for how to create an OAuth auth instance * Control identity plugin reauthentication * Use token and discovery fixture in identity tests * Config fixture from oslo-incubator is not used * Use config fixture from oslo.config * List federated projects and domains * Redact tokens in request headers * Convert httpretty to requests-mock * Updated from global requirements * Add the 'auth' interface type * Use oslosphinx to generate doc theme * Add an example of using v3 client with sessions 0.10.1 ------ * Don't log sensitive auth data * Reorder the old compatibility arguments * Use keystoneclient.exceptions * Insert space between \`\`#\`\` and the comment * Rename saml2\_token\_url to token\_url * Enforce authenticated=False in saml2 plugin * Allow passing kwargs from managers to session * Scope unscoped saml2 tokens * Example JSON files should be human-readable 0.10.0 ------ * use embedded URLs for hyperlinks in the README * Only conditionally import working keyring * Calculate a suitable column width for positional arguments * Fix mistakes in token fixtures * add deprecation warning for auth\_token * SAML2 ECP auth plugin * remove useless part of error message * Test that tenant list function can use auth\_url * Add v2 Token manager authenticate tests * Use jsonutils to load adapter response * Provide an \_\_all\_\_ for auth module * Docstrings for usability * Add CRUD operations for Federated Protocols * Direct move of the revoke model from keystone server * Add tests without optional create endpoint params * Allow loading auth plugins from CLI * Plugin loading from config objects * Ensure no double slash in get token URL * Pass user and roles manager to tenant manager * Add profiling support to keystoneclient * Add V2 tenant user manager tests * Pass roles manager to user manager * Add V2 user roles tests * endpoint\_id and service\_id should be random uuid * Add CONTRIBUTING.rst * Modify oauth calls to expect urlencoded responses * Document authentication plugins * Add a fixture for Keystone version discovery * Sync with oslo-incubator fd90c34a9 * Session loading from CLI options * Session loading from conf * Keystoneclient create user API should have optional password * Use immutable arg rather mutable arg * Add trust users to AccessInfo and fixture * Add OAuth data to AccessInfo * Minor grammatical fix in doc * Updated from global requirements * Correcting using-api-v2.rst * Make parameters in EndpointManager optional * Add invalidate doc string to identity plugin * Session Adapters * Unversioned endpoints in service catalog * Update keystoneclient code to account for hacking 0.9.2 * Rename v3.\_AuthConstructor to v3.AuthConstructor * Add issued handlers to auth\_ref and fixtures * Add role ids to the AccessInfo * Doc build fails if warnings * Imports to fix build warnings * Updated from global requirements * auth\_token \_cache\_get checks token expired * Adjust Python 2.6 OSerror-on-EPIPE workaround * Using six.u('') instead of u'' * Added help text for the debug option * Session Documentation * Link to docstrings in using-api-v3 * Set the iso8601 log level to WARN * Refactor auth\_token token cache members to class * Add service\_name to URL discovery * Don't use mock non-exist method assert\_called\_once * Remove \_factory methods from auth plugins * Make get\_oauth\_params conditional for specific oauthlib versions * Changes exception raised by v3.trusts.update() * Add role assignments as concept in Client API V3 docs * Fix tests to use UUID strings rather than ints for IDs 0.9.0 ----- * Clean up oauth auth plugin code * Fix a misspelling in a comment * Sync with oslo-incubator caed79d * Fix attributes ordering at v3/client.py * Add endpoint handling to Token/Endpoint auth * Add support for extensions-list * auth\_token middleware hashes tokens with configurable algorithm * Remove left over vim headers * Add /role\_assignments endpoint support * Authenticate via oauth * Add description param to v3 service create/update * Fixed an aparent typo in the code * Auth Plugin invalidation * Updated from global requirements * Move DisableModuleFixture to utils * replace string format arguments with function parameters * Fixes an erroneous type check in a test * Mark keystoneclient as being a universal wheel * auth\_token hashes PKI token once * add docstr to v2 shell module regarding CLI deprecation * Compressed Signature and Validation * OAuth request/access token and consumer support for oauth client API * Add mailmap entry * Regions Management * Sync with oslo-incubator 2640847 * Discovery URL querying functions * Remove importutils from oslo config * Move auth\_token tests not requiring v2/v3 to new class * Cached tokens aren't expired * Move auth\_token cache pool tests out of NoMemcache * Make auth\_token return a V2 Catalog * Fix client fixtures * fixed typos found by RETF rules * Fix docstrings in keystoneclient * auth\_token configurable check of revocations for cached * Remove unused AdjustedBaseAuthTokenMiddlewareTest * Synced jsonutils from oslo-incubator * auth\_token test remove unused fake\_app parameter * Fix typo in BaseAuthTokenMiddlewareTest * Updated from global requirements * Enhance tests for auth\_token middleware * Limited use trusts * Debug log when token found in revocation list * Ensure that cached token is not revoked * Fix the catalog format of a sample token * remove universal\_newlines * replace double quotes with single * Deprecate admin\_token option in auth\_token 0.8.0 ----- * CLI always configures logging * Create a V3 Token Generator * Implement endpoint filtering functionality on the client side * Fix typo of ANS1 to ASN1 * Add new error for invalid response * Rename HTTPError -> HttpError * Add CRUD operations for Federation Mapping Rules * Don't use generic kwargs in v2 Token Generation * Update docs for auth\_token middleware config options * Allow session to return an error response object * Updated from global requirements * Add service name to catalog * Hash functions support different hash algorithms * Add CRUD operations for Identity Providers * eliminate race condition fetching certs * Allow passing auth plugin as a parameter * Prefer () to continue line per PEP8 * Use \`HttpNotImplemented\` in \`tests.v3.test\_trusts\` * Ensure JSON headers in Auth Requests * Create a test token generator and use it * Reuse module \`exceptions\` from Oslo * Updated from global requirements * Rename request\_uri to identity\_uri * Tests should use identity\_uri by default * Replace auth fragements with identity\_uri 0.7.1 ----- * Adds to Keystone to convert V2 endpoints to V3 * Remove releases.rst from keystone docs 0.7.0 ----- * Improve language in update\_password() validation error * Handle URLs via the session and auth\_plugins * Add a method for changing a user's password in v3 * sanity check memcached availability before running tests against it * Start using positional decorator * Fix passing get\_token kwargs to get\_access * add functional test for cache pool * Sync config fixture object from oslo.incubator * Add a positional decorator * add pooling for cache references * use v3 api to get certificates * Don't use a connection pool unless provided * Reference docstring for auth\_token fields * Docs link to middlewarearchitecture * Discover should support other services * Revert "Add request/access token and consumer..." * Revert "Authenticate via oauth" * Fix doc build errors * Generate module docs * document that --pass can be required * Authenticate via oauth * Add request/access token and consumer support for keystoneclient * Use AccessInfo in auth\_token middleware * Add 'methods' to all v3 test tokens * Handle Token/Endpoint authentication * Split sample PKI token generation * Updated from global requirements * Fix retry logic * Provide more data to AuthMethod plugins * Fix state modifying catalog tests * Remove reference to non-existent shell doc * increase default revocation\_cache\_time * Improve help strings * Make keystoneclient not log auth tokens * improve configuration help text in auth\_token * Log the command output on CertificateConfigError * Enforce scope mutual exclusion for trusts * Atomic write of certificate files and revocation list * Privatize auth construction parameters * Remove blank space after print * Set the right permissions for signing\_dir in tests * Capitalize Client API title consistently * Remove dependent module py3kcompat * Remove http\_handler config option in auth\_token * Rely on OSLO.config * Use admin\_prefix consistently * demonstrate auth\_token behavior with a simple echo service * Remove redundant default value None for dict.get * correct typo of config option name in error message * remove extra indentation * refer to non-deprecated config option in help * Create V3 Auth Plugins * Create V2 Auth Plugins * Fix role\_names call from V3 AccessInfo * Interactive prompt for create user * Add Python 3 classifiers * Replace assertEqual(None, \*) with assertIsNone in tests * Ensure domains.list filtered results are correct * Test query-string for list actions with filter arguments * Use Resource class from Oslo * Fix keystone command man page * Add link to the v3 client api doc * Fix references to auth\_token in middlewarearchitecture doc 0.6.0 ----- * Don't use private last\_request variable * Python: Pass bytes to derive\_keys() * Make sure to unset all variable starting with OS\_ * Remove tox locale overrides * Python3: use six.moves.urllib.parse.quote instead of urllib.quote * Remove vim header * Python3: httpretty.last\_request().body is now bytes * Python3: fix test\_insecure * Sync openstack/common/memorycache.py with Oslo * cms: Use universal\_newlines=True in subprocess.Popen() * HTTPretty: Bump to 0.8.0 * Check for any monkeypatching * Python3: webob.Response.body must be bytes * Python 3: call functions from memcache\_crypt.py with bytes as input * Update my mailmap * Improve output of "keystone help discover" * Use requests library in S3 middleware * Python 3: make tests from v2\_0/test\_access.py pass * Sync apiclient from oslo * Create Authentication Plugins * Fix debug curl commands for included data * Add back --insecure option to CURL debug 0.5.1 ----- * Use HTTPretty in S3 test code * Provide a conversion function for creating session * Update reference to middlewarearchitecture doc * Update middlewarearchitecture config options docs * Remove support for old Swift memcache interface 0.5.0 ----- * refactor handling of extension list * Python 3: fix tests/test\_utils.py * Python 3: Fix an str vs bytes issue in tempfile * Return role names by AccessInfo.role\_names * Copy s3\_token middleware from keystone * Fix discover command failed to discover keystone in ssl * Fix E12x warnings found by Pep8 1.4.6 * Fix typos in documents and comments * Consistently support kwargs across all v3 CRUD Manager ops * Using common method 'bool\_from\_string' from oslo strutils * Python 3: set webob.Response().body to a bytes value * Remove test\_print\_{dict,list}\_unicode\_without\_encode * Tests use cleanUp rather than tearDown * Sort items in requirement related files * Respect region name when processing domain URL * Python 3: fix the \_calc\_signature\_\* functions * Adjust import items according to hacking import rule * Replace assertTrue with explicit assertIsInstance * Sync with global requirements * Fix discover command failed to read extension list issue * Clarify roles validation error messages * Fix incorrect assertTrue usage * Make assertQueryStringIs usage simpler 0.4.2 ----- * auth\_token tests use assertIs/Not/None * Updated from global requirements * Python 3: Use HTTPMessage.get() rather than HTTPMessage.getheader() * auth\_token tests close temp file descriptor * Tests cleanup temporary files * Use install\_venv from oslo to fix no post\_process issue * Removes use of timeutils.set\_time\_override * Saner debug log message generation * Controllable redirect handling * Revert "Whitelist external netaddr requirement" * Sync strutils from oslo * Verify token binding in auth\_token middleware * Fix auth\_token middleware test invalid cross-device link issue * Add unit tests for generic/shell.py * Rename using-api.rst to using-api-v2.rst * Debug env for tox * Whitelist external netaddr requirement * Prevent dictionary size from changing while iterating over its items * Do not try to call decode() on a text string * Documents keystone v3 API usage - part 1 * v3 test utils, don't modify input parameter * Fix error in v3 credentials create/update * Rename instead of writing directly to revoked file * Correctly handle auth\_url/token authentication * Move redirect handling to session * Remove debug specific handling * Fix missed management\_url setter in v3 client * Add service catalog to domain scoped token fixture * Update requirements * Change assertEquals to assertIsNone * Avoid meaningless comparison that leads to a TypeError * HTTPretty: update to 0.7.1 * Python3: replace urllib by six.moves.urllib * Remove the 'cmp' keyword from a call to 'sort()' * Make \_get\_utf8\_value Python3 compliant * Don't install pre-release software with tox * Sync global requirements to pin sphinx to sphinx>=1.1.2,<1.2 * Allow commit title messages to end with a period * Sync with latest module from oslo * Fix --debug handling in the shell * Rename tokenauth to authtoken in the doc * use six.StringIO for compatibility with io.StringIO in python3 * Properly handle Regions in keystoneclient * Use testresources for example files * Discover supported APIs * Warn user about unsupported API version * Bump hacking to 0.8 * Add workaround for OSError raised by Popen.communicate() * Use assertIn where appropriate * Updates .gitignore * Updates .gitignore * Extract a base Session object * Reorganize Service Catalog * Do not format messages before they are logged * keystoneclient requires an email address when creating a user * Fix typo in keystoneclient * Encode the text before print it to console * Opt-out of service catalog * Fix trustee/trustor definitions * Revert "Merge "Avoid returning stale token via auth\_token property"" * "publicurl" should be required on endpoint-create * Update the management url for every fetched token * Remove service type restriction from keystone client help text * Add testresources test requirement * Fix python3 incompatible use of urlparse * Update tox.ini to usedevelop * Make HACKING.rst DRYer and turn into rst file * Quote URL in curl output to handle query params * Fix curl debug output for requests with a body * Add --insecure to curl output if required * Convert revocation list file last modified to UTC * Improved error message on connection failure * PEP 8 Public and internal interfaces * python3: Work around httpretty issue * Remove unused simplejson requirement * Migrate the keystone.common.cms to keystoneclient * Avoid returning stale token via auth\_token property * Remove SERVICE\_TOKEN and SERVICE\_ENDPOINT env vars * Apply six for metaclass * Make ROOTDIR determination more robust 0.4.1 ----- * Replace OpenStack LLC with OpenStack Foundation * Add AssertRequestHeaderEqual test helper and make use of it * Sync jsonutils from oslo * python3: Refactor dict for python2/python3 compat * Updated from global requirements * python3: Make iteritems py3k compat 0.4.0 ----- * Normalize datetimes to account for tz * assertEquals is deprecated, use assertEqual (H602) * Fix H202 assertRaises Exception * Refactor for testability of an upcoming change * Fixes print error for keystone action with non-English characters * Allow v2 client authentication with trust\_id * Fix misused assertTrue in unit tests * Add auth\_uri in conf to avoid unnecessary warning * Require oslo.config 1.2.0 final * Move tests in keystoneclient * Change Babel to a runtime requirement * Allow blank to email in user-update * Set example timestamps to 2038-01-18T21:14:07Z * Replace HttpConnection in auth\_token with Requests * Convert tests to HTTPretty and reorganize * Support client generate literal ipv6 auth\_uri base on auth\_host * Log user info in auth\_token middleware * Decode the non-english username str to unicode * Deprecation warning should be 'pending' * Deprecation warning for the CLI * Don't need to init testr explicitly * Allow Hacking 0.7.x or later * Remove testcase test\_invalid\_auth\_version\_request * Correct keyword args in test cases * python3: Use from future import unicode\_literals * Fixing potential NameErrors * Modify keyring tests to test authentication * Fix and enable gating on F811 * Fix and enable gating on F841 * Remove duplicate method in AccessInfo * remove the UUID check for userids * Standardize base.py with novaclient * Fix and enable gating on H302: only import modules * Fix License Headers and Enable Gating on H102 * Replace auth\_token middleware tests with httpretty * Add domain attributes to accessinfo * Support older token formats for projects in accessinfo * Use OSLO jsonutils instead of json module * python3: Transition to mox3 instead of mox * Sync py3kcompat from oslo-incubator * Update oslo.config * clearer error when authenticate called without auth\_url 0.3.2 ----- * Add unittests for exceptions.EmptyCatalog * Allow configure the number of http retries * Restore client.py for backward compatibility * Initial Trusts support * Add importutils and strutils from oslo * Synchronize code from oslo * Add apiclient.exceptions hierarchy * Use hashed token for invalid PKI token cache key * Move flake8 option from run\_tests.sh to tox.ini * Extract test token data from auth\_token middleware * Make auth\_token middleware fetching respect prefix * Fix and enable Gating on H404 * Move all opens in auth\_token to be in context * Refactor verify signing dir logic * Fixes files with wrong bitmode * flake8: enable H201, H202, H802 * Adds support for passing extra tenant attributes to keystone * Add a get\_data function to Service Catalog * Extract basic request call * Rename client.py to httpclient.py * Updated from global requirements * Don't cache tokens as invalid on network errors * Fix test\_request\_no\_token\_dummy cms dependency * Fix a typo in fetch\_revocation\_list * Make TestResponse properly inherit Response * auth\_uri (public ep) should not default to auth\_\* values (admin ep) * Adds help in keystone\_authtoken config opts * python3: Add basic compatibility support * Pass the default\_project\_id when managing User * Use flake8 in run\_tests.sh and updated ignore flake8 rules with tox.ini * flake8: fix alphabetical imports and enable H306 * Ec2Signer : Allow signature verification for older boto versions * Correct mis-spell in comments * Reorganize url creation * Add -u option in run\_tests.sh * Drop webob from auth\_token.py * no logging on cms failure * Client V3 shouldn't inherit V2 * Add discover to test-requirements * rm improper assert syntax * Update openstack-common.conf format * Fix and enable gating on H403 * Fix and enable gating on H402 * Raise key length defaults * Use ServiceCatalog.factory, the object has no \_\_init\_\_ * Ec2Signer : Modify v4 signer to match latest boto * Sync install\_venv\_common from oslo * Flake8 should ignore build folder * Fix auth\_token.py bad signing\_dir log message * Add name arguments to keystone command 0.3.1 ----- * Fix and enable H401 * List groups by domain in keystoneclient * Unmock requests when testing complete 0.3.0 ----- * Use Python 3.x compatible print syntax * Fix the cache interface to use time= by default * Implements v3 auth client * Change memcache config entry name in Keystone to be consistent with Oslo * Fix memcache encryption middleware * Python-2.6 compatibility for tests/test\_keyring.py * Remove endpoint.name attribute from v3 manager (bug 1191152) * Provide keystone CLI man page * Log cms\_verify issues as warnings (not errors) * Cleanup shell's authentication check * Use AuthRef for some client fields * Fix optional keyring support, add basic keyring tests 0.2.5 ----- * Fix unused imports(flake8 F401, F999) * Fix line continuations (flake8 E125, E126) * Fix --version to output version * python3: Introduce py33 to tox.ini * Add find() method to CrudManager 0.2.4 ----- * Check Expiry * Enumerate ignored flake8 rules * Missing command descriptions for 'token-get' and 'endpoint-get' * Suggestion of a new arguments display in the help, to reflect required ones Fix bug 1182130 * Rename requires files to standard names * Default signing\_dir to secure temp dir (bug 1181157) * Only add logging handlers if there currently aren't any * Pass memcache\_servers as array * Allow secure user password update * Make ManagerWithFind abstract and fix TokenManager * Migrate to flake8 * Migrate to pbr * change "int(marker)" to "marker" on user list pagination * Use testr instead of nose * Perform oslo-incubator code sync * Securely create signing\_dir (bug 1174608) * Added Conflict Exception to the exception code map * Refactor v3 API to support filtering * Revert "Use TokenManager to get token" * Restore compatibility with PrettyTable < 0.7.2 * Remove duplicate test definitions * Use TokenManager to get token * Pass json object when invoking exception handler * modify mistake in comment * Ec2Signer: Initial support for v4 signature verification * adding notes about dealing with exceptions in the client * Fix v3 with UUID and memcache expiring * Convert requests.ConnectionError to ClientException * Restrict prettytable to >=0.6,<0.8 * Allow keystoneclient to work with older keystone installs * Config value for revocation list timeout 0.2.3 ----- * Cache tokens using memorycache from oslo * Make keystone client handle the response code 300 * Make auth\_token lazy load the auth\_version * Doc info and other readability improvements * Retry http\_request and json\_request failure * Use v2.0 api by default in auth\_token middleware * Switch to final 1.1.0 oslo.config release * Fix auth-token middleware to understand v3 tokens * help text improvements * Switch to oslo.config * update v3 domains - remove public/private namespace * Work better in server env with no keyrings * Remove test dep on name of dir (bug 1124283) * Sync memorycache and timeutils from oslo * Improve error message for missing endpoint * Update oslo-config version * Fix selef to self in class * Save test\_auth\_token\_middleware from unlimited recursion * Use oslo-config-2013.1b3 * Added missing unit tests for shell.py * Allow configure auth\_token http connect timeout * Fix debug with requests * Allow requests up to 0.8 and greater * sync README with "keystone help" * Use install\_venv\_common.py from oslo * Fix incomplete sentence in help * Update .coveragerc * Pin requests module more strictly * Treat HTTP code 400 and above as error * Update requests requirements * Mark password config options with secret * Implements token expiration handling * fix discrepancies seen in domain and credential, v3 - bug 1109349 * Fix how python 2.6 doesn't have assertDictEqual * If you specify the --debug argument, it doesn't show the body of a POST request. The body (string rep) is at 'data' in the kwargs dict. 'body' was deleted prior to this call * Fix STALE\_TOKEN\_DURATION usage * Factorize endpoint retrieval in access * Take region\_name into account when calling url\_for * Remove useless code * Fix thinko in self.middleware.cert\_file\_missing * Remove useless import * Restore Python 2.6 compatibility * Allow request timeout to be specified * Remove assertDictEqual for python 2.6 compatibility * Add name arguments to keystone command * Blueprint memcache-protection: enable memcache value encryption/integrity check * Make WebOb version specification more flexible * Warning message is not logged for valid token-less request 0.2.2 ----- * Use os.path to find ~/keystone-signing (bug 1078947) * Remove iso8601 dep in favor of openstack.common * Move iso8601 dependency from test- to pip-requires * Pin requests to >=0.8.8 * Use testtools instead of unittest for base classes * Add support for user groups 0.2.1 ----- * Make it possible to debug by running module * remove unused import * Bug 1052674: added support for Swift cache * Add file 'ChangeLog' to MANIFEST.in * Fix keystone \*-list order by 'name' * Use requests module for HTTP/HTTPS * Print to stderr when keyring module is missing * Prevent an uncaught exception from being rasied * modify ca-certificate default value * Spelling: compatibile->compatible * URL-encode user-supplied tokens (bug 974319) * Fix middleware logging for swift * Fix keystoneclient user-list output order * Misspelling error in README.rst * Rename --no\_cache to --os\_cache * Make use\_keyring False by default * bug-1040361: use keyring to store tokens * Don't try to split a list of memcache servers * Drop hashlib/hmac from pip-requires * Add --version CLI opt and \_\_version\_\_ module attr * Add Ec2Signer utility class to keystoneclient * Add command to allow users to change their own password * updating PEP8 to 1.3.3 * Correct a misspelled in comments * Remove Policy.endpoint\_id reference * Fix scoped auth for non-admins (bug 1081192) 0.2.0 ----- * Throw validation response into the environment * fixes auth\_ref initialization error * Update README and CLI help * Add auth-token code to keystoneclient, along with supporting files * Make initial structural changes to keystoneclient in preparation to moving auth\_token here from keystone. No functional change should occur from this commit (even though it did refresh a newer copy of openstack.common.setup.py, none of the newer updates are in functions called from this client) * removing repeat attempt at authorization in client * Check for auth URL before password (bug 1076235) * check creds before token/endpoint (bug 1076233) * Warn about bypassing auth on CLI (bug 1076225) * fixes 1075376 * Fix keystoneclient so swift works against Rackspace Cloud Files * HACKING compliance: consistent usage of 'except' * Update --os-\* error messages * Replace refs to 'Keystone API' with 'Identity API' * Don't log an exception for an expected empty catalog * Add OpenStack trove classifier for PyPI * add a new HTTPClient attr for setting the original IP * Fixes https connections to keystone when no CA certificates are specified * use mock context managers instead of decorators+functions * Ensure JSON isn't read on no HTTP response body * Added 'service\_id' column to endpoint-list * Useful error msg when missing catalog (bug 949904) * bootstrap a keystone user (e.g. admin) in one cmd * Enable/disable services/endpoints (bug 1048662) * v3 Domain/Project role grants * Fixed httplib2 mocking (bug 1050091, bug 1050097) * v3 List projects for a user * v3 Credential CRUD * v3 User CRUD * v3 Project CRUD * v3 Role CRUD * v3 Domain CRUD * v3 Policy CRUD * v3 Endpoint CRUD * v3 Service CRUD * change default wrap for tokens from 78 characters to 0 * v3 Client & test utils * Manager for generic CRUD on v3 * virtualenv quite installation for zypper * updating base keystoneclient documentation * updating keystoneclient doc theme * enabling i18n with Babel * pep8 1.3.1 cleanup * Allow empty description for tenants * Add wrap option to keystone token-get for humans * switching options to match authentication paths * Fixes setup compatibility issue on Windows * Handle "503 Service Unavailable" exception * removing deprecated commandline options * Require httplib2 version 0.7 or higher * Fixed httplib2 mocking (bug 1050091, bug 1050097) * Allow serialization impl to be overridden * Add generic entity.delete() * Add support for HEAD and PATCH * Don't need to lazy load resources loaded from API 0.1.3 ----- * fixing pep8 formatting for 1.0.1+ pep8 * Fix PEP8 issues * splitting http req and resp logging also some pep8 cleanup in shell.py * Change underscores in new cert options to dashes * Add nosehtmloutput as a test dependency 0.1.2 ----- * Add '--insecure' commandline argument * If no password in env or command line, try prompting * Install test-requires in development venv * add keystone bash-completion * Replace obsolete option in README * Support 2-way SSL with Keystone server if it is configured to enforce 2-way SSL. See also https://review.openstack.org/#/c/7706/ for the corresponding review for the 2-way SSL addition to Keystone * Don't call PrettyTable add\_row with a tuple * Change CLI options to use dashes 0.1.1 ----- * Add post-tag versioning * decoding json only on 2xx success response bug 1007661 * Do not display None in pretty tables for fields with no value 0.1.0 ----- * Drop support for legacy OS args * Skip argparse when injecting requirements * Move unittest2 dependency * Fix coverage job. Turns out you need coverage * Update to latest openstack.common setup code * Move docs to doc * fix bug lp:936168,format output * pep8 1.1 changes and updates * Updated Sphinx documentation * Fix Tenant.update() for enabled=False * Change --user to --user\_id and --role to --role\_id in the keystone client for consistency * Remove printt * Auto generate AUTHORS for python-keystoneclient * Require service\_id for endpoint-create (bug 987457) * Removed unused imports and variables * Include last missing files in tarball * fix parameter name error in exapmle * Drop support for OS --tenant\_id (bug 960977) * Open Folsom * Useful messages for missing auth data (bug 946297) * Updated tox.ini to work properly with Jenkins * Implement user-get based on tenant-get (bug 940272) * Backslash continuations (python-keystoneclient) * Split user-role-list from user-list * Change CLIAuth arg names * enabled treated as string (bug 953678) * CLI shows help without args (bug 936398) * fix bug 950685,make update user password works * Add endpoint commands help text * List roles for user on CLI (bug 932282) * prevent keyerrors when accessing optional keys * Removed ?fresh=nonsense (bug 936405) * Make ec2-credentials-\* commands work properly for non-admin user * Remove trailing whitespaces in regular file * Endpoints: Add create, delete, list support * Clean up EC2 CRUD * Fix --tenant\_id corner case with ec2-create-creds command * Improve usability of CLI * Help output tweaks, Vol I * Move --version to --identity\_api\_version * Remove internal '-' from flag names * Fix inconsistient method names and add tests * Added condition requirement to argparse * Add tenant commands to cli * Display token and service catalog for user * Restores proper PUT method for user update now that KSL supports it * Add license file to the tarball * Fixes user update methods * Use unittest2 instead of unittest * Fix conflicts with shell args for subcommands * Allow --token and --endpoint to bypass catalog * Blueprint cli-auth: common cli args * Correct tenant update HTTP method * Added delete token * Updates client to work with keystone essex roles API routes * Enabling/disabling users should use OS-KSADM extension (bug 922394) * Add limit and marker to user\_list and tenant\_list * Support for version and extension discovery * Implementing a minimal, but useful CLI * Adjust version number to match other deliveries * update ec2 crud responses we test against * support ec2 crud calls * Install a good version of pip in the venv * Modify tox.ini file to do the standard thigns * Added in common test, venv and gitreview stuff * log when no service catalog * update comment to be tenant\_name * should have had tenant\_name * use full name for args in readme * finish removing project\_id * update test env shell * Fix the tests * remove X-Auth-Project-Id, re-add auth by token support (most tests pass) * pep8 * set the management\_url from the service\_catalog * more work on standardizing project\_id * typo in comments * remove print statements and uncomment exceptions * more work on standardization of cliauth * remove user\_id as you shouldn't auth using it * initial pass to cliauth blueprint * Improved error message when unable to communicate with keystone * Improved logging/error messages * adding myself to authors * switching back per docs * fixing up the VerifyAll() bits * more pep8 cleanup * pep8 cleanup * Updated the docs a little bit * Project ID always treated as a string * Cleans up the data returned for a token a little * Fixed a typo... "API" should've been "CLI". Thanks termie. ;-) * Initial commit python-keystoneclient-4.0.0/lower-constraints.txt0000664000175000017500000000240113644407055022357 0ustar zuulzuul00000000000000appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 bandit==1.1.0 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 debtcollector==1.2.0 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8-docstrings==0.2.1.post1 flake8==2.2.4 future==0.16.0 gitdb==0.6.4 GitPython==1.0.1 hacking==0.10.0 idna==2.6 iso8601==0.1.11 jsonschema==2.6.0 keyring==5.5.1 keystoneauth1==3.4.0 linecache2==1.0.0 lxml==3.4.1 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 oauthlib==0.6.2 os-client-config==1.28.0 os-testr==1.0.0 oslo.concurrency==3.25.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.serialization==2.18.0 oslo.utils==3.33.0 oslotest==3.2.0 paramiko==2.0.0 pbr==2.0.0 pep257==0.7.0 pep8==1.5.7 prettytable==0.7.2 pyasn1==0.1.8 pycparser==2.18 pyflakes==0.8.1 pyinotify==0.9.6 pyparsing==2.1.0 pyperclip==1.5.27 python-dateutil==2.5.3 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 requests-mock==1.2.0 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 six==1.10.0 smmap==0.9.0 stevedore==1.20.0 tempest==17.1.0 stestr==2.0.0 testresources==2.0.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 urllib3==1.21.1 wrapt==1.7.0 python-keystoneclient-4.0.0/requirements.txt0000664000175000017500000000102713644407055021410 0ustar zuulzuul00000000000000# 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. pbr!=2.1.0,>=2.0.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 python-keystoneclient-4.0.0/python_keystoneclient.egg-info/0000775000175000017500000000000013644407143024255 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/python_keystoneclient.egg-info/pbr.json0000664000175000017500000000005613644407143025734 0ustar zuulzuul00000000000000{"git_version": "b58ebe2", "is_release": true}python-keystoneclient-4.0.0/python_keystoneclient.egg-info/top_level.txt0000664000175000017500000000001713644407143027005 0ustar zuulzuul00000000000000keystoneclient python-keystoneclient-4.0.0/python_keystoneclient.egg-info/dependency_links.txt0000664000175000017500000000000113644407143030323 0ustar zuulzuul00000000000000 python-keystoneclient-4.0.0/python_keystoneclient.egg-info/SOURCES.txt0000664000175000017500000003150713644407143026147 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg bindep.txt lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/.gitignore doc/Makefile doc/requirements.txt doc/source/conf.py doc/source/history.rst doc/source/index.rst doc/source/using-api-v2.rst doc/source/using-api-v3.rst doc/source/using-sessions.rst examples/pki/gen_cmsz.py examples/pki/gen_pki.sh examples/pki/run_all.sh examples/pki/certs/cacert.pem examples/pki/certs/middleware.pem examples/pki/certs/signing_cert.pem examples/pki/certs/ssl_cert.pem examples/pki/cms/auth_token_revoked.json examples/pki/cms/auth_token_revoked.pem examples/pki/cms/auth_token_revoked.pkiz examples/pki/cms/auth_token_scoped.json examples/pki/cms/auth_token_scoped.pem examples/pki/cms/auth_token_scoped.pkiz examples/pki/cms/auth_token_scoped_expired.json examples/pki/cms/auth_token_scoped_expired.pem examples/pki/cms/auth_token_scoped_expired.pkiz examples/pki/cms/auth_token_unscoped.json examples/pki/cms/auth_token_unscoped.pem examples/pki/cms/auth_token_unscoped.pkiz examples/pki/cms/auth_v3_token_revoked.json examples/pki/cms/auth_v3_token_revoked.pem examples/pki/cms/auth_v3_token_revoked.pkiz examples/pki/cms/auth_v3_token_scoped.json examples/pki/cms/auth_v3_token_scoped.pem examples/pki/cms/auth_v3_token_scoped.pkiz examples/pki/cms/revocation_list.der examples/pki/cms/revocation_list.json examples/pki/cms/revocation_list.pem examples/pki/cms/revocation_list.pkiz examples/pki/private/cakey.pem examples/pki/private/signing_key.pem examples/pki/private/ssl_key.pem keystoneclient/__init__.py keystoneclient/_discover.py keystoneclient/access.py keystoneclient/adapter.py keystoneclient/base.py keystoneclient/baseclient.py keystoneclient/client.py keystoneclient/discover.py keystoneclient/exceptions.py keystoneclient/httpclient.py keystoneclient/i18n.py keystoneclient/service_catalog.py keystoneclient/session.py keystoneclient/utils.py keystoneclient/auth/__init__.py keystoneclient/auth/base.py keystoneclient/auth/cli.py keystoneclient/auth/conf.py keystoneclient/auth/token_endpoint.py keystoneclient/auth/identity/__init__.py keystoneclient/auth/identity/access.py keystoneclient/auth/identity/base.py keystoneclient/auth/identity/v2.py keystoneclient/auth/identity/generic/__init__.py keystoneclient/auth/identity/generic/base.py keystoneclient/auth/identity/generic/cli.py keystoneclient/auth/identity/generic/password.py keystoneclient/auth/identity/generic/token.py keystoneclient/auth/identity/v3/__init__.py keystoneclient/auth/identity/v3/base.py keystoneclient/auth/identity/v3/federated.py keystoneclient/auth/identity/v3/password.py keystoneclient/auth/identity/v3/token.py keystoneclient/common/__init__.py keystoneclient/common/cms.py keystoneclient/contrib/__init__.py keystoneclient/contrib/auth/__init__.py keystoneclient/contrib/auth/v3/__init__.py keystoneclient/contrib/auth/v3/oidc.py keystoneclient/contrib/auth/v3/saml2.py keystoneclient/contrib/ec2/__init__.py keystoneclient/contrib/ec2/utils.py keystoneclient/fixture/__init__.py keystoneclient/fixture/discovery.py keystoneclient/fixture/exception.py keystoneclient/fixture/v2.py keystoneclient/fixture/v3.py keystoneclient/generic/__init__.py keystoneclient/generic/client.py keystoneclient/tests/__init__.py keystoneclient/tests/functional/__init__.py keystoneclient/tests/functional/base.py keystoneclient/tests/functional/test_base.py keystoneclient/tests/functional/v3/__init__.py keystoneclient/tests/functional/v3/client_fixtures.py keystoneclient/tests/functional/v3/test_credentials.py keystoneclient/tests/functional/v3/test_domain_configs.py keystoneclient/tests/functional/v3/test_domains.py keystoneclient/tests/functional/v3/test_ec2.py keystoneclient/tests/functional/v3/test_endpoint_filters.py keystoneclient/tests/functional/v3/test_endpoint_groups.py keystoneclient/tests/functional/v3/test_endpoints.py keystoneclient/tests/functional/v3/test_federation.py keystoneclient/tests/functional/v3/test_groups.py keystoneclient/tests/functional/v3/test_implied_roles.py keystoneclient/tests/functional/v3/test_policies.py keystoneclient/tests/functional/v3/test_projects.py keystoneclient/tests/functional/v3/test_regions.py keystoneclient/tests/functional/v3/test_roles.py keystoneclient/tests/functional/v3/test_services.py keystoneclient/tests/functional/v3/test_users.py keystoneclient/tests/unit/__init__.py keystoneclient/tests/unit/client_fixtures.py keystoneclient/tests/unit/test_base.py keystoneclient/tests/unit/test_cms.py keystoneclient/tests/unit/test_discovery.py keystoneclient/tests/unit/test_ec2utils.py keystoneclient/tests/unit/test_fixtures.py keystoneclient/tests/unit/test_http.py keystoneclient/tests/unit/test_https.py keystoneclient/tests/unit/test_keyring.py keystoneclient/tests/unit/test_session.py keystoneclient/tests/unit/test_utils.py keystoneclient/tests/unit/utils.py keystoneclient/tests/unit/apiclient/__init__.py keystoneclient/tests/unit/apiclient/test_exceptions.py keystoneclient/tests/unit/auth/__init__.py keystoneclient/tests/unit/auth/test_access.py keystoneclient/tests/unit/auth/test_auth.py keystoneclient/tests/unit/auth/test_cli.py keystoneclient/tests/unit/auth/test_conf.py keystoneclient/tests/unit/auth/test_default_cli.py keystoneclient/tests/unit/auth/test_identity_common.py keystoneclient/tests/unit/auth/test_identity_v2.py keystoneclient/tests/unit/auth/test_identity_v3.py keystoneclient/tests/unit/auth/test_identity_v3_federated.py keystoneclient/tests/unit/auth/test_loading.py keystoneclient/tests/unit/auth/test_password.py keystoneclient/tests/unit/auth/test_token.py keystoneclient/tests/unit/auth/test_token_endpoint.py keystoneclient/tests/unit/auth/utils.py keystoneclient/tests/unit/generic/__init__.py keystoneclient/tests/unit/generic/test_client.py keystoneclient/tests/unit/v2_0/__init__.py keystoneclient/tests/unit/v2_0/client_fixtures.py keystoneclient/tests/unit/v2_0/test_access.py keystoneclient/tests/unit/v2_0/test_auth.py keystoneclient/tests/unit/v2_0/test_certificates.py keystoneclient/tests/unit/v2_0/test_client.py keystoneclient/tests/unit/v2_0/test_discovery.py keystoneclient/tests/unit/v2_0/test_ec2.py keystoneclient/tests/unit/v2_0/test_endpoints.py keystoneclient/tests/unit/v2_0/test_extensions.py keystoneclient/tests/unit/v2_0/test_roles.py keystoneclient/tests/unit/v2_0/test_service_catalog.py keystoneclient/tests/unit/v2_0/test_services.py keystoneclient/tests/unit/v2_0/test_tenants.py keystoneclient/tests/unit/v2_0/test_tokens.py keystoneclient/tests/unit/v2_0/test_users.py keystoneclient/tests/unit/v2_0/utils.py keystoneclient/tests/unit/v3/__init__.py keystoneclient/tests/unit/v3/client_fixtures.py keystoneclient/tests/unit/v3/saml2_fixtures.py keystoneclient/tests/unit/v3/test_access.py keystoneclient/tests/unit/v3/test_access_rules.py keystoneclient/tests/unit/v3/test_application_credentials.py keystoneclient/tests/unit/v3/test_auth.py keystoneclient/tests/unit/v3/test_auth_manager.py keystoneclient/tests/unit/v3/test_auth_oidc.py keystoneclient/tests/unit/v3/test_auth_saml2.py keystoneclient/tests/unit/v3/test_client.py keystoneclient/tests/unit/v3/test_credentials.py keystoneclient/tests/unit/v3/test_discover.py keystoneclient/tests/unit/v3/test_domain_configs.py keystoneclient/tests/unit/v3/test_domains.py keystoneclient/tests/unit/v3/test_ec2.py keystoneclient/tests/unit/v3/test_endpoint_filter.py keystoneclient/tests/unit/v3/test_endpoint_groups.py keystoneclient/tests/unit/v3/test_endpoint_policy.py keystoneclient/tests/unit/v3/test_endpoints.py keystoneclient/tests/unit/v3/test_federation.py keystoneclient/tests/unit/v3/test_groups.py keystoneclient/tests/unit/v3/test_limits.py keystoneclient/tests/unit/v3/test_oauth1.py keystoneclient/tests/unit/v3/test_policies.py keystoneclient/tests/unit/v3/test_projects.py keystoneclient/tests/unit/v3/test_regions.py keystoneclient/tests/unit/v3/test_registered_limits.py keystoneclient/tests/unit/v3/test_role_assignments.py keystoneclient/tests/unit/v3/test_roles.py keystoneclient/tests/unit/v3/test_service_catalog.py keystoneclient/tests/unit/v3/test_services.py keystoneclient/tests/unit/v3/test_simple_cert.py keystoneclient/tests/unit/v3/test_tokens.py keystoneclient/tests/unit/v3/test_trusts.py keystoneclient/tests/unit/v3/test_users.py keystoneclient/tests/unit/v3/utils.py keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenResponse.xml keystoneclient/tests/unit/v3/examples/xml/ADFS_fault.xml keystoneclient/v2_0/__init__.py keystoneclient/v2_0/certificates.py keystoneclient/v2_0/client.py keystoneclient/v2_0/ec2.py keystoneclient/v2_0/endpoints.py keystoneclient/v2_0/extensions.py keystoneclient/v2_0/roles.py keystoneclient/v2_0/services.py keystoneclient/v2_0/tenants.py keystoneclient/v2_0/tokens.py keystoneclient/v2_0/users.py keystoneclient/v3/__init__.py keystoneclient/v3/access_rules.py keystoneclient/v3/application_credentials.py keystoneclient/v3/auth.py keystoneclient/v3/client.py keystoneclient/v3/credentials.py keystoneclient/v3/domain_configs.py keystoneclient/v3/domains.py keystoneclient/v3/ec2.py keystoneclient/v3/endpoint_groups.py keystoneclient/v3/endpoints.py keystoneclient/v3/groups.py keystoneclient/v3/limits.py keystoneclient/v3/policies.py keystoneclient/v3/projects.py keystoneclient/v3/regions.py keystoneclient/v3/registered_limits.py keystoneclient/v3/role_assignments.py keystoneclient/v3/roles.py keystoneclient/v3/services.py keystoneclient/v3/tokens.py keystoneclient/v3/users.py keystoneclient/v3/contrib/__init__.py keystoneclient/v3/contrib/endpoint_filter.py keystoneclient/v3/contrib/endpoint_policy.py keystoneclient/v3/contrib/simple_cert.py keystoneclient/v3/contrib/trusts.py keystoneclient/v3/contrib/federation/__init__.py keystoneclient/v3/contrib/federation/base.py keystoneclient/v3/contrib/federation/core.py keystoneclient/v3/contrib/federation/domains.py keystoneclient/v3/contrib/federation/identity_providers.py keystoneclient/v3/contrib/federation/mappings.py keystoneclient/v3/contrib/federation/projects.py keystoneclient/v3/contrib/federation/protocols.py keystoneclient/v3/contrib/federation/saml.py keystoneclient/v3/contrib/federation/service_providers.py keystoneclient/v3/contrib/oauth1/__init__.py keystoneclient/v3/contrib/oauth1/access_tokens.py keystoneclient/v3/contrib/oauth1/auth.py keystoneclient/v3/contrib/oauth1/consumers.py keystoneclient/v3/contrib/oauth1/core.py keystoneclient/v3/contrib/oauth1/request_tokens.py keystoneclient/v3/contrib/oauth1/utils.py playbooks/run-ds-tox.yaml playbooks/tox-post.yaml python_keystoneclient.egg-info/PKG-INFO python_keystoneclient.egg-info/SOURCES.txt python_keystoneclient.egg-info/dependency_links.txt python_keystoneclient.egg-info/entry_points.txt python_keystoneclient.egg-info/not-zip-safe python_keystoneclient.egg-info/pbr.json python_keystoneclient.egg-info/requires.txt python_keystoneclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml releasenotes/notes/bug-1641674-4862454115265e76.yaml releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml releasenotes/notes/implied_roles-ea39d3c3d998d482.yaml releasenotes/notes/ksc_2.1.0-739ded9c4c3f8aaa.yaml releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml releasenotes/notes/list_role_assignment_names-7e1b7eb8c2d22d7c.yaml releasenotes/notes/project-tags-1f8a32d389951e7a.yaml releasenotes/notes/remove-credentials-data-46ab3c3c248047cf.yaml releasenotes/notes/remove-middleware-eef8c40117b465aa.yaml releasenotes/notes/remove_apiclient_exceptions-0cd5c8d16aa09a22.yaml releasenotes/notes/remove_apiclient_exceptions-6580003a885db286.yaml releasenotes/notes/remove_cli-d2c4435ba6a09b79.yaml releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.popython-keystoneclient-4.0.0/python_keystoneclient.egg-info/not-zip-safe0000664000175000017500000000000113644407143026503 0ustar zuulzuul00000000000000 python-keystoneclient-4.0.0/python_keystoneclient.egg-info/entry_points.txt0000664000175000017500000000124513644407143027555 0ustar zuulzuul00000000000000[keystoneclient.auth.plugin] admin_token = keystoneclient.auth.token_endpoint:Token password = keystoneclient.auth.identity.generic:Password token = keystoneclient.auth.identity.generic:Token v2password = keystoneclient.auth.identity.v2:Password v2token = keystoneclient.auth.identity.v2:Token v3oidcpassword = keystoneclient.contrib.auth.v3.oidc:OidcPassword v3password = keystoneclient.auth.identity.v3:Password v3scopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2ScopedToken v3token = keystoneclient.auth.identity.v3:Token v3unscopedadfs = keystoneclient.contrib.auth.v3.saml2:ADFSUnscopedToken v3unscopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2UnscopedToken python-keystoneclient-4.0.0/python_keystoneclient.egg-info/PKG-INFO0000664000175000017500000000720713644407143025360 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-keystoneclient Version: 4.0.0 Summary: Client Library for OpenStack Identity Home-page: https://docs.openstack.org/python-keystoneclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-keystoneclient-4.0.0/python_keystoneclient.egg-info/requires.txt0000664000175000017500000000031013644407143026647 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 debtcollector>=1.2.0 keystoneauth1>=3.4.0 oslo.config>=5.2.0 oslo.i18n>=3.15.3 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.33.0 requests>=2.14.2 six>=1.10.0 stevedore>=1.20.0 python-keystoneclient-4.0.0/PKG-INFO0000664000175000017500000000720713644407143017225 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-keystoneclient Version: 4.0.0 Summary: Client Library for OpenStack Identity Home-page: https://docs.openstack.org/python-keystoneclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-keystoneclient-4.0.0/.coveragerc0000664000175000017500000000015113644407055020242 0ustar zuulzuul00000000000000[run] branch = True source = keystoneclient omit = keystoneclient/tests/* [report] ignore_errors = True python-keystoneclient-4.0.0/examples/0000775000175000017500000000000013644407143017740 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/0000775000175000017500000000000013644407143020523 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/gen_pki.sh0000775000175000017500000001316613644407055022507 0ustar zuulzuul00000000000000#!/bin/bash # Copyright 2012 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. # These functions generate the certificates and signed tokens for the tests. DIR=`dirname "$0"` CURRENT_DIR=`cd "$DIR" && pwd` CERTS_DIR=$CURRENT_DIR/certs PRIVATE_DIR=$CURRENT_DIR/private CMS_DIR=$CURRENT_DIR/cms function rm_old { rm -rf $CERTS_DIR/*.pem rm -rf $PRIVATE_DIR/*.pem } function cleanup { rm -rf *.conf > /dev/null 2>&1 rm -rf index* > /dev/null 2>&1 rm -rf *.crt > /dev/null 2>&1 rm -rf newcerts > /dev/null 2>&1 rm -rf *.pem > /dev/null 2>&1 rm -rf serial* > /dev/null 2>&1 } function generate_ca_conf { echo ' [ req ] default_bits = 2048 default_keyfile = cakey.pem default_md = default prompt = no distinguished_name = ca_distinguished_name x509_extensions = ca_extensions [ ca_distinguished_name ] serialNumber = 5 countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone emailAddress = keystone@openstack.org commonName = Self Signed [ ca_extensions ] basicConstraints = critical,CA:true ' > ca.conf } function generate_ssl_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = default prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = localhost emailAddress = keystone@openstack.org ' > ssl_req.conf } function generate_cms_signing_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = default prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = Keystone emailAddress = keystone@openstack.org ' > cms_signing_req.conf } function generate_signing_conf { echo ' [ ca ] default_ca = signing_ca [ signing_ca ] dir = . database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/certs/cacert.pem serial = $dir/serial private_key = $dir/private/cakey.pem default_days = 21360 default_crl_days = 30 default_md = default policy = policy_any [ policy_any ] countryName = supplied stateOrProvinceName = supplied localityName = optional organizationName = supplied organizationalUnitName = supplied emailAddress = supplied commonName = supplied ' > signing.conf } function setup { touch index.txt echo '10' > serial generate_ca_conf mkdir newcerts } function check_error { if [ $1 != 0 ] ; then echo "Failed! rc=${1}" echo 'Bailing ...' cleanup exit $1 else echo 'Done' fi } function generate_ca { echo 'Generating New CA Certificate ...' openssl req -x509 -newkey rsa:2048 -days 21360 -out $CERTS_DIR/cacert.pem -keyout $PRIVATE_DIR/cakey.pem -outform PEM -config ca.conf -nodes check_error $? } function ssl_cert_req { echo 'Generating SSL Certificate Request ...' generate_ssl_req_conf openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/ssl_key.pem -keyform PEM -out ssl_req.pem -outform PEM -config ssl_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function cms_signing_cert_req { echo 'Generating CMS Signing Certificate Request ...' generate_cms_signing_req_conf openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/signing_key.pem -keyform PEM -out cms_signing_req.pem -outform PEM -config cms_signing_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function issue_certs { generate_signing_conf echo 'Issuing SSL Certificate ...' openssl ca -in ssl_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/10.pem -out $CERTS_DIR/ssl_cert.pem check_error $? echo 'Issuing CMS Signing Certificate ...' openssl ca -in cms_signing_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/11.pem -out $CERTS_DIR/signing_cert.pem check_error $? } function check_openssl { echo 'Checking openssl availability ...' which openssl check_error $? } JSON_FILES="${CMS_DIR}/auth_token_revoked.json ${CMS_DIR}/auth_token_unscoped.json ${CMS_DIR}/auth_token_scoped.json ${CMS_DIR}/auth_token_scoped_expired.json ${CMS_DIR}/revocation_list.json ${CMS_DIR}/auth_v3_token_scoped.json ${CMS_DIR}/auth_v3_token_revoked.json" function gen_sample_cms { for json_file in $JSON_FILES do openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem} done } python-keystoneclient-4.0.0/examples/pki/certs/0000775000175000017500000000000013644407143021643 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/certs/ssl_cert.pem0000664000175000017500000000245613644407055024175 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpjCCAo4CARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDL06AaJROwHPgJ9tcySSBepzJ81jYars2sMvLjyuvd iIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rpv8dGDIDsxZQVjT/4SLaQUOeDM+9b fkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLYWso0SJD0vAi1gmGDlSM/mmhhHTpC DGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1ZrslPC5lFvlHD7KBBf6IU2A8Xh/dUa3 p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYRR45pPCVkk6vFsy6P0JwwpnkszB+L cK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4YEPirGGrAgMBAAEwDQYJKoZIhvcN AQEFBQADggEBAAjU7YomUx/U56p1KWHvr1B7oczHF8fPHYbuk5c/N81WOJeSRy+P 5ZGZ2UPjvqqXByv+78YWMKGY1BZ/2doeWuydr0sdSxEwmIUBYxFpujuYY+0AjS/n mMr1ZijK7TJssteKM7/MClzghUhPweDZrAg3ff1hbhK5QSy+9UPxUqLH44tfYSVC /BzM6se0p5ToM0bwdsa8TofaBRE1L1IW/Hg4VIGOoKs0R0uLm7+Oot2me2cEuZ6h Wls6MED8ND1Nz8EAKwndkeDu2iMM+qx/YFp6K8BQ5E5nXd2rbUZUlQMp1WbUlZ87 KvC98aT0UYIq6uo1Lx/dQvJs7faAkYd4lmE= -----END CERTIFICATE----- python-keystoneclient-4.0.0/examples/pki/certs/cacert.pem0000664000175000017500000000255713644407055023622 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIID1jCCAr6gAwIBAgIJAJOtRP2+wrM/MA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD VQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55 dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMG CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2Vs ZiBTaWduZWQwIBcNMTMwOTEzMTYyNTQyWhgPMjA3MjAzMDcxNjI1NDJaMIGeMQow CAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1 bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTEl MCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxML U2VsZiBTaWduZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCl8906 EaRpibQFcCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTe qQEZtHpC3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LT Dkjox0eA9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08 XPJX+F0bUDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1 df1fBtZ+6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyT RvEoFie7qtGADIofAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN AQEFBQADggEBAJRMdEwAdN+crqI9dBLYlbBbnQ8xr9mk+REMdz9+SKhDCNdVisWU iLEZvK/aozrsRsDi81JjS4Tz0wXo8zsPPoDnXgDYEicNPTKifbPKgHdDIGFOwBKn y2cF6fHEn8n3KIBrDCNY6rHcYGZ7lbq/8eF0GoYQboPiuYesvVpynPmIK5/Mmire EuuZALAe1IFqqFt+l6tiJU2JWUFjLkFARMOD14qFZm+SInl64toi08j6gdou+NMW 7GEMbVHwNTafM/TgFN5j0yP9SAnYubckLSyH6hwR+rM8dztP5769joxQfnc9O/Bn TBD9KFpeQv6VJWLAxiIKcQCRTTDJLZZ0MQI= -----END CERTIFICATE----- python-keystoneclient-4.0.0/examples/pki/certs/middleware.pem0000664000175000017500000000572613644407055024477 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpjCCAo4CARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDL06AaJROwHPgJ9tcySSBepzJ81jYars2sMvLjyuvd iIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rpv8dGDIDsxZQVjT/4SLaQUOeDM+9b fkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLYWso0SJD0vAi1gmGDlSM/mmhhHTpC DGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1ZrslPC5lFvlHD7KBBf6IU2A8Xh/dUa3 p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYRR45pPCVkk6vFsy6P0JwwpnkszB+L cK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4YEPirGGrAgMBAAEwDQYJKoZIhvcN AQEFBQADggEBAAjU7YomUx/U56p1KWHvr1B7oczHF8fPHYbuk5c/N81WOJeSRy+P 5ZGZ2UPjvqqXByv+78YWMKGY1BZ/2doeWuydr0sdSxEwmIUBYxFpujuYY+0AjS/n mMr1ZijK7TJssteKM7/MClzghUhPweDZrAg3ff1hbhK5QSy+9UPxUqLH44tfYSVC /BzM6se0p5ToM0bwdsa8TofaBRE1L1IW/Hg4VIGOoKs0R0uLm7+Oot2me2cEuZ6h Wls6MED8ND1Nz8EAKwndkeDu2iMM+qx/YFp6K8BQ5E5nXd2rbUZUlQMp1WbUlZ87 KvC98aT0UYIq6uo1Lx/dQvJs7faAkYd4lmE= -----END CERTIFICATE----- -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL06AaJROwHPgJ 9tcySSBepzJ81jYars2sMvLjyuvdiIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rp v8dGDIDsxZQVjT/4SLaQUOeDM+9bfkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLY Wso0SJD0vAi1gmGDlSM/mmhhHTpCDGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1Zrs lPC5lFvlHD7KBBf6IU2A8Xh/dUa3p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYR R45pPCVkk6vFsy6P0JwwpnkszB+LcK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4 YEPirGGrAgMBAAECggEATwvbY0hNwlb5uqOIAXBqpUqiQdexU9fG26lGmSDxKBDv 9o5frcRgBDrMWwvDCgY+HT4CAvB9kJx4/qnpVjkzJp/ZNiJ5VIiehIlbv348rXbh xkk+bz5dDATCFOXuu1fwL2FhyM5anwhMAav0DyK1VLQ3jGzr9GO6L8hqAn+bQFFu 6ngiODwfhBMl5aRoL9UOBEhccK07znrH0JGRz+3+5Cdz59Xw91Bv210LhNNDL58+ 0JD0N+YztVOQd2bgwo0bQbOEijzmYq+0mjoqAnJh1/++y7PlIPs0AnPgqSnFPx9+ 6FsQEVRgk5Uq3kvPLaP4nT2y6MDZSp+ujYldvJhyQQKBgQDuX2pZIJMZ4aFnkG+K TmJ5wsLa/u9an0TmvAL9RLtBpVpQNKD8cQ+y8PUZavXDbAIt5NWqZVnTbCR79Dnd mZKblwcHhtsyA5f89el5KcxY2BREWdHdTnJpNd7XRlUECmzvX1zGj77lA982PhII yflRBRV3vqLkgC8vfoYgRyRElwKBgQDa5jnLdx/RahfYMOgn1HE5o4hMzLR4Y0Dd +gELshcUbPqouoP5zOb8WOagVJIgZVOSN+/VqbilVYrqRiNTn2rnoxs+HHRdaJNN 3eXllD4J2HfC2BIj1xSpIdyh2XewAJqw9IToHNB29QUhxOtgwseHciPG6JaKH2ik kqGKH/EKDQKBgFFAftygiOPCkCTgC9UmANUmOQsy6N2H+pF3tsEj43xt44oBVnqW A1boYXNnjRwuvdNs9BPf9i1l6E3EItFRXrLgWQoMwryakv0ryYh+YeRKyyW9RBbe fYs1TJ8unx4Ae79gTxxztQsVNcmkgLs0NWKTjAzEE3w14V+cDhYEie1DAoGBAJdI V5cLrBzBstsB6eBlDR9lqrRRIUS2a8U9m+1mVlcSfiWQSdehSd4K3tDdwePLw3ch W4qR8n+pYAlLEe0gFvUhn5lMdwt7U5qUCeehjUKmrRYm2FqWsbu2IFJnBjXIJSC4 zQXRrC0aZ0KQYpAL7XPpaVp1slyhGmPqxuO78Y0dAoGBAMHo3EIMwu9rfuGwFodr GFsOZhfJqgo5GDNxxf89Q9WWpMDTCdX+wdBTrN/wsMbBuwIDHrUuRnk6D5CWRjSk /ikCgHN3kOtrbL8zzqRomGAIIWKYGFEIGe1GHVGo5r//HXHdPxFXygvruQ/xbOA4 RGvmDiji8vVDq7Shho8I6KuT -----END PRIVATE KEY----- python-keystoneclient-4.0.0/examples/pki/certs/signing_cert.pem0000664000175000017500000000245613644407055025032 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpTCCAo0CAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgY8xCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAMz5WsgsuX3rZUdLwQpZXN2Ro7LQ6jEZnreBqMztVObw BuC1WdiJsg6dVlC7PVdt+0gY1c8WFg1TKmsucxesQSyfGAPg+9T/hsRMb6y12uJx fp3Wgqqw0U1HsXvMiaJH87MaGnt043BxzF+R9fhAcDk6Cyj5cx9J0LvZJEOzN4J4 ZRyO6j/DZZItb3lK5W9xkuoT+mTdDZOQJnXyG818uiWfjdCkLjr1ruytRcBOo4na Y828voT/A7I95+YCgKgbjiUWhHeTaNmMEQiGy0nGYfteC+oSsHOlxZ3b12azzHPk 83Bh2ez0Ih9vcZoe9DqvlFOXfv9q8OsYc5Yo6gPTXEsCAwEAATANBgkqhkiG9w0B AQUFAAOCAQEAmaYE98kOQWu6DV84ZcZP/OdT8eeu3vdB247nRj+6+GYItN/Gzqt4 HVvz7c+FVTolCcAQQ+z3XGswI9fIJ78Hb0p9CgnLprc3L7Xtk60Im59Xlf3tcurn r/ZnSDcjRBXKiEDrSM0VrhAnc0GoSeb6aDWopec+1hWOWfBVAg9R8yJgU9sUgO3O 0gimGyrw8eubmNhckSQLJTunUTsrkcBjuSg63wAD9OqCiX6c2eoQr+0YBp2eV2/n aOiJXWNLbeueMKSYiJNyyvM/dlON7/56cdwDTzKzgD34TImouM5VKipUwCX1ovLu ITLzALzpqFFzc8ugV9pMgUKtDbZoPp9EEA== -----END CERTIFICATE----- python-keystoneclient-4.0.0/examples/pki/private/0000775000175000017500000000000013644407143022175 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/private/signing_key.pem0000664000175000017500000000325413644407055025214 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDM+VrILLl962VH S8EKWVzdkaOy0OoxGZ63gajM7VTm8AbgtVnYibIOnVZQuz1XbftIGNXPFhYNUypr LnMXrEEsnxgD4PvU/4bETG+stdricX6d1oKqsNFNR7F7zImiR/OzGhp7dONwccxf kfX4QHA5Ogso+XMfSdC72SRDszeCeGUcjuo/w2WSLW95SuVvcZLqE/pk3Q2TkCZ1 8hvNfLoln43QpC469a7srUXATqOJ2mPNvL6E/wOyPefmAoCoG44lFoR3k2jZjBEI hstJxmH7XgvqErBzpcWd29dms8xz5PNwYdns9CIfb3GaHvQ6r5RTl37/avDrGHOW KOoD01xLAgMBAAECggEAaIi22qWsh+JYCW9B6NRAPyN6V8Sh2x6UykOO4cwb45b/ +vOh+YPn0fo9vfhvxTnq0A8SY4WBA5SpanYK7kTEDEyqw7em1y7l/RB6V5t7IMb+ 6uIuS3zXkVEB3AApJSEK0Ql7/gBTydHPh+H5jnzWfujyLhhhtNBBarvH+drZcWio lWx8RERN4cH+3DZD/xxjH2Ff+X1XMvb8Xcup7MlWi2FtREg7LttLNWNK25iWjciP QwfWQIrURRJrD2IrOr9V2nuIEvRqRRBoO+pxJT2sC48NJ3hiKV2GtSQe2nRpQJ47 f9MEsF5KVQOOn+aQ60EKOI0MpNPmpiCZ5hFvBrNuOQKBgQD6vueEdI9eJgz5YN+t XWdpNippv35RTD8R4bQcE6GqIUXOmtQFS2wPJLn7nisZUsGMNEs36Yl0T9iow63r 5GNAfgzpqN1XZqaSMwAdxKmlBNYpAkVXHhv+1jN+9diDYmoj9T+3Q6Zvk5e/Liyp 6i+TsDppwmmr2utWajhyJ7owFwKBgQDRROncTztGDYLfRcrIoYsPo79KQ8tqwd2a 07Usch2kplTqojCUmmhMMFgV2eZPPiCjnEy2bAYh9I/oj7xG6EwApXTshZdCpivC rbUV64MakRTUP8IvM6PdI+apkJRsRUi/bSyIbcRlvEoCMNZhfj/5VY6w/jlwrPJj oBOCXBlB7QKBgQDGEbEeX1i03UfYYh6uep7qbEAaooqsu5cCkBDPMO6+TmQvLPyY Zhio6bEEQs/2w/lhwBk+xHqw5zXVMiWbtiB03F1k4eBeXxbrW+AWo7gCQ4zMfh+6 Dm284wVwn9D1D/OaDevT31uEvcjb2ySq3/PPLSEnU8xXVaoa6/NEsX8Q5wKBgQCm 2smULWBXZKJ6n00mVxdnqun0rsVcI6Mrta14+KwGAdEnG5achdivFsTE924YtLKV gSPxN4RUQokTprc52jHvOf1WMNYAADpYCOSfy55G6nKvIP8VX5lB00Qw4uRUx5FP gB7H0K2NaGmiAYqNRXqAtOUG3kyyOFMzeAjWIdTJqQKBgQCHzY1c7sS1vv7mPEkr 6CpwoaEbZeFnWoHBA8Rd82psqfYsVJIRwk5Id8zgDSEmoEi8hQ9UrYbrFpLK77xq EYSxLQHTNlM0G3lyEsv/gJhwYYhdTYiW3Cx3F6Y++jyn9O/+hFMyQvuesAL7DUYE ptEfvzFprpQUpByXkIpuJub6fg== -----END PRIVATE KEY----- python-keystoneclient-4.0.0/examples/pki/private/cakey.pem0000664000175000017500000000325013644407055023776 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCl8906EaRpibQF cCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTeqQEZtHpC 3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LTDkjox0eA 9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08XPJX+F0b UDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1df1fBtZ+ 6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyTRvEoFie7 qtGADIofAgMBAAECggEBAJ47X3y2xaU7f0KQHsVafgI2JAnuDl+zusOOhJlJs8Wl 0Sc1EgjjAxOQiqcaE96rap//qqYDTuFLjCenkuItV32KNzizr3+GLZWaruRHS6X4 xpFG2/gUrsQL3fdudOxpP+01lmzW+f25xRvZ4VilWRabquSDntWxA0R3cOwKFbGD uuwbTw3pBrRfCk/2IdpQtRrvvkVIFiYT6b/zeCQzhp4RETbC0oxqcEEOIUGmimAV 9cbwafinxCo54cOfX4JAh3j7Mp3eQUymoFk5gnmIeVe0QmpH2VkN7eItrhEvHKOk On7a5xvQ8s3wqPV5ZawHQcqar/p3QnGkiT6a+8LkIMECgYEA2iJ2DprTGZFRN0M7 Yj4WLsSC3/GKK8eYsKG3TvMrmPqUDaiWLIvBoc1Le59x9eoF7Mha+WX+cAFL+GTg 1sB+PUZZStpf1R1tGvMldvpQ+5GplUBpuQe4J0n5rCG6+5jkvSr7xO+G1B+C3GFq KR3iltiW5WJRVwh2k8yGvx3agyUCgYEAwsKFX82F7O+9IVud1JSQWmZMiyEK+DEX JRnwx4HBuWr+AZqbb0grRRb6x8JTUOD4T7DZGxTaAdfzzRjKU2sBAO8VCgaj2Auv 5nsbvfXvrmDDCqwoaD2PMy+kgFvE0QTh65tzuGXl1IgpIYSC1JwnP6kOeUDbqE+k UXzfVZzDdvMCgYByk9dfJIPt0h7O4Em4+NO+DQqRhtYE2PqjDM60cZZc7IIICp2X GHHFA4i6jq3Vde9WyIbAqYpUWtoExzgylTm6BdGxN7NOxf4hQcZUEHepLIHfG85s mlloibrTZ4RH06+SjZlhgE9Z7JNYHvMcVc5HXc0k/9ep15AxYiUFDjFQ4QKBgG7i k089U4/X2wWgBNdgkmN1tQTNllJCmNvdzhG41dQ8j0vYe8C7BS+76qJLCGaW/6lX lfRuRcUg78UI5UDjPloKxR7FMwmxdb+yvdPEr2bH3qQ36nWW/u30pSMTnJYownwD MLp/AYCk2U4lBNwJ3+rF1ODCRY2pcnOWtg0nSL5zAoGAWRoOinogEnOodJzO7eB3 TmL6M9QMyrAPBDsCnduJ8yW5mMUNod139YbSDxZPYwTLhK/GiHP/7OvLV5hg0s4s QKnNaMeEowX7dyEO4ehnbfzysxXPKLRVhWhN6MCUc71NMxqr7QkuCXAjJS6/G21+ Im3+Xb3Scq+UZghR+jiEZF0= -----END PRIVATE KEY----- python-keystoneclient-4.0.0/examples/pki/private/ssl_key.pem0000664000175000017500000000325013644407055024353 0ustar zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL06AaJROwHPgJ 9tcySSBepzJ81jYars2sMvLjyuvdiIBbhWvbS/a9Tw3WgL8H6OALkHiOU/f0A6Rp v8dGDIDsxZQVjT/4SLaQUOeDM+9bfkKHpSd9G3CsdSSZgOH08n+MyZ7slPHfUHLY Wso0SJD0vAi1gmGDlSM/mmhhHTpCDGo6Wbwqare6JNeTCGJTJYwrxtoMCh/W1Zrs lPC5lFvlHD7KBBf6IU2A8Xh/dUa3p5pmQeHPW8Em90DzIB1qH0DRXl3KANc24xYR R45pPCVkk6vFsy6P0JwwpnkszB+LcK6CEsJhLsOYvQFsiQfSZ8m7YGhgrMLxtop4 YEPirGGrAgMBAAECggEATwvbY0hNwlb5uqOIAXBqpUqiQdexU9fG26lGmSDxKBDv 9o5frcRgBDrMWwvDCgY+HT4CAvB9kJx4/qnpVjkzJp/ZNiJ5VIiehIlbv348rXbh xkk+bz5dDATCFOXuu1fwL2FhyM5anwhMAav0DyK1VLQ3jGzr9GO6L8hqAn+bQFFu 6ngiODwfhBMl5aRoL9UOBEhccK07znrH0JGRz+3+5Cdz59Xw91Bv210LhNNDL58+ 0JD0N+YztVOQd2bgwo0bQbOEijzmYq+0mjoqAnJh1/++y7PlIPs0AnPgqSnFPx9+ 6FsQEVRgk5Uq3kvPLaP4nT2y6MDZSp+ujYldvJhyQQKBgQDuX2pZIJMZ4aFnkG+K TmJ5wsLa/u9an0TmvAL9RLtBpVpQNKD8cQ+y8PUZavXDbAIt5NWqZVnTbCR79Dnd mZKblwcHhtsyA5f89el5KcxY2BREWdHdTnJpNd7XRlUECmzvX1zGj77lA982PhII yflRBRV3vqLkgC8vfoYgRyRElwKBgQDa5jnLdx/RahfYMOgn1HE5o4hMzLR4Y0Dd +gELshcUbPqouoP5zOb8WOagVJIgZVOSN+/VqbilVYrqRiNTn2rnoxs+HHRdaJNN 3eXllD4J2HfC2BIj1xSpIdyh2XewAJqw9IToHNB29QUhxOtgwseHciPG6JaKH2ik kqGKH/EKDQKBgFFAftygiOPCkCTgC9UmANUmOQsy6N2H+pF3tsEj43xt44oBVnqW A1boYXNnjRwuvdNs9BPf9i1l6E3EItFRXrLgWQoMwryakv0ryYh+YeRKyyW9RBbe fYs1TJ8unx4Ae79gTxxztQsVNcmkgLs0NWKTjAzEE3w14V+cDhYEie1DAoGBAJdI V5cLrBzBstsB6eBlDR9lqrRRIUS2a8U9m+1mVlcSfiWQSdehSd4K3tDdwePLw3ch W4qR8n+pYAlLEe0gFvUhn5lMdwt7U5qUCeehjUKmrRYm2FqWsbu2IFJnBjXIJSC4 zQXRrC0aZ0KQYpAL7XPpaVp1slyhGmPqxuO78Y0dAoGBAMHo3EIMwu9rfuGwFodr GFsOZhfJqgo5GDNxxf89Q9WWpMDTCdX+wdBTrN/wsMbBuwIDHrUuRnk6D5CWRjSk /ikCgHN3kOtrbL8zzqRomGAIIWKYGFEIGe1GHVGo5r//HXHdPxFXygvruQ/xbOA4 RGvmDiji8vVDq7Shho8I6KuT -----END PRIVATE KEY----- python-keystoneclient-4.0.0/examples/pki/cms/0000775000175000017500000000000013644407143021305 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped_expired.pkiz0000664000175000017500000000375113644407055027432 0ustar zuulzuul00000000000000PKIZ_eJylVtuSmzgQfddX7PtUKiDsGfOQBy4yhrHEcLd4MzAGZLA99pjb16_Ak2QySW2yu66iDC3RnO5zulufPvGfigyT_KVhb3z4BLBpmnZOrcdjbBZNShQn1Y7c9jho_JdqioM6zVdWah6c9RxrBtM0dZ8amvdieGYiAd1BK2XLjSxHeU6F594qKCRVKuHSLtUH8-A2WxheTXbM-doplYgYR-6Obhy-rpQAM6XFpdBiT-jspdNj_9gR_dgS8ViuNaWMN0W73VhV2pv3pmb2WEft2lcgv_pQRwKwmSPZDAtRaV5MzTrF2rjRahNjyeKoaBLDrdLbmhBH8yI5ODdkdXilkXUBcTQZhPQQVuMXt9ENWmaMG-bCBlZ77E0O-LNYjaHwsKqkXl6zpXwFo_Ftwz7eEJbWVZsZVZOUHIkxFxOjk3dey1_ieTnEJwpDnW7cIjHkw-gMRNKl5NB4XuVTcnCH0TjaaOS-bqN5GOzbCdF25QqpfmzWA-pJP2vXTLnyfM0AGVK4lmi3HqhAWVxjGJcUYhEPzkCiYEZ92sY1qW29KinjK35WmOWIyKpiWDVggqpZfRxlpwTOn5I6KG-5mAvxZoy7-0cUYITx31GoIqB1d6Ji6Pk3-o7Zym3tctFg35SmOLVZZ7NcIgNtMoYawtyS1HSIa4vRIRgA0bEY-0VBmFpTSGd2ZJWE0Y5AVO5CYWSHU-a2Cayu2YrsEqO6bm8qTTacHcA5nadGcOO-li_ZyPUIr-aiiT7YD9zh6kfWwL-kbY7Zvh_z9ZUJMFLxf5gAv_asin-W3H0PbN8cs_tHCXufr20kFjEMSjBC5YXxGnvTlw68Cq-UL4z65_X_zuHN0dgvYkM8JdUNHfhn7p0R3RV7C0gME8aMK6AmjPhYwENY2QaCABsxi1k-p7UJCUMSvVXmW0JnE9y0Dg_bSL76cP5GMUdkhD1HfgFhaOGpxutCyFbK_bpfdJilIwpOHbq3dd7ENBlib_ZLqYPfaf13bIB_E-_P4VoymOjh_S1eqc0onFSUL_z_PDXR5Ws2spStqvaNJZZAsc027je5g696T2oZjh7X2q1hfnN4c8Rty30SVdePOQMfk4Z5iRI_5eGY3PYzI8EHRsB7Sn7HyC-ctyDjvX0bkd9VoYh1peW10vPnH2QP_kz3fA4c3FO22pcfpH8G8aYaMkO-xjyBtxfDId6Yb3NxvH8_UKbn3eTAR5MzkPJuwwfKDwh4o-AjLfjaNMb73qyE96NPTGHYj1MLvE2lPoFd9Z2yan9LJm25bPfUL2oupAEzZ06ZVZCB90-2rLGfQkD9jDdR3H3sgxMyDvOtrL9-ucY6qWMDzWJWFHgw-XSOzJ76Ka8Fs4u5PEnNJcob9s8D9S2Ug5i9TyT4Hs_09Y5vkHe-oSnpsc3zlaHkSMWmsefXM3aOraZQPXScJWqRiJ1LCzRnMhiotcJgQGus7A1FDJCmYs0RUIeY4qg5CVUl9bWQiEk9n2dcdDw8D6uKAabNBbZ8Sa2Sigg0ImfsolZvJ8dr1Bbrb1T7qMIa_nY-4scjCygujfgZaJ5KbpPUoZKMjg43R-ta7uMBBVg1J1RKh9cBDC9xqfrbKLvyw4nGHaBWbenysZ3pSnFsdef9iQ2pqqPwwxdShifbKSSRDulneAkzL_1s95acEXAHC8PNXUdeP_Qrz9qeXvW6znWfvOrq4irIun8XtKKPVnfijJEHMs9DT9RWUmE9KjynDFjbJfPxS7Mx0iWWFs9RWj46L3Z3V587XM1PWwNCXoTJ2UNDv1d9vvuz9tLXV_kxDWYYgqHYd8_N6XzoH2b-xWpw0y_gJtAsxErjRb5G4RbKz_YBO2VeLXPDpyxSbVs8N2ZTBVgCiztMAyErXrdbb7N_Me8fcjVnq9dBsKTF4alod0-SuyzE3LNe81ZdFU6TCv48WWXN-hB7O_B8DmemaSyebLh7CO6kaJFerYooCIa2zek7UjVGq6ck30Okq4_3s0OV2WpyLwkwwsqXL2A6MSOifz89_w1E1sSWpython-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_scoped.pem0000664000175000017500000001730713644407055026130 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIWmgYJKoZIhvcNAQcCoIIWizCCFocCAQExCTAHBgUrDgMCGjCCFKcGCSqGSIb3 DQEHAaCCFJgEghSUew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgIm1ldGhvZHMi OiBbDQogICAgICAgICAgICAicGFzc3dvcmQiDQogICAgICAgIF0sDQogICAgICAg ICJyb2xlcyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiaWQi OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg ICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3 MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogInJvbGUy Ig0KICAgICAgICAgICAgfQ0KICAgICAgICBdLA0KICAgICAgICAiaXNzdWVkX2F0 IjogIjIwMDItMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImV4cGlyZXNfYXQi OiAiMjAzOC0wMS0xOFQyMToxNDowN1oiLA0KICAgICAgICAicHJvamVjdCI6IHsN CiAgICAgICAgICAgICJpZCI6ICJ0ZW5hbnRfaWQxIiwNCiAgICAgICAgICAgICJk b21haW4iOiB7DQogICAgICAgICAgICAgICAgImlkIjogImRvbWFpbl9pZDEiLA0K ICAgICAgICAgICAgICAgICJuYW1lIjogImRvbWFpbl9uYW1lMSINCiAgICAgICAg ICAgIH0sDQogICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAgICAgICAg ICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgIm5hbWUiOiAidGVu YW50X25hbWUxIg0KICAgICAgICB9LA0KICAgICAgICAiY2F0YWxvZyI6IFsNCiAg ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAg ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi OiAiM2I1ZTU1NGJjZjExNGYyNDgzZThhMWJlN2EwNTA2ZDEiLA0KICAgICAgICAg ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAg ICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82 NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAg ICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAg ICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICAgICAgICAgImlkIjogIjU0YWJkMmRjNDYzYzRiYTRhNzI5MTU0OThmOGVjYWQx IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiaW50ZXJu YWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3 LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei LA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUi DQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICI3MGE3ZWZhNGIxYjk0MTk2 ODM1N2NjNDNhZTE0MTllZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJmYWNlIjogInB1YmxpYyIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJs IjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2 MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9u IjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAg ICAgICAgIF0sDQogICAgICAgICAgICAgICAgImlkIjogIjU3MDdjM2ZjMGEyOTQ3 MDNhM2M2MzhlOWNmNmE2YzNhIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2 b2x1bWUiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAg ICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBv aW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICAgICAgICAgImlkIjogIjkyMjE3YTNiOTUzOTQ0OTI4NTliYzQ5ZmQ0NzQzODJm IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4i LA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAu MC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6 ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAg ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICJmMjA1 NjNiZGY2NmY0ZWZhOGExZjExZDk5YjY3MmJlMSIsDQogICAgICAgICAgICAgICAg ICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAg ICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMzc1ZjliYTQ1OWE0NDc3MzhmYjYw ZmU1ZmMyNmU5YWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj ZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJo dHRwOi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAg ICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0N CiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICIxNWMy MWFhZTZiMjc0YThkYTUyZTBhMDY4ZTkwOGFhYyIsDQogICAgICAgICAgICAgICAg InR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImdsYW5j ZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAg ICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICAgICAgICAgImlkIjogImVkYmQ5ZjUwZjY2NzQ2YWU5ZWQxMWRj M2IxYWUzNWRhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2Ui OiAiYWRtaW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRw Oi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5 YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJl Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAg ICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjllMDNjNDZj ODBhMzRhMTU5Y2IzOWY1Y2IwNDk4YjkyIiwNCiAgICAgICAgICAgICAgICAgICAg ICAgICJpbnRlcmZhY2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAg ICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAg fSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg ICAgImlkIjogIjFkZjBiNDRkOTI2MzRkNTliZDBlMGQ2MGNmN2NlNDMyIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4 Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAg ICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAg ICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAg ICAgICAgICJpZCI6ICIyZjQwNGZkYjg5MTU0YzU4OWVmYmMxMDcyNmIwMjllYyIs DQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIsDQogICAgICAgICAg ICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAg ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogImE0NTAx ZTE0MWE0YjRlMTRiZjI4MmU3YmZmZDgxZGM1IiwNCiAgICAgICAgICAgICAgICAg ICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4iLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAg ICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAg ICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAg ICAgICAgICAgICAgICAgICAiaWQiOiAiM2QxN2UzMjI3YmZjNDQ4M2I1OGRlNWVh YTU4NGUzNjAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6 ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjMiLA0KICAgICAgICAgICAgICAgICAgICAg ICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0s DQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAg ICJpZCI6ICI4Y2Q0Yjk1NzA5MGY0Y2E1ODQyYTIyZTlhNzQwOTljZCIsDQogICAg ICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQogICAg ICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAw MC92MyIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lv bk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s DQogICAgICAgICAgICAgICAgImlkIjogImM1ZDkyNmQ1NjY0MjRlNGZiYTRmODBj Mzc5MTZjZGU1IiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpZGVudGl0eSIs DQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5c3RvbmUiDQogICAgICAgICAg ICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2VyIjogew0KICAgICAgICAgICAg ImRvbWFpbiI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIs DQogICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAg ICAgICAgfSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg ICAgICAgICAgImlkIjogInVzZXJfaWQxIg0KICAgICAgICB9DQogICAgfQ0KfQ0K MYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD VQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sx ETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu c3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkq hkiG9w0BAQEFAASCAQCPCzpknZOfDONpHDWGrYTeyirjGGjrJem2EF2qsJ4K1x/V guNLX1AfRnRUC95wSpGS5VCQ+OSfSFmLjJQOnMqLZ1L2MkVfn0CIkqig19sgRZ+O hpi+0TpJ6XlCWRERJEICCOAHZ/M2iiiVFbFkIGtaJLw3HcXFreV+nEBuQSeIGH/H FjnmocYu9vy612YT47HcyQKNMaku3QBLzFTSTiGkS4ft9yT2pNMbHZsMmysaRKWl SfuA/DZHT6zi5D4lkxDBCexf3JAw4kOQSf/dirfDUKmIy4VPeAOuO1u86hN/coIS JvgAJGOVUxtZCQ9256dUvKa1pLpQAgW/Ok3oPulS -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_revoked.json0000664000175000017500000001214413644407055026474 0ustar zuulzuul00000000000000{ "token": { "catalog": [ { "endpoints": [ { "id": "3b5e554bcf114f2483e8a1be7a0506d1", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "54abd2dc463c4ba4a72915498f8ecad1", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "70a7efa4b1b941968357cc43ae1419ee", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "5707c3fc0a294703a3c638e9cf6a6c3a", "type": "volume", "name": "volume" }, { "endpoints": [ { "id": "92217a3b95394492859bc49fd474382f", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "f20563bdf66f4efa8a1f11d99b672be1", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "375f9ba459a447738fb60fe5fc26e9aa", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "id": "15c21aae6b274a8da52e0a068e908aac", "type": "image", "name": "glance" }, { "endpoints": [ { "id": "edbd9f50f66746ae9ed11dc3b1ae35da", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "9e03c46c80a34a159cb39f5cb0498b92", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "1df0b44d92634d59bd0e0d60cf7ce432", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "2f404fdb89154c589efbc10726b029ec", "type": "compute", "name": "nova" }, { "endpoints": [ { "id": "a4501e141a4b4e14bf282e7bffd81dc5", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "3d17e3227bfc4483b58de5eaa584e360", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "8cd4b957090f4ca5842a22e9a74099cd", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "id": "c5d926d566424e4fba4f80c37916cde5", "type": "identity", "name": "keystone" } ], "issued_at": "2002-01-18T21:14:07Z", "expires_at": "2038-01-18T21:14:07Z", "audit_ids": ["ZzzZ2ZZYqT8OzfUVvrjEITQ", "cCCCCCctTzO1-XUk5STybw"], "project": { "enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "user": { "name": "revoked_username1", "id": "revoked_user_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "methods": [ "password" ] } } python-keystoneclient-4.0.0/examples/pki/cms/auth_token_revoked.pkiz0000664000175000017500000000404113644407055026065 0ustar zuulzuul00000000000000PKIZ_eJylVkt3ozgT3etXzD6nTwBjJ17MQjyMRSzZYAxIOwMJIB524gePX_8Jk0xn0n2me77xxlCC4ureW1X69k38NNNC5A8db4ebbwAjhHaQ2k8HhrJrTKAT6wcRczxd1w1Jh47Z6h5caunuzUixbnFd10rJ0rev1hZFE2A45hLuRbBQzTRlT8-dnVGFlPEE5-tce0C1e90r_gXxQyrWjvGEyCxwX2joiHWYA8xhg3OpwVupXS-cDnuHlhiHhsiHfKXDnIVZsw_tMu7QDOmowwZWVx5sV56p-gZqwZqb0prDSZCjE9LtI9OHB-0mshacBdk1stwyHtckFkyzqHZGZJV_oYF9Aiy4BaS49svhi_tghJZYwwNTKVTKAm9vCcS9XA5bEdsqo2pxSRbzCxiC7w8ULCQ8rsomscprlAsk1lSOrHb-sm3ES4KXmh2p4hs0dLPImtdDMhBMTrmAVsTW_BjVbj8EhxgN3PM-mPq7orkh2i9dKTYO11VvdqRTmxWHF8GXCkgfK6sJbVc9lShnFVZYThUs497pSbBTqUcbVpFqbZQ55WLFSzKUD4jskinlFdyg6nbHguQYKdNNVO3ykYupxMJh3-0_ogADjP8fhSYDWrVHKvtbb5TvkCzdZp0_XrGHJrd96mq75umE9PSacPNKuJuTivassjntdz0gBpaZl2WEaxVVqLoO7Jxw2hLFzF98aVBHSOY2kVJekiV5iazysh9dGoVCHSA0ncbWbtS-mp-SQesBXiVME3yJ1yLh8u-qgX8p2xTzohv4-lACDFL8FyXAzzNr8u-RW3Rg7aGB3d8i7DNf-0DOmLLLwQBVFMaZbW9fqkUVXqhYGPwv6v9TwjHR0C-YJR-jckQH_ll7Z0B3wdtHhVhIYVw4oCKceFjCvV-uLVMB2GKc8XRKK6QQbk7oWJnvhKo3uHHl1_tgfvGU6bvEApHldwL5Cfi-jW81XmVSsoSzVffYYh4PKIR05mxtiCamzxW8VX9qdfArr_9KDfBv9vvjdu05uMkj-htbatfBOLE8P4n_t1sTXZyTQaVkWTbvKvFIkZskdP-yO_jwe1TNlSHjSh8b5l8Jx0QitiiioLx85Qx8JQ2LEiVeLLaDROxHRXZfFAGfJfmVIj9J3oBE9PZ9QH5VhTI2YCNqpRP3f7M9-D3fizlQu8dkWeRfrP8GWFj2iTW_MEHg-KLfsxC9z8Xh-vNAsUvRXN6G2ZiEYk68q_DRHMQY8_tQaY9RdR7nQ2d3kdJ-DJ7xOreTzxMMCJ8rkXIu2WIux4rffRpltxe-y1gWI8G0EVYuqJdVwly9mM7OlHI7I71oqnxRYS9WqJeIxorbr70xFr2ReeZHqd8G8VDOFTZIxSxTZTzLcI-kdYA66sWiPlDLhGVJJWwrmviPQ9YWk8nadaiWky_sdixkw8GiCCfficSC6JdQatN0-SRONlqbIg1AT9eOhq7V3HzCMLWgvDM1F2vEM1cYFuN9hnXfx63eQ1tLia_B1IMF0bCLGmBCaviOszSb0kuC6eU5ZGJ071qTQ2d8-ODpu3kjZoGXiEPHvjddDB9vifUWI7BV_Gk8ca-ilbe2B7mWFq9ZkVvzRtJ0xwwW1bl8Dokk2n3pWLdE_S1RN73GVdyChQG345ewp8ukjCya7pSyjiq_gOnwtdjStqc1bNAew--nM3E406Czg0ATZMAFn0pmu102GdE2eWhrLybzSqvOEc8n8LJlq0g9L06bbtIfD1acv21OyvLk6kb3Bp4QtCYpT6PzDLZP8n5Wwf1dc7w7blCXcsuzZEWPC3y_UFf1RZVbQ1XWj538TKM7PF89WkDG98-hu9laucfd3RVqao5fpSe-Wbjqw7qfxcfkGDMyu3cbVWXO9m55ThBaeQQnC1p6BKiBVOuHh24fsocHLV3fvzqlVlPJC-zjYfase-fr9IyuxdWfNefEhnpyj9y7N_rcsOeFaGOgxmdovBYUBqbjPhQPrSvL-LHbrifzqzQld_3GOLGNUt_Npe40zarJpFyJOb905oi60SsEslMv7oOYuA9v_Cl5aJhHZhMEX7B9-BPcjtMmMb4frf8Hm6bNOA==python-keystoneclient-4.0.0/examples/pki/cms/auth_token_revoked.pem0000664000175000017500000001161313644407055025674 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIOTQYJKoZIhvcNAQcCoIIOPjCCDjoCAQExCTAHBgUrDgMCGjCCDFoGCSqGSIb3 DQEHAaCCDEsEggxHew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMzgtMDEtMThUMjE6MTQ6MDda IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5 MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6 Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6 ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2 MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7 DQogICAgICAgICAgICAidXNlcm5hbWUiOiAicmV2b2tlZF91c2VybmFtZTEiLA0K ICAgICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJy b2xlMSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwN CiAgICAgICAgICAgICJpZCI6ICJyZXZva2VkX3VzZXJfaWQxIiwNCiAgICAgICAg ICAgICJyb2xlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAg ICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIs DQogICAgICAgICAgICAgICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAg ICAgICAgIH0sDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAg ICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAg ICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAgICAg ICB9DQogICAgICAgICAgICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAicmV2b2tl ZF91c2VybmFtZTEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHKMIIBxgIBATCB pDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYD VQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5 c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDAS BgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIB AA2C5qslA4D7vzbiPJ+PzI6CWKH4fxy2nl6wFneHRlzflRGVtbk7/gwVpgHvVH8+ FvQEWeXiCvpXDcHUae0YsdB6aifDRkRctoBwWZkSIkLtdLjZTBrwoOBD2cWPTlr6 gFPp0ARCKVP87YXiKHXStvivZDQFbnBrPTZbGwsCZFXzDYtVPkDvgWOIzHP+olB0 k0wrFXdTQrr62GmkUdgmY31SBLAmPRlvbFBsdM8R62EVc9Mdk7A8Xenpib6+3hPV 7Jgj5IcC3WWtI1A/WOzuEepfW5AU3bcmsJ4UrsJdZLPYqxy/FS37s7oekBOfSR+Y WSVmaaTY21X3kOqAQULJTDI= -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/revocation_list.pkiz0000664000175000017500000000222113644407055025407 0ustar zuulzuul00000000000000PKIZ_eJx9VElzszgUvPMr5p5KBbPY5vAdtGAsYokAYr0ZbIvdSXDM8usHO1Uzl6lRlQ6v1dVPr6vrvb4uB5oWYX8h6j-KV4kSgvmQ2O_XlBT3nAE3R9cFczFCYB4QcM0RcbCHIvjGgiKrWvBwsJD_ZfkkUyXsmntwXMBANoXY2efJntI4vR-VsCbVVURqX6ZxMRxju8knsiaITJSb04ED7cBNWQqxqTpVoDmVq0Ul6QmyP1P0INp1UtVaGrlTEiVKMicqxacyjaiSWvRRaw4nquTgpqDINg4IbkgbarnVLD-gpVOCklbmSEt5cJA8sp07svm6cvBVdnbX8oBAeYzcUnoSeVilHKzS1pUdvivZXKsONwdWFU2KxZDwpmJKskp5Xl78QSxjNuc9_MzbcJYec5KKjJSTG8XiRrkXUJ6vGRdrhosjKQdB2ubpB2m90uEPUbtIq7RiVT5ITLGbZE7r5S6A0GmVa05kDqSTe7L_fwMf_kn_bSAZWcQaisM2xa5OI7KMlOuUA8WxwtrBsHAiqqZV2Ehsso04lkch9u9LJuAoCAQcwU-MYFeZ7xQIC6wCE3oUMm4eKKh_68X6MKSjhGZgQ8FCCAQHNYPUI4MJEhy67t4cGn6K9J9znBaZFYxmBdxf7pWjwBjSSOfSydpVx9n0KNg-ldFIia-Eeq5696wNRpuDCor6q6hLyxhkiFwz2rW35hwzOVP0RnKtp9L8FJr0e97m4w4D_7cT5WjFmsxKRKA0XdaGNRCPZrkF_d4BAzlKFMj_5HqJNQRuAODiBbA6rf6eRvvnxNOEXlRFvHdXtj-D2MuMDbqiuOSiVyTx85Y18dtloKfvw9Y6COXzJ_HkTQxFddXXd9pjQ0uJNxW5v2p2t9K4n939bRN_buvM2zZSl43GpXcKOgb7g9eN5bU8A4Ovpvpjm96TUC0SdI5rkhQfAmsNAKBlX4aRjtDz1bw3JfzxEs-rlyC587XbvrHI-6k20ZK7S3cmcCP4-qCX330gf90ocs9fRD31H4bl95O2t-_QkyAks7u5mNRDFgc4q7W2eVnj8cVudd_ZyuxedvOIKkdd3nLTWn26qmeFZqeKaRbKUer7oxdoU54lAAHDeNeDGd38aisaK94dV3k3akrnd8ohu9gfK_pHeu4hk-F_d9Lf2fB_ww==python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped_expired.json0000664000175000017500000000573113644407055027426 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2010-06-02T14:47:34Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "user_name1" } } } python-keystoneclient-4.0.0/examples/pki/cms/revocation_list.pem0000664000175000017500000000225013644407055025215 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIDTwYJKoZIhvcNAQcCoIIDQDCCAzwCAQExCTAHBgUrDgMCGjCCAVwGCSqGSIb3 DQEHAaCCAU0EggFJeyJyZXZva2VkIjogW3siZXhwaXJlcyI6ICIyMTEyLTA4LTE0 VDE3OjU4OjQ4WiIsICJpZCI6ICJkYjk4ZWQyYWY2YzY3MDdiZWM2ZGM2YzY4OTI3 ODlhMCJ9LCB7ImV4cGlyZXMiOiAiMjExMi0wOC0xNFQxNzo1ODo0OFoiLCAiaWQi OiAiMTVjZTA1ZmQ0OTFiNzk3OTEwNjhlZDgwYTljN2Y1ZTcifSwgeyJleHBpcmVz IjogIjIxMTItMDgtMTRUMTc6NTg6NDhaIiwgImlkIjogImRiOThlZDJhZjZjNjcw N2JlYzZkYzZjNjg5Mjc4OWEwIn0sIHsiZXhwaXJlcyI6ICIyMTEyLTA4LTE0VDE3 OjU4OjQ4WiIsICJpZCI6ICIxNWNlMDVmZDQ5MWI3OTc5MTA2OGVkODBhOWM3ZjVl NyJ9XX0xggHKMIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMx CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5T dGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25l QG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIa MA0GCSqGSIb3DQEBAQUABIIBAGn4kryxJudTZYMf32gKnoNHeAXRb97CoCXiTgs2 gu/blX/fwMdrL8GLg2puYR07XBgjo56vMsD94ZIRyhcS1lFti9veQHt7Xp8kbR8l nbx9fsOhMxUHLRnxioieA9T1ykP8ZvYV3hYCeXkIYhPgD4lAAAmNq99ZxBRS3csE DP+Xz1+UYvT6Qm/NWRuj7WIjofneIB7gT6L5irsU0qtMCQeqI3dsP9GSsy4HJvBR BBIzQ7fEMRCGTADbk4ml+6Dx+Jm5SO80NvinzxCjO3DbkcEG1pQ3RGVEn3gyzg2a ssaRU4ycbYACA99K5UzCtSj8glGXFa1cnx42nSn2LbfJP1M= -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped.pem0000664000175000017500000001155613644407055025520 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIONwYJKoZIhvcNAQcCoIIOKDCCDiQCAQExCTAHBgUrDgMCGjCCDEQGCSqGSIb3 DQEHAaCCDDUEggwxew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMzgtMDEtMThUMjE6MTQ6MDda IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5 MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6 Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6 ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2 MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7 DQogICAgICAgICAgICAidXNlcm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAg ICAgICAicm9sZXNfbGlua3MiOiBbDQogICAgICAgICAgICAgICAgInJvbGUxIiwN CiAgICAgICAgICAgICAgICAicm9sZTIiDQogICAgICAgICAgICBdLA0KICAgICAg ICAgICAgImlkIjogInVzZXJfaWQxIiwNCiAgICAgICAgICAgICJyb2xlcyI6IFsN CiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICJpZCI6ICJm MDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAg ICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAgICAgICAgIH0sDQogICAg ICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQiOiAiZjAzZmRh OGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAgICAgICAgICAg ICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAg ICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAidXNlcl9uYW1lMSINCiAgICAgICAg fQ0KICAgIH0NCn0NCjGCAcowggHGAgEBMIGkMIGeMQowCAYDVQQFEwE1MQswCQYD VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTESMBAGA1UE ChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3DQEJARYW a2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2VsZiBTaWduZWQCAREw BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEggEAxyHPqb53KXaWJH1IE6IFp3zzm5vl zlotcMxMepMRIxQPUDwJrP2ZJwXemQXVTpRa3Aer7hSkCRlyI++mcj/rD4h5Ygb0 q9sscjfeZB11Y436E4ZhXCdTfrtmKyBlHMqyhTBz64zroN0P+DVH7OLZDX/gqN2U KTX99HTN+LvUa8VqQYIzsjNv80CU6pog/YOCGPixjMKE9m9xYUr9huKZUxliHtX2 AHoCfQPhI8nsnNHLzCx6u5xIM7A69ZIDPQ82hSHC58k+g0bq9uflRCixBSD7ulR7 7ZRJM8IgOgFGpNeuyKcHJsCdPpZS8p1MmDCkwTOt5Kvf7Nopz+Cc325uOA== -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_token_unscoped.pem0000664000175000017500000000334513644407055026060 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIE9gYJKoZIhvcNAQcCoIIE5zCCBOMCAQExCTAHBgUrDgMCGjCCAwMGCSqGSIb3 DQEHAaCCAvQEggLwew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIxMTItMDgtMTdUMTU6MzU6MzRa IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow N1oiLA0KICAgICAgICAgICAgImlkIjogIjAxZTAzMmM5OTZlZjQ0MDZiMTQ0MzM1 OTE1YTQxZTc5Ig0KICAgICAgICB9LA0KICAgICAgICAic2VydmljZUNhdGFsb2ci OiB7fSwNCiAgICAgICAgInVzZXIiOiB7DQogICAgICAgICAgICAidXNlcm5hbWUi OiAidXNlcl9uYW1lMSIsDQogICAgICAgICAgICAicm9sZXNfbGlua3MiOiBbXSwN CiAgICAgICAgICAgICJpZCI6ICJjOWM4OWUzYmUzZWU0NTNmYmYwMGM3OTY2ZjZk M2ZiZCIsDQogICAgICAgICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMzU5ZGE0MmQzMWMwNDQzN2Ez MjgxMmFlYjc5ZTljMGIiLA0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJy b2xlMSINCiAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAg ICAgICAgICAgICAgICAgICAgImlkIjogIjU4MWFmMTk3MjZmYTRhZjViZGE3NDU3 ODlhYjJiZjJiIiwNCiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIi DQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAg ICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHK MIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT AkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8G A1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFj ay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3 DQEBAQUABIIBAMmrhRIUjSd+SLUAYn+18MDB8MXiaiF+FJQbu86IFW3OpL86ksvg CTP44Rvu1F4vvoZAQ60/tOfFVNTnBgnMv0NEfl4huiFqYrXjCphnNFQ5OYnmU6LR bFV+dvjZXWUn0wJDroUUEjbgyy/mqUnULzQgUzyK7Ho8T0dWahQc7EFMNVjoeKfa K7DeRe9trNNHM8anKVaeKhpWIfzbxiwIwypukce6wVGfdhaP+58jeFnGwHUIsY8V 8rzWj9UN46ko61piMAZljcktbrpqw2fDJ1H9Xl23G83rnXY7uVLQWUe7fRcUFtQt gQvKsGkN2hqlOgMT/FxFM3HC8kcl3wmzrNA= -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_scoped.json0000664000175000017500000001212413644407055026310 0ustar zuulzuul00000000000000{ "token": { "methods": [ "password" ], "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "issued_at": "2002-01-18T21:14:07Z", "expires_at": "2038-01-18T21:14:07Z", "audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"], "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": true, "description": null, "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "id": "3b5e554bcf114f2483e8a1be7a0506d1", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "54abd2dc463c4ba4a72915498f8ecad1", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "70a7efa4b1b941968357cc43ae1419ee", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "5707c3fc0a294703a3c638e9cf6a6c3a", "type": "volume", "name": "volume" }, { "endpoints": [ { "id": "92217a3b95394492859bc49fd474382f", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "f20563bdf66f4efa8a1f11d99b672be1", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "375f9ba459a447738fb60fe5fc26e9aa", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "id": "15c21aae6b274a8da52e0a068e908aac", "type": "image", "name": "glance" }, { "endpoints": [ { "id": "edbd9f50f66746ae9ed11dc3b1ae35da", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "9e03c46c80a34a159cb39f5cb0498b92", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "1df0b44d92634d59bd0e0d60cf7ce432", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "2f404fdb89154c589efbc10726b029ec", "type": "compute", "name": "nova" }, { "endpoints": [ { "id": "a4501e141a4b4e14bf282e7bffd81dc5", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "3d17e3227bfc4483b58de5eaa584e360", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "8cd4b957090f4ca5842a22e9a74099cd", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "id": "c5d926d566424e4fba4f80c37916cde5", "type": "identity", "name": "keystone" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped.pkiz0000664000175000017500000000375513644407055025716 0ustar zuulzuul00000000000000PKIZ_eJylVkmTmzgUvutXzN2VCotxm0MObMbQSG52ixtLG5DBdrttC_j1I3An6XRSk8wMVS6jJ_H0vfe97dMn9qiGaaG_NOiPi08AWpa1KbH9eEys6pYjxc21I5M9Dhp7ck1xjU4LlLVahme9hJpJNE3d56bmv5i-lYlAd421kjIhKY2yxNxzb1dYQE0uwnpTqw_WwbulQnS1yLFke6dcRHwSezu8ddm-UgNIFAprjkKf6zYrt4fBsUP6kSL-WDuaUifbiqZbu8l7a2FpVg91OHcCpXMCYx7pVgc2xOA2RBHj2nq1NPuUaONBm2bmiiRxdctMr8nve1wSS1V2cO_I2uiKY_sVJPEk4PJD1Iw3pvEdWmGOByRuKzR76E8K2JpvRlOYWU3Wrq7FSr6CUfh2YJ9sEcnbhhZmc8tqhsSU-Mzs5J1P2UfML4fkhIVIx1uvykz5MCoDsfhaM2jMr_IpO3jDKBxlOPYuaSxF4Z5OiNK1x-X68eYMRo_6OXWIcmX-mgM05IIj4s4ZMIdJ0kIhqbEAeTi4A4rDOQ4wTVrUbvSmxoTtBEVl1SMiu0mE5gYmqJrdJ3FxygTpKWvD-u4LiUu2o93dP6IAI4z_jkLlAW67E-YjP7jTdyzWHt3UyxsMLHGyU5t3G1KKaMC3ghg3RLwatXhIWpvgIRwA0iGfBFWFiNpiAc83sV0jgjskGPUu4kZ2GGUezYTmWqzRLjOba3qP0mzL2AGMUyk3wzv3rfxajFyP8FoWNPEH-YEpXP_IGviXtEmQ7PvRX1-ZACMV_4cJ8GvNKv9nzt33YBNYo3f_yGHv_ZXGfJUIYQ1GqCwxLok_3XRgWXjFbGOMf5b_7xTeFY31IjH5U9bc0YF_5t4d0V2hvxSQaQkJYRHQIoICyMEhajamIQBoJiQhpYRbS0DEEPE9M98cOp_g5m10SGP5GgjSG8UMkRn1DPkriCIbTjneVlyxVhZOv-wgyUcUjDpjsdFZEdNkAfrzX4Y6-F2s_44N8G_s_dlcWwYTPay-JWv1NgZOzsuv7P88FdHVpRhZKtYNfWOJZAJPi633LdzB13jPWlkYNTravWB-U3hXxGSrfRY3148-Ax-dBlmKoiBn5lhM9jMj4QdGwHtKfsfIL5RTULDansbod1nIQ12hLFd6tv4h7MGfxT3rAwfvVKz39YfQP4Nk2wyFKV8T5sD7h9GQbK23vji-v28o03o3KQiMSRnIWbVhDeUHBKxQsJYWfi0a43tvNdz71sfnQtSPXQu8daU-E7rmO2XN_u5MTFnY7nFQtSyQBkhcCRO7QgOrn2TVwiAXAA4KVkRh97EOTsgYzLe0_npzC3XUJqYxT0hVwcHiwCa2ehzkLBesLmHhiVoWoqxg_9xQ30w58MV7R4Lv9ky3d-yAvAtMTcmPtCzXplIaKrTMPfs9Q_dINQXrkeuuDGrw0H2lQHMngWlQOwoHw4HK3lT40NBUqLmc0RlEcdUSRaqSB1qE-KyVpIIFHTPPh6pigulwBe1AVJusQRyO0Rl6BtXppNgxaOV8ozowGqjBb_MRG49soHg4ZjOQlIveLWsjJRsVHe6KnFbuk8EIoWpNqJQOOqEQvSa1GqRxcWXDicYUGFSlePVI57pSHanuvp_YDFV1FTZ8GcrR8fnhBZ6Ka4w_z1waumEnBQICw9PlSQwpmuOXQEtDY1bOTjj9LPl8uh4cdbkwrm2OWkvkdNecc8q88Qq03ivo7FKXPokgTtSDB88PHJnrPsGz2usVbDw-n8O63D4f3dNgPKk7a_biR4EmIrWSU4srF7vhtnDD54cdmMmZX1mv-7amy94ZxKBOLw9pcsj4gX19SR_oSrDzM5JO9SyfyVJcbZ6e02A2S-OLTC4FkJf-jddP9bBYPl1m5Voqs3WnWhDXnVq-SMre4j9_3qsCryp28xwfnzDPZaQ4s5GkWyzkGNyiy9Z9sG_4Obsttd3jUhXFonIWc_9Y53m3ibLSDg2P1fIjuZ1Z3WnDTVPBLjy2p8H98gVME7OB9O_T89-mTsWepython-keystoneclient-4.0.0/examples/pki/cms/auth_token_unscoped.json0000664000175000017500000000132613644407055026245 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2112-08-17T15:35:34Z", "issued_at": "2002-01-18T21:14:07Z", "id": "01e032c996ef4406b144335915a41e79" }, "serviceCatalog": {}, "user": { "username": "user_name1", "roles_links": [], "id": "c9c89e3be3ee453fbf00c7966f6d3fbd", "roles": [ { "id": "359da42d31c04437a32812aeb79e9c0b", "name": "role1" }, { "id": "581af19726fa4af5bda745789ab2bf2b", "name": "role2" } ], "name": "user_name1" } } } python-keystoneclient-4.0.0/examples/pki/cms/revocation_list.der0000664000175000017500000000000013644407055025175 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/examples/pki/cms/auth_token_revoked.json0000664000175000017500000000575713644407055026100 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "revoked_username1", "roles_links": [ "role1", "role2" ], "id": "revoked_user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "revoked_username1" } } } python-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_scoped.pkiz0000664000175000017500000000550513644407055026321 0ustar zuulzuul00000000000000PKIZ_eJylWEl34rwS3etXvH2fPu0BElh6wthBcmw8IO2wnWDLNiRh8PDrXwlId4bu70u_lyxycKBUt-reWyW-f4cf3bId8h8DL8WL7wg7Ds5b6t7tmFOcMqL5mbGDZ2vTMEzbNzTf6oxQm-ub6MXcYMPmhmHsfNtYPttLJ1WR6VtzbQ0Pt5G12Tx1D70rpcqhTkvnxpnvyzSJpbU9rbIeXs_2ZWbPhkzNT1njl6tlu0HO1j2ldjw4fLdJ1H25TvzSK7WScW1gTVB4Nh3REPfErEvcWCq2WYkT2pGBFURxFIQHq1wYWpk2swNbwimG26dKV-OlO10Y-q3T1JUI7jS0xQqraFg0nm0NmPtjyt0CkUFvKJ81OMwUGuYl4bhzyhY-MC7SJDpnkzXTPQud8jGW9nBA_TDXn7ImHlbKTELn6Nxp8bA5YNM64LCIMLducOjfYDNfn4NtdcjqqaaqgCeyCk5pMnsSdUKiUD9x29MDTerjSqkrvHTEaUeayPUFwvVD9fT87AJRKxFLxgVtupoZoupBnyeR-ODT-bXhSuL_6TZ4hEM-Qcvt-IhoMpZWyvnh9Q1BnSmkX690aZ1Mj-L0dBvv0_kZP6eroEjt6fa1ayKDKrOnT3DKm1aOJbZyG5qQa_qzKgVol3rEfXrJbpfPgxZ55eSEQ0ddcO2IjVHn8Y1KBnrKuXUiPChJQ4EPcPIQDcTEMguLgnDonEJHXuKWiHAghXLhArRm-5o2EKxmSn1Kq-mRXQp6rYszUB7XJIwk2pAG4dCSGHckzyQ1EKSjTaTSUJOxyao3ZDpCwXrWzPiVbAJynUFBEeAR0eWsac-VXc8DKTN3p8Vg9aQftWdo4W5EhkxZqLRbDFSinDXAypIqWAYq-wNJIuA7bRmk5AHnKYd_hXlxKdoVSnmhOUvyp1QZ36dNdIaNXklEwgD44PfMxhLh8Gu7BbFBPLzqSOiPhahYQgpmWuUjqBBUe4aBsoYVVLlyfh6XqV3z37XrT91CX23Xn7qF_qFdoH1LZQno3nY6yisJh5XiQXCiAEQT4EAHoY11zaBdwl2cbTDO7CvPQcK5ENKZ3ldP4JEKCuXQAQ7Bey_0VYQhElbgdyhqLyHQB0uhAyk-Cec14BY0AQp-lQD6XzXwWlT0q6oVpOQIDfwNrccIc0dUF_hdyXioJGJCIDMa0wZLXsIqYmYSuFXPyt_TGr3l9RdpPQZy9YLWAhr6N6r-snmnJSEdaBM0BLRA7LgBhy6Q8HicAFUTRyGDW0Jv31H135iK_kzVrxUVfTQLMsQNULcgopChL4GMBw-mkaA1CyHtwVFYWBf0Sj70ln3rRC6Y8h47DmOO-aygSaRQ7qig0BGzLRk3UQvGoyDPjsbYLOAN-OOI26b27CjwX2tSp03Qpgq0cY7FgElFndDHQtEkOKyT0TlYvnL3F0YWUj7Xbhb9pMM8EzWCllo3npmpiBhTBS9Hn6zqq06F_rX_SVAys25IqEP_qUpMBjIGBZtWB-41IJjM8AAMxP5z_68ig5nYfoKG_oTtq9DQ37rwKzQviWDhwBIiDR6BuwzQX3DfmlOOx4zH8FeTvLAoPbFY_AO10d-5sC-ofcTLiQI-CcYGEGC-cRJi0HwsRpsCSxRnfAN-AOLilkovVL9CGV1XnosuQmVco_emOasY10tiBhWkDj4ZVAAL2qjX2PYhOHimQmqw_d7Zyvl5MuXzur1Sl6eK3Oar4IMTuw0xNQlWvtIzxZQKIOPNGOy-pIPVwpbmgEi03kti_tGJ02aq0J8TOj6yuX4SnLgsnYezvaEPY7tgtiy2r69Y2wC1kxHpgTD950JFbwr1DlJjSSTUOjGVcAKTifKgxmEgPG-ExXLRsIaZG8Fz-XWIfJ4XF2cG6e_R1zggKByp4MTDR7YKT1z-ia5Y8WFmRLAXOyIzGKj-CCuO7NlBTZK4oGE0QsSGcc61v3Lit5mi96mCQEwN6Io_O3H9-_EGEJwRVXxJjDYyaGNsg3wVS_ZMv6eh0wsHBg20HgiNGZ_XANiRghrSfsrn1Tv8dIjPdGZmBJrwe4AKbBR7EwX1YrB_YGVDKsTC6KMbv7BVPeS2SPX1xHhgK-fTqi9ajP6fVV8ciq6nypkS9--39ivzzqe7l3V_e97YizwByLPpE4P5gMSAcGrGH2ZRv6zrLjaL-4eGxfGW9esqduPZdTZG4zi26juoV_RQTbpFXMTrIQ5RPK_LvHfP2l6vyJAncSXuQj-vQqbz-6vQVp5i6uioh4ukllFxwWw3a7_dsFFncM3RNyTWtSjUwqgzBs29vIZpWMch9vet4VMz9n0HWa1r-qG1xLpma3Jk6R12IzU-pttaoQlc_wKntbTzm--str7P4JoTqbAWK_vOCrV7dIm8Dw3rUD-sCNxax1DlqHXeXYcrHcbPr_ZG-kkEyiAQgkjHVHW3OPBba3M-ybTaQ8iSrnFm5IlBQAaIrHf3Z43om-q5qEobTVtJB_wzTVtCHfQyJe6ErBKVzTaj52ktJzfLb5v18ZmC1e32cXCI5qRZzslkMg823voBTYYq2m8GfXVblmo0dM3LjN3xqVkYCzr6sV1KyTqii1l6WDem8cKauebfL3da5hH1YX0kB3S3s8JH95Yrw_PIcQ-yjZenh4leSlL5GLTsx2EXLshhkdaPVjtbG_2tGo-Ml930B6tGP4y9h0aBP1TLYtZNb8udfW9NW0t2T1M6dLvxMdzcDMroZlOOlIexdFw6M4Ybgp-rKB-k7Y2fHb4hecCPk-Tbg_zUV3f7LNaH2_HtbLr99qwVnTxedF1u3DkvvgwjZpJr_SLQ_Uh6Zg-LZnFaqgnaNo8_3Nq_XZh-86yufGsepL68H-fDj6bFE6dcNhN0_rLDIuavLz7-Cz0ItdI=python-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_revoked.pem0000664000175000017500000001733313644407055026311 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIIWqQYJKoZIhvcNAQcCoIIWmjCCFpYCAQExCTAHBgUrDgMCGjCCFLYGCSqGSIb3 DQEHAaCCFKcEghSjew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgImNhdGFsb2ci OiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6 IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg ICAgImlkIjogIjNiNWU1NTRiY2YxMTRmMjQ4M2U4YTFiZTdhMDUwNmQxIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4iLA0KICAg ICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3 NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAg ICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAg ICAgICAgICAgICAgICAgICJpZCI6ICI1NGFiZDJkYzQ2M2M0YmE0YTcyOTE1NDk4 ZjhlY2FkMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjog ImludGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0 cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn aW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAg ICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiNzBhN2VmYTRi MWI5NDE5NjgzNTdjYzQzYWUxNDE5ZWUiLA0KICAgICAgICAgICAgICAgICAgICAg ICAgImludGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAg ICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUz NDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAg InJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAg ICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICI1NzA3YzNm YzBhMjk0NzAzYTNjNjM4ZTljZjZhNmMzYSIsDQogICAgICAgICAgICAgICAgInR5 cGUiOiAidm9sdW1lIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ2b2x1bWUi DQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAg ICAgICAgICAgICAgICAgICJpZCI6ICI5MjIxN2EzYjk1Mzk0NDkyODU5YmM0OWZk NDc0MzgyZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjog ImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDov LzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJy ZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAg ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi OiAiZjIwNTYzYmRmNjZmNGVmYThhMWYxMWQ5OWI2NzJiZTEiLA0KICAgICAgICAg ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAg ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92 MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9u ZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjM3NWY5YmE0NTlhNDQ3 NzM4ZmI2MGZlNWZjMjZlOWFhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJp bnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1 cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAg ICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQi OiAiMTVjMjFhYWU2YjI3NGE4ZGE1MmUwYTA2OGU5MDhhYWMiLA0KICAgICAgICAg ICAgICAgICJ0eXBlIjogImltYWdlIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6 ICJnbGFuY2UiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICJlZGJkOWY1MGY2Njc0NmFl OWVkMTFkYzNiMWFlMzVkYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwi OiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThh NjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lv biI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAg ICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICI5 ZTAzYzQ2YzgwYTM0YTE1OWNiMzlmNWNiMDQ5OGI5MiIsDQogICAgICAgICAgICAg ICAgICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAg ICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEv NjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAg ICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAg ICAgICAgICAgICJpZCI6ICIxZGYwYjQ0ZDkyNjM0ZDU5YmQwZTBkNjBjZjdjZTQz MiIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1Ymxp YyIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcu MC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdh IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25l Ig0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAg ICAgICAgICAgICAgICAiaWQiOiAiMmY0MDRmZGI4OTE1NGM1ODllZmJjMTA3MjZi MDI5ZWMiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjogImNvbXB1dGUiLA0KICAg ICAgICAgICAgICAgICJuYW1lIjogIm5vdmEiDQogICAgICAgICAgICB9LA0KICAg ICAgICAgICAgew0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAg ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6 ICJhNDUwMWUxNDFhNGI0ZTE0YmYyODJlN2JmZmQ4MWRjNSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAg ICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTozNTM1Ny92MyIs DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIN CiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0K ICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjNkMTdlMzIyN2JmYzQ0ODNi NThkZTVlYWE1ODRlMzYwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRl cmZhY2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVy bCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAgICAgICAgICAg ICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAgICAgICAgICAg ICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAg ICAgICAgICAiaWQiOiAiOGNkNGI5NTcwOTBmNGNhNTg0MmEyMmU5YTc0MDk5Y2Qi LA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJwdWJsaWMi LA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAu MC4xOjUwMDAvdjMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6 ICJSZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAg ICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICJjNWQ5MjZkNTY2NDI0ZTRm YmE0ZjgwYzM3OTE2Y2RlNSIsDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaWRl bnRpdHkiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImtleXN0b25lIg0KICAg ICAgICAgICAgfQ0KICAgICAgICBdLA0KICAgICAgICAiaXNzdWVkX2F0IjogIjIw MDItMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImV4cGlyZXNfYXQiOiAiMjAz OC0wMS0xOFQyMToxNDowN1oiLA0KICAgICAgICAicHJvamVjdCI6IHsNCiAgICAg ICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlv biI6IG51bGwsDQogICAgICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLA0K ICAgICAgICAgICAgImlkIjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRv bWFpbiI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQog ICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAg ICAgfQ0KICAgICAgICB9LA0KICAgICAgICAidXNlciI6IHsNCiAgICAgICAgICAg ICJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUxIiwNCiAgICAgICAgICAgICJpZCI6 ICJyZXZva2VkX3VzZXJfaWQxIiwNCiAgICAgICAgICAgICJkb21haW4iOiB7DQog ICAgICAgICAgICAgICAgImlkIjogImRvbWFpbl9pZDEiLA0KICAgICAgICAgICAg ICAgICJuYW1lIjogImRvbWFpbl9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAg ICAgfSwNCiAgICAgICAgInJvbGVzIjogWw0KICAgICAgICAgICAgew0KICAgICAg ICAgICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYz MSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTEiDQogICAgICAgICAg ICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJpZCI6ICJmMDNm ZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAgICAg Im5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAg ICAgICJtZXRob2RzIjogWw0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAg ICBdDQogICAgfQ0KfQ0KMYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJ BgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYD VQQKEwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkB FhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIB ETAHBgUrDgMCGjANBgkqhkiG9w0BAQEFAASCAQCy1xOK1+nQy8tL3fORdWkcp0Y5 88cNgl4sXmJOE1TOOEauMyVWE188gtxHelVDCFWr8kICALvAnPX0UbIhoEaxscey mvcUazUMP2WWSsBMgSXBfbl6amTZp5KMgpMmAuGjP1xok3yvOecEF6Szh8yE3Q5O sNEKsMI5UiJTDU7WWSUp1Zs7E4UvFjAepZGhIQWOCxSvEnrl3Mfw1f7HWKDBlijR 4XnPqJPiTmYLjzyDmi31GOHWZM4nZxShHfidLblPV4AyA/gsCh27/cZxYW/Q+cyL wfQogs4g7XfNgLdDHlbvv7NCS06RhydhLeiqNUcCp4hnZZC16KDPWzJ2Ql5y -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_token_unscoped.pkiz0000664000175000017500000000234113644407055026247 0ustar zuulzuul00000000000000PKIZ_eJxdVNmWqjoQfecr7nuvs5pBW30MIWKiCSKT5A1QZqQdEOTrb7DPHVmLIVWVqr03Vfn1S1w6MjH7A1JnWvySKMZGa4dk23KcPxMG7AS2wlaVEILZDAIbDdAFGz3zbkZGoTnZo5kJnavp4FiTDBttQCSMfImyzIzPL5KHKqsTjRZWoS_w5fCMVL_DZZsJ33eiMYUHhzQ82sIPComWoKeF3FNHHqy1_aJuOzCj7ZnSFjsICn7M--hI6uSFvzDEwo9eOxfMdi7SfAMpklVSRdxyUOA7huSbw3dgTwOvpyMpLbdSeRDKzABqWCLxpiNzq4EFSBYxmmQ5ZDVVSlT_dWrqknssP5nre6wmbwqp02f44o_8iH9Tmr5JFwZKPdGSfhvSuFk_uIvesJNmdedHlsZm3UU_WsTHKVFTV9Mm3NB5OGZz7rJCEo-au7ZCVV5woUc4JnNW8oY19sgbUuFiQkCesemP0-ZAuxdR8CMgHb25xE3BRQTTgPbMsEemopGW2UCbdR2WiahSl9TEb2RvlM6kEXnF6lBTQV_aQcHrL2ilN6PBuqFupVGBInQPOS_9QhTRmOFpllHnYUkEUlK8kTXzXIoD7w3nzdvFRerL09_4W6T_a5QelRUNsf6aGioJoSQ6rc8iu8_4bIAlwHrGfB14LnC9AY6A_KxDF9S-S-17D-3Q8G0bo54YtoscierABIqH9IEST_O7-FKrYSD4HXCPwDt4i_p6n5h-52kH0aX3Ablg_5P47koQPerzkcmxOheieD3u_z0Xlb7O-Y0f6_Fkrjru6c8pUfKTqIs1cpHowe5R9q5koP7h8mBo8Jp9c5GQC0boP4MEmJ5V17wqzFUv64L-WgLAEROnxy40lpf0Fg28fjkQr5bP1g0-Qt-trp_4RXab1bDZlQvFPkffLGmr62wLSLTYS9b2MhKrIzeowvOwgP35VOcfVbq7nLEefJzqb1nfpgf1dh0sdWM-QmW3WJwrvzBhh9bFl0R2kU3U3eqYwodf6ddV2OfzzAwWt0fqJI62dwS7fUn0wd2q22tM5LzxcyqjjMNZt46kpJtbY5PjW218jXuv1s3ncAZhvFAeymKFu2I8xOuxSZf-vH76-_IKeLy0WKvq7Hgbv6A0dJ-seV45vZufmqwrsZ68hlxvo2ZBFrkV55blvzbp_nOZmZes3LycHQkPVlzKz9N2OXxJS00ui6s_aPNNIDjeWsvY-vuP9mOuIel96iFm_HMC_gm2VKmFpython-keystoneclient-4.0.0/examples/pki/cms/revocation_list.json0000664000175000017500000000051113644407055025403 0ustar zuulzuul00000000000000{"revoked": [{"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}, {"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}]}python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped_expired.pem0000664000175000017500000001130413644407055027227 0ustar zuulzuul00000000000000-----BEGIN CMS----- MIINuQYJKoZIhvcNAQcCoIINqjCCDaYCAQExCTAHBgUrDgMCGjCCC8YGCSqGSIb3 DQEHAaCCC7cEgguzew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6 IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMTAtMDYtMDJUMTQ6NDc6MzRa IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3 LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50 ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5 MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6 Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6 ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2 MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7 DQogICAgICAgICAgICAidXNlcm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAg ICAgICAicm9sZXNfbGlua3MiOiBbDQogICAgICAgICAgICAgICAgInJvbGUxIiwN CiAgICAgICAgICAgICAgICAicm9sZTIiDQogICAgICAgICAgICBdLA0KICAgICAg ICAgICAgImlkIjogInVzZXJfaWQxIiwNCiAgICAgICAgICAgICJyb2xlcyI6IFsN CiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICJuYW1lIjog InJvbGUxIg0KICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgew0K ICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAg ICAgICB9DQogICAgICAgICAgICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAidXNl cl9uYW1lMSINCiAgICAgICAgfQ0KICAgIH0NCn0NCjGCAcowggHGAgEBMIGkMIGe MQowCAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcT CVN1bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9u ZTElMCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UE AxMLU2VsZiBTaWduZWQCAREwBwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEggEAw7K9 7FaxXE6QNbsWmTAo/mtppDB2hv2DCwxMnjaZuOlV3g7UGnF8mxHjWd2Pcj1r0oGb 0iACE9qmoZVHTPWU6WWBClAIF/bcs6Y+5S10bCu1uRVrzUCsLEbbJOLxBZG1qiEZ opLn6pBIOY8ovxcoKKmI56JgsqVGclZM5yH9Z9E5hSZgMREJZFZcVHA3pTJeTjc2 9Mpb3RS5Q/FXf2nP09YA4Mp9+J15gFH/YuhBQiyo+LqvHtg+DdWdxcM3keAaTuxw Z8Cd26T+cTv1iS5qXcykd8OP7V0eIF7i39wshXGm6B9XpwFEYiLTZy7398O/yeGd izImJNpCowBA0Pyr8w== -----END CMS----- python-keystoneclient-4.0.0/examples/pki/cms/auth_v3_token_revoked.pkiz0000664000175000017500000000560113644407055026500 0ustar zuulzuul00000000000000PKIZ_eJydWEt3ozgT3etXfPucPgPCpOPFLHgZ47FEwDws7QxODELYTvzA8OunsJ3uPDrT6a9z-uSE4FLdqntvlfLtG_wzHdej_7PIrP_hGyKeR4qGTf7ZcK845tQIcmsDzx5sy7LHgWUEzsmKjLG5ip_tFbFcYVnWNnCt2ZM78zIN2YEzNhbwcBM7q9WT-dBOlAzvZVZ6t954V2ZpoizcYZW38PNoV-buqMu15TGvg3I-a1bIW0-OmZt0ntisUm1XLtKg9Euj5MLoeB0WvssGLCIttWVJakcjLi9Jyk604wXFHkakc8qpZZRZPdrzGZxiTdoMnySZTYZTy_zu1bLqg3s1awjmFYuK2nedjohAZ2JSINqZNROjmkQ5ZtGypIKcvLKBD-hFlsbnbPJ6uOORVz4myg4OkA9jc5vXSTfHIwWdowuvId1qT2xnT6IiJsK5JVFwS-zl4hxsbUJWW8m0Ht6rrNahRJD6YTkabrl9gcLd4Z6l8tC_AAXdcusMq8qwWixS_RFq9CZDdC7Y9UNzfH548taXVCF4CQU-n7YcT1Q-6z8YyhzTdjE3lUU6PJwhZOtkl1lvcS_d5MBSXXkXVLB5WGTucP3SNcRTvcrd4TZbh69aqSt8PqlZSuWlA6Mq62Gd65G02QXWZjkOG4BwdySRp02FcSDW4OSLlUY7dlwK50hFWNKaAR_g5C7uqE1UHhUFFdA5zAZ-OikRFUAKfCkgtGbd47pUeCI5lsesGh6AH33614J6HROJpFGssJrWiESOwoWn-DaVQJATq2ONRYZKbF69ItMBatLyeiSuZOshyxxqhgBPH13N6-ZcvMU4VHJ7c5x2TkvbQXOGFm0GtMvxVGOnaccUJngNrCwZJipQOehoGgPfWcMhJR84zwT8KloWl6JdoZQXmvN0uc2wfp_V8Rk2ehEPjcKC1UHLXaJQAV_upKAuiEdUJxoFei8qntKiJ9wj8KEnWQ8D5TUvGL5yfpwAcaT4Vbs-6xb6ars-6xb6j3aB9h2Np6B71zsxUSkkqrAPwSkGiDbAgQ5CG6Xk0K7eXUBdeu5eqQwSXqaqvAjnqj4Ra6BQAR0QELz1o0BDBCIRDF9dIf2UQh8czDpavPeEHwF7TYDVvUgA_b8aeCkq-lnVClLyeg38Ca11RITXVxf4XamkqxRqQyA71llNFD_lFbVzBdyq5eWvaY1e8_qLtNaBXG1P6x4a-h1Vf9q819CIdawOawpaoG5Sg0MXqPd4kgJVUw_TblJCb99Q9XdMRZ9T9WtFRe_NgnZJDdQtaF_IKFBAxp0P06inNY8g7c7DPJIFu5IPvWbfIlULjt9iJ1EiiBgVLI0xE54GCh1w11FJHTdgPBj5bqwTu4AXyPsRt87c0aHHf60J2HzYZBjaOCb9gMn6OqH3hWJpuF-kg3Ow5XyyuzCyUJZj43ba3p2IyPsaQUudW9_ONUStISazwQer-qpTod_2Pw1LbsuaRib0n2nU5iBjULDtnMC9OgSTGR6Agbif9_8qMphUzQdo6DNsX4WG_tSFX6D5aQwLB1EQrckA3KWD_oL7SsEE0blI4Luh-FFR-v1i8R_URn_mwkFP7QOZ3WHwSTA2gADzTdCIgOaTfrRhWKIEFyvwAxCXcDR2ofoVyuC68lx0EWFdoremOaq4MEtqhxWkDj4ZVgAL2mhK4gYQHDwTUwm233prdXmeTMuxbK7UFbDGNMt5-M6JJzW1DQVWvtK3-ykVQsYrHey-ZJ3TwJbmgUiM1k8T8d6Js3qI2Y8JnRz42Dz2nLgsnfuzvaF3Y7vgrrqFFn7F2jqonYpoC4RpPxYqflWoN5BqR6GRceqnEklhMjERShKFvecNSL9c1Lzm9qrnufoyRD7Oi4szg_R36Gsc6Ckca-DE3Xu29p44-4yuBAcwM2LYi70-MxiowYBgT_XdUNI0KVgUDxB1YZwL44-c-HWm6G2qIBDbALqSj04sfz3eAII3YDhQ-tFGO0MnLsgXO6pvBy2LvLZ3YNBA44PQuPVxDYAdKZSQ9nY5rt7gZ11ypjO3Y9BE0AJUYGO_NzFQLwH7B1bWtEI8it-78TOfy27p9qm-nJh0fO5dV_3wmKWj7cuV6MeW9nNjl7BgnjGChanXvl8_JIfnZ5cF9HIoernm8Dk_LnBSzbX-tMn1xddT68M757sDuq7xxTKFOvQXj-vQ8OT29kFu2lRueZ4Eg0jb1knCcV5vR7MkDC_0pjaC6WcHmCrJeHtPZipL0p0aq6HO1vn5Wge0hWteIvloWCwv87OFVrdT0MM0cgYosT3ov6P4wtBS2EIeI9cy8k2zWo1dY-WYxHMr-P9Agk1jGcxOgmDkNDAbg11jBcxG8MB1mkkSd86UGJVrqLFjmcQKFOfkCCMwVzQxjTyyEqpmta4vQUCgxBkxjfO7yCrIJNJMmUmqgNqeSeg0dnM-aeo0xfRHSyNHEov8uPLCjXdihCxFUFU916BNdWJkfaD1JdC0Hra8c2JieueTjBOZxjjZ8dKMFunywFO4V8NhyGzY6J9mYBvFprGD15dwxzQDA-7TjtGOxMIPR9FjE37XlON2OLSOB7sjD972Dj3vk-d2KBcPs-i21Uf3T1YNbT7l2DUf13g_cx-GOztaP5Uj9n3HlcmoCOybMtQmpSKD5BQhO1wbnuf8VQxq81BFdydp7pVVfCrNm25YBtSST48zfdt8V7ARb2Ot3DaMTjl_rr2tk_ylofomW_jOatpsRxv_xr9ZVVYs75RsIBUdVHqzte-8gzmYPVZW87zOHruTtDb5Kdb8h0X2qHkomd3WT8Pd6GCnj3KCT-U8NZXn440q0yM7TXLn4K6G08aLk0qtd_e3M3pj4XEi56UbYWMNV1823R3Nm6ySHdnj1nkw_MhV8NPqefKPqdLh-AFnnXW_N-82BSmq2VgeqvvWHAyCv_9G5z-CONT--QeRfwEmaLr4python-keystoneclient-4.0.0/examples/pki/cms/auth_token_scoped.json0000664000175000017500000000573113644407055025706 0ustar zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "user_name1" } } } python-keystoneclient-4.0.0/examples/pki/run_all.sh0000775000175000017500000000143513644407055022523 0ustar zuulzuul00000000000000#!/bin/bash -x # Copyright 2012 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. # This script generates the crypto necessary for the SSL tests. . gen_pki.sh check_openssl rm_old cleanup setup generate_ca ssl_cert_req cms_signing_cert_req issue_certs gen_sample_cms cleanup python-keystoneclient-4.0.0/examples/pki/gen_cmsz.py0000664000175000017500000001017313644407055022706 0ustar zuulzuul00000000000000#!/usr/bin/python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_serialization import jsonutils from keystoneclient.common import cms from keystoneclient import utils CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) def make_filename(*args): return os.path.join(CURRENT_DIR, *args) def generate_revocation_list(): REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked'] revoked_list = [] for token in REVOKED_TOKENS: with open(make_filename('cms', '%s.pkiz' % name), 'r') as f: token_data = f.read() id = utils.hash_signed_token(token_data.encode('utf-8')) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) with open(make_filename('cms', '%s.pem' % name), 'r') as f: pem_data = f.read() token_data = cms.cms_to_token(pem_data).encode('utf-8') id = utils.hash_signed_token(token_data) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) revoked_json = jsonutils.dumps({"revoked": revoked_list}) with open(make_filename('cms', 'revocation_list.json'), 'w') as f: f.write(revoked_json) encoded = cms.pkiz_sign(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f: f.write(encoded) encoded = cms.cms_sign_data(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pem'), 'w') as f: f.write(encoded) CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem') SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem') SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem') EXAMPLE_TOKENS = ['auth_token_revoked', 'auth_token_unscoped', 'auth_token_scoped', 'auth_token_scoped_expired', 'auth_v3_token_scoped', 'auth_v3_token_revoked'] # Helper script to generate the sample data for testing # the signed tokens using the existing JSON data for the # MII-prefixed tokens. Uses the keys and certificates # generated in gen_pki.sh. def generate_der_form(name): derfile = make_filename('cms', '%s.der' % name) with open(derfile, 'w') as f: derform = cms.cms_sign_data(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME, cms.PKIZ_CMS_FORM) f.write(derform) for name in EXAMPLE_TOKENS: json_file = make_filename('cms', name + '.json') pkiz_file = make_filename('cms', name + '.pkiz') with open(json_file, 'r') as f: string_data = f.read() # validate the JSON try: token_data = jsonutils.loads(string_data) except ValueError as v: raise SystemExit('%s while processing token data from %s: %s' % (v, json_file, string_data)) text = jsonutils.dumps(token_data).encode('utf-8') # Uncomment to record the token uncompressed, # useful for debugging # generate_der_form(name) encoded = cms.pkiz_sign(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) # verify before writing cms.pkiz_verify(encoded, SIGNING_CERT_FILE_NAME, CA_CERT_FILE_NAME) with open(pkiz_file, 'w') as f: f.write(encoded) generate_revocation_list() python-keystoneclient-4.0.0/tox.ini0000664000175000017500000000545513644407055017450 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 skipsdist = True envlist = py37,pep8,releasenotes ignore_basepython_conflict = True [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete stestr run --slowest {posargs} whitelist_externals = find basepython = python3 [testenv:pep8] commands = flake8 bandit -r keystoneclient -x tests -n5 [testenv:bandit] # NOTE(browne): This is required for the integration test job of the bandit # project. Please do not remove. commands = bandit -r keystoneclient -x tests -n5 [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON=coverage run --source keystoneclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:debug] commands = oslo_debug_helper -t keystoneclient/tests {posargs} [testenv:functional] setenv = {[testenv]setenv} OS_TEST_PATH=./keystoneclient/tests/functional passenv = OS_* [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) # W504 line break after binary operator # F601 dictionary key repeated with different value ignore = D100,D101,D102,D103,D104,D203,W504,F601 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [testenv:docs] commands = python setup.py build_sphinx deps = -r{toxinidir}/doc/requirements.txt [testenv:pdf-docs] envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} whitelist_externals = make rm commands = rm -rf doc/build/pdf sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html deps = -r{toxinidir}/doc/requirements.txt [hacking] import_exceptions = keystoneclient.i18n [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files. deps = bindep commands = bindep test [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-keystoneclient-4.0.0/keystoneclient/0000775000175000017500000000000013644407143021162 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v3/0000775000175000017500000000000013644407143021512 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v3/policies.py0000664000175000017500000000755113644407055023705 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Policy(base.Resource): """Represents an Identity policy. Attributes: * id: a uuid that identifies the policy * blob: a policy document (blob) * type: the MIME type of the policy blob """ def update(self, blob=None, type=None): kwargs = { 'blob': blob if blob is not None else self.blob, 'type': type if type is not None else self.type, } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval class PolicyManager(base.CrudManager): """Manager class for manipulating Identity policies.""" resource_class = Policy collection_key = 'policies' key = 'policy' def create(self, blob, type='application/json', **kwargs): """Create a policy. :param str blob: the policy document. :param str type: the MIME type of the policy blob. :param kwargs: any other attribute provided will be passed to the server. :returns: the created policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).create( blob=blob, type=type, **kwargs) def get(self, policy): """Retrieve a policy. :param policy: the policy to be retrieved from the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :returns: the specified policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).get( policy_id=base.getid(policy)) def list(self, **kwargs): """List policies. :param kwargs: allows filter criteria to be passed where supported by the server. :returns: a list of policies. :rtype: list of :class:`keystoneclient.v3.policies.Policy`. """ return super(PolicyManager, self).list(**kwargs) def update(self, policy, blob=None, type=None, **kwargs): """Update a policy. :param policy: the policy to be updated on the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :param str blob: the new policy document. :param str type: the new MIME type of the policy blob. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).update( policy_id=base.getid(policy), blob=blob, type=type, **kwargs) def delete(self, policy): """"Delete a policy. :param policy: the policy to be deleted on the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(PolicyManager, self).delete( policy_id=base.getid(policy)) python-keystoneclient-4.0.0/keystoneclient/v3/projects.py0000664000175000017500000003171413644407055023725 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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 six.moves.urllib as urllib from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class Project(base.Resource): """Represents an Identity project. Attributes: * id: a uuid that identifies the project * name: project name * description: project description * enabled: boolean to indicate if project is enabled * parent_id: a uuid representing this project's parent in hierarchy * parents: a list or a structured dict containing the parents of this project in the hierarchy * subtree: a list or a structured dict containing the subtree of this project in the hierarchy """ def update(self, name=None, description=None, enabled=None): kwargs = { 'name': name if name is not None else self.name, 'description': (description if description is not None else self.description), 'enabled': enabled if enabled is not None else self.enabled, } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval def add_tag(self, tag): self.manager.add_tag(self, tag) def update_tags(self, tags): return self.manager.update_tags(self, tags) def delete_tag(self, tag): self.manager.delete_tag(self, tag) def delete_all_tags(self): return self.manager.update_tags(self, []) def list_tags(self): return self.manager.list_tags(self) def check_tag(self, tag): return self.manager.check_tag(self, tag) class ProjectManager(base.CrudManager): """Manager class for manipulating Identity projects.""" resource_class = Project collection_key = 'projects' key = 'project' def create(self, name, domain, description=None, enabled=True, parent=None, **kwargs): """Create a project. :param str name: the name of the project. :param domain: the domain of the project. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: the description of the project. :param bool enabled: whether the project is enabled. :param parent: the parent of the project in the hierarchy. :type parent: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server. :returns: the created project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` """ # NOTE(rodrigods): the API must be backwards compatible, so if an # application was passing a 'parent_id' before as kwargs, the call # should not fail. If both 'parent' and 'parent_id' are provided, # 'parent' will be preferred. if parent: kwargs['parent_id'] = base.getid(parent) return super(ProjectManager, self).create( domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def list(self, domain=None, user=None, parent=None, **kwargs): """List projects. :param domain: the domain of the projects to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param user: filter in projects the specified user has role assignments on. :type user: str or :class:`keystoneclient.v3.users.User` :param parent: filter in projects the specified project is a parent for :type parent: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will filter projects on. Project tags filter keyword: ``tags``, ``tags_any``, ``not_tags``, and ``not_tags_any``. tag attribute type string. Pass in a comma separated string to filter with multiple tags. :returns: a list of projects. :rtype: list of :class:`keystoneclient.v3.projects.Project` """ base_url = '/users/%s' % base.getid(user) if user else None projects = super(ProjectManager, self).list( base_url=base_url, domain_id=base.getid(domain), parent_id=base.getid(parent), fallback_to_auth=True, **kwargs) base_response = None list_data = projects if self.client.include_metadata: base_response = projects list_data = projects.data base_response.data = list_data for p in list_data: p.tags = self._encode_tags(getattr(p, 'tags', [])) if self.client.include_metadata: base_response.data = list_data return base_response if self.client.include_metadata else list_data def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids, parents_as_list): if parents_as_ids and parents_as_list: msg = _('Specify either parents_as_ids or parents_as_list ' 'parameters, not both') raise exceptions.ValidationError(msg) def _check_not_subtree_as_ids_and_subtree_as_list(self, subtree_as_ids, subtree_as_list): if subtree_as_ids and subtree_as_list: msg = _('Specify either subtree_as_ids or subtree_as_list ' 'parameters, not both') raise exceptions.ValidationError(msg) def get(self, project, subtree_as_list=False, parents_as_list=False, subtree_as_ids=False, parents_as_ids=False): """Retrieve a project. :param project: the project to be retrieved from the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool subtree_as_list: retrieve projects below this project in the hierarchy as a flat list. It only includes the projects in which the current user has role assignments on. :param bool parents_as_list: retrieve projects above this project in the hierarchy as a flat list. It only includes the projects in which the current user has role assignments on. :param bool subtree_as_ids: retrieve the IDs from the projects below this project in the hierarchy as a structured dictionary. :param bool parents_as_ids: retrieve the IDs from the projects above this project in the hierarchy as a structured dictionary. :returns: the specified project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` :raises keystoneclient.exceptions.ValidationError: if subtree_as_list and subtree_as_ids or parents_as_list and parents_as_ids are included at the same time in the call. """ self._check_not_parents_as_ids_and_parents_as_list( parents_as_ids, parents_as_list) self._check_not_subtree_as_ids_and_subtree_as_list( subtree_as_ids, subtree_as_list) # According to the API spec, the query params are key only query_params = [] if subtree_as_list: query_params.append('subtree_as_list') if subtree_as_ids: query_params.append('subtree_as_ids') if parents_as_list: query_params.append('parents_as_list') if parents_as_ids: query_params.append('parents_as_ids') query = self.build_key_only_query(query_params) dict_args = {'project_id': base.getid(project)} url = self.build_url(dict_args_in_out=dict_args) p = self._get(url + query, self.key) p.tags = self._encode_tags(getattr(p, 'tags', [])) return p def find(self, **kwargs): p = super(ProjectManager, self).find(**kwargs) p.tags = self._encode_tags(getattr(p, 'tags', [])) return p def update(self, project, name=None, domain=None, description=None, enabled=None, **kwargs): """Update a project. :param project: the project to be updated on the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :param str name: the new name of the project. :param domain: the new domain of the project. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: the new description of the project. :param bool enabled: whether the project is enabled. :param kwargs: any other attribute provided will be passed to server. :returns: the updated project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` """ return super(ProjectManager, self).update( project_id=base.getid(project), domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def delete(self, project): """Delete a project. :param project: the project to be deleted on the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(ProjectManager, self).delete( project_id=base.getid(project)) def _encode_tags(self, tags): """Encode tags to non-unicode string in python2. :param tags: list of unicode tags :returns: List of strings """ return [str(t) for t in tags] def add_tag(self, project, tag): """Add a tag to a project. :param project: project to add a tag to. :param tag: str name of tag. """ url = "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag)) return self._put(url) def update_tags(self, project, tags): """Update tag list of a project. Replaces current tag list with list specified in tags parameter. :param project: project to update. :param tags: list of str tag names to add to the project :returns: list of tags """ url = "/projects/%s/tags" % base.getid(project) for tag in tags: tag = urllib.parse.quote(tag) resp, body = self.client.put(url, body={"tags": tags}) return self._prepare_return_value(resp, body['tags']) def delete_tag(self, project, tag): """Remove tag from project. :param projectd: project to remove tag from. :param tag: str name of tag to remove from project """ return self._delete( "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag))) def list_tags(self, project): """List tags associated with project. :param project: project to list tags for. :returns: list of str tag names """ url = "/projects/%s/tags" % base.getid(project) resp, body = self.client.get(url) body['tags'] = self._encode_tags(body['tags']) return self._prepare_return_value(resp, body['tags']) def check_tag(self, project, tag): """Check if tag is associated with project. :param project: project to check tags for. :param tag: str name of tag :returns: true if tag is associated, false otherwise """ url = "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag)) try: resp, body = self.client.head(url) # no errors means found the tag return self._prepare_return_value(resp, True) except exceptions.HttpError as ex: # return false with request_id if include_metadata=True return self._prepare_return_value(ex.response, False) python-keystoneclient-4.0.0/keystoneclient/v3/__init__.py0000664000175000017500000000012313644407055023621 0ustar zuulzuul00000000000000 from keystoneclient.v3.client import Client # noqa __all__ = ( 'client', ) python-keystoneclient-4.0.0/keystoneclient/v3/client.py0000664000175000017500000003402013644407055023343 0ustar zuulzuul00000000000000# 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 logging import warnings from oslo_serialization import jsonutils from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ from keystoneclient.v3 import access_rules from keystoneclient.v3 import application_credentials from keystoneclient.v3 import auth from keystoneclient.v3.contrib import endpoint_filter from keystoneclient.v3.contrib import endpoint_policy from keystoneclient.v3.contrib import federation from keystoneclient.v3.contrib import oauth1 from keystoneclient.v3.contrib import simple_cert from keystoneclient.v3.contrib import trusts from keystoneclient.v3 import credentials from keystoneclient.v3 import domain_configs from keystoneclient.v3 import domains from keystoneclient.v3 import ec2 from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoints from keystoneclient.v3 import groups from keystoneclient.v3 import limits from keystoneclient.v3 import policies from keystoneclient.v3 import projects from keystoneclient.v3 import regions from keystoneclient.v3 import registered_limits from keystoneclient.v3 import role_assignments from keystoneclient.v3 import roles from keystoneclient.v3 import services from keystoneclient.v3 import tokens from keystoneclient.v3 import users _logger = logging.getLogger(__name__) class Client(httpclient.HTTPClient): r"""Client for the OpenStack Identity API v3. :param session: Session for requests. (optional) :type session: keystoneauth1.session.Session :param string user_id: User ID for authentication. (optional) :param string username: Username for authentication. (optional) :param string user_domain_id: User's domain ID for authentication. (optional) :param string user_domain_name: User's domain name for authentication. (optional) :param string password: Password for authentication. (optional) :param string token: Token for authentication. (optional) :param string domain_id: Domain ID for domain scoping. (optional) :param string domain_name: Domain name for domain scoping. (optional) :param string project_id: Project ID for project scoping. (optional) :param string project_name: Project name for project scoping. (optional) :param string project_domain_id: Project's domain ID for project scoping. (optional) :param string project_domain_name: Project's domain name for project scoping. (optional) :param string tenant_name: Tenant name. (optional) The tenant_name keyword argument is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. :param string tenant_id: Tenant id. (optional) The tenant_id keyword argument is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. :param string auth_url: Identity service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param string endpoint: A user-supplied endpoint URL for the identity service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation. (optional) :param integer timeout: Allows customization of the timeout for client http requests. (optional) .. warning:: Constructing an instance of this class without a session is deprecated as of the 1.7.0 release and will be removed in the 2.0.0 release. Example:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(user_domain_name=DOMAIN_NAME, ... username=USER, ... password=PASS, ... project_domain_name=PROJECT_DOMAIN_NAME, ... project_name=PROJECT_NAME, ... auth_url=KEYSTONE_URL) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() ... >>> user = keystone.users.get(USER_ID) >>> user.delete() Instances of this class have the following managers: .. py:attribute:: credentials :py:class:`keystoneclient.v3.credentials.CredentialManager` .. py:attribute:: domain_configs :py:class:`keystoneclient.v3.domain_configs.DomainConfigManager` .. py:attribute:: ec2 :py:class:`keystoneclient.v3.ec2.EC2Manager` .. py:attribute:: endpoint_filter :py:class:`keystoneclient.v3.contrib.endpoint_filter.\ EndpointFilterManager` .. py:attribute:: endpoint_groups :py:class:`keystoneclient.v3.endpoint_groups.\ EndpointGroupManager` .. py:attribute:: endpoint_policy :py:class:`keystoneclient.v3.contrib.endpoint_policy.\ EndpointPolicyManager` .. py:attribute:: endpoints :py:class:`keystoneclient.v3.endpoints.EndpointManager` .. py:attribute:: domains :py:class:`keystoneclient.v3.domains.DomainManager` .. py:attribute:: federation :py:class:`keystoneclient.v3.contrib.federation.core.FederationManager` .. py:attribute:: groups :py:class:`keystoneclient.v3.groups.GroupManager` .. py:attribute:: limits :py:class:`keystoneclient.v3.limits.LimitManager` .. py:attribute:: oauth1 :py:class:`keystoneclient.v3.contrib.oauth1.core.OAuthManager` .. py:attribute:: policies :py:class:`keystoneclient.v3.policies.PolicyManager` .. py:attribute:: regions :py:class:`keystoneclient.v3.regions.RegionManager` .. py:attribute:: registered_limits :py:class:`keystoneclient.v3.registered_limits.RegisteredLimitManager` .. py:attribute:: role_assignments :py:class:`keystoneclient.v3.role_assignments.RoleAssignmentManager` .. py:attribute:: roles :py:class:`keystoneclient.v3.roles.RoleManager` .. py:attribute:: simple_cert :py:class:`keystoneclient.v3.contrib.simple_cert.SimpleCertManager` .. py:attribute:: services :py:class:`keystoneclient.v3.services.ServiceManager` .. py:attribute:: tokens :py:class:`keystoneclient.v3.tokens.TokenManager` .. py:attribute:: trusts :py:class:`keystoneclient.v3.contrib.trusts.TrustManager` .. py:attribute:: users :py:class:`keystoneclient.v3.users.UserManager` """ version = 'v3' def __init__(self, **kwargs): """Initialize a new client for the Keystone v3 API.""" super(Client, self).__init__(**kwargs) if not kwargs.get('session'): warnings.warn( 'Constructing an instance of the ' 'keystoneclient.v3.client.Client class without a session is ' 'deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) self.access_rules = ( access_rules.AccessRuleManager(self._adapter) ) self.application_credentials = ( application_credentials.ApplicationCredentialManager(self._adapter) ) self.auth = auth.AuthManager(self._adapter) self.credentials = credentials.CredentialManager(self._adapter) self.ec2 = ec2.EC2Manager(self._adapter) self.endpoint_filter = endpoint_filter.EndpointFilterManager( self._adapter) self.endpoint_groups = endpoint_groups.EndpointGroupManager( self._adapter) self.endpoint_policy = endpoint_policy.EndpointPolicyManager( self._adapter) self.endpoints = endpoints.EndpointManager(self._adapter) self.domain_configs = domain_configs.DomainConfigManager(self._adapter) self.domains = domains.DomainManager(self._adapter) self.federation = federation.FederationManager(self._adapter) self.groups = groups.GroupManager(self._adapter) self.limits = limits.LimitManager(self._adapter) self.oauth1 = oauth1.create_oauth_manager(self._adapter) self.policies = policies.PolicyManager(self._adapter) self.projects = projects.ProjectManager(self._adapter) self.registered_limits = registered_limits.RegisteredLimitManager( self._adapter) self.regions = regions.RegionManager(self._adapter) self.role_assignments = ( role_assignments.RoleAssignmentManager(self._adapter)) self.roles = roles.RoleManager(self._adapter) self.inference_rules = roles.InferenceRuleManager(self._adapter) self.services = services.ServiceManager(self._adapter) self.simple_cert = simple_cert.SimpleCertManager(self._adapter) self.tokens = tokens.TokenManager(self._adapter) self.trusts = trusts.TrustManager(self._adapter) self.users = users.UserManager(self._adapter) # DEPRECATED: if session is passed then we go to the new behaviour of # authenticating on the first required call. if 'session' not in kwargs and self.management_url is None: self.authenticate() def serialize(self, entity): return jsonutils.dumps(entity, sort_keys=True) def process_token(self, **kwargs): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ super(Client, self).process_token(**kwargs) if self.auth_ref.domain_scoped: if not self.auth_ref.domain_id: raise exceptions.AuthorizationFailure( _("Token didn't provide domain_id")) self._process_management_url(kwargs.get('region_name')) self.domain_name = self.auth_ref.domain_name self.domain_id = self.auth_ref.domain_id if self._management_url: self._management_url = self._management_url.replace('/v2.0', '/v3') def get_raw_token_from_identity_service(self, auth_url, user_id=None, username=None, user_domain_id=None, user_domain_name=None, password=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, token=None, trust_id=None, **kwargs): """Authenticate against the v3 Identity API. If password and token methods are both provided then both methods will be used in the request. :returns: access.AccessInfo if authentication was successful. :rtype: :class:`keystoneclient.access.AccessInfoV3` :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token. :raises keystoneclient.exceptions.Unauthorized: if authentication fails due to invalid token. """ try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) auth_methods = [] if token: auth_methods.append(v3_auth.TokenMethod(token=token)) if password: m = v3_auth.PasswordMethod(user_id=user_id, username=username, user_domain_id=user_domain_id, user_domain_name=user_domain_name, password=password) auth_methods.append(m) if not auth_methods: msg = _('A user and password or token is required.') raise exceptions.AuthorizationFailure(msg) plugin = v3_auth.Auth(auth_url, auth_methods, trust_id=trust_id, 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 plugin.get_auth_ref(self.session) except (exceptions.AuthorizationFailure, exceptions.Unauthorized): _logger.debug('Authorization failed.') raise except exceptions.EndpointNotFound: msg = _('There was no suitable authentication url for this' ' request') raise exceptions.AuthorizationFailure(msg) except Exception as e: raise exceptions.AuthorizationFailure( _('Authorization failed: %s') % e) python-keystoneclient-4.0.0/keystoneclient/v3/tokens.py0000664000175000017500000001125313644407055023373 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import access from keystoneclient import base def _calc_id(token): if isinstance(token, access.AccessInfo): return token.auth_token return base.getid(token) class TokenManager(object): """Manager class for manipulating Identity tokens.""" def __init__(self, client): self._client = client def revoke_token(self, token): """Revoke a token. :param token: The token to be revoked. :type token: str or :class:`keystoneclient.access.AccessInfo` """ token_id = _calc_id(token) headers = {'X-Subject-Token': token_id} return self._client.delete('/auth/tokens', headers=headers) def get_revoked(self, audit_id_only=False): """Get revoked tokens list. :param bool audit_id_only: If true, the server is requested to not send token IDs, but only audit IDs instead. **New in version 2.2.0.** :returns: A dict containing ``signed`` which is a CMS formatted string if the server signed the response. If `audit_id_only` is true then the response may be a dict containing ``revoked`` which is the list of token audit IDs and expiration times. :rtype: dict """ path = '/auth/tokens/OS-PKI/revoked' if audit_id_only: path += '?audit_id_only' resp, body = self._client.get(path) return body def get_token_data(self, token, include_catalog=True, allow_expired=False, access_rules_support=None): """Fetch the data about a token from the identity server. :param str token: The ID of the token to be fetched. :param bool include_catalog: Whether the service catalog should be included in the response. :param allow_expired: If True the token will be validated and returned if it has already expired. :param access_rules_support: Version number indicating that the client is capable of enforcing keystone access rules, if unset this client does not support access rules. :type access_rules_support: float :rtype: dict """ headers = {'X-Subject-Token': token} if access_rules_support: headers['OpenStack-Identity-Access-Rules'] = access_rules_support flags = [] url = '/auth/tokens' if not include_catalog: flags.append('nocatalog') if allow_expired: flags.append('allow_expired=1') if flags: url = '%s?%s' % (url, '&'.join(flags)) resp, body = self._client.get(url, headers=headers) return body def validate(self, token, include_catalog=True, allow_expired=False, access_rules_support=None): """Validate a token. :param token: The token to be validated. :type token: str or :class:`keystoneclient.access.AccessInfo` :param include_catalog: If False, the response is requested to not include the catalog. :param allow_expired: If True the token will be validated and returned if it has already expired. :type allow_expired: bool :param access_rules_support: Version number indicating that the client is capable of enforcing keystone access rules, if unset this client does not support access rules. :type access_rules_support: float :rtype: :class:`keystoneclient.access.AccessInfoV3` """ token_id = _calc_id(token) body = self.get_token_data(token_id, include_catalog=include_catalog, allow_expired=allow_expired, access_rules_support=access_rules_support) return access.AccessInfo.factory(auth_token=token_id, body=body) python-keystoneclient-4.0.0/keystoneclient/v3/ec2.py0000664000175000017500000000735413644407055022550 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class EC2(base.Resource): """Represents an EC2 resource. Attributes: * id: a string that identifies the EC2 resource. * user_id: the ID field of a pre-existing user in the backend. * project_id: the ID field of a pre-existing project in the backend. * access: a string representing access key of the access/secret pair. * secret: a string representing the secret of the access/secret pair. """ def __repr__(self): """Return string representation of EC2 resource information.""" return "" % self._info class EC2Manager(base.ManagerWithFind): resource_class = EC2 def create(self, user_id, project_id): """Create a new access/secret pair. :param user_id: the ID of the user having access/secret pair. :type user_id: str or :class:`keystoneclient.v3.users.User` :param project_id: the ID of the project having access/secret pair. :type project_id: str or :class:`keystoneclient.v3.projects.Project` :returns: the created access/secret pair returned from server. :rtype: :class:`keystoneclient.v3.ec2.EC2` """ # NOTE(jamielennox): Yes, this uses tenant_id as a key even though we # are in the v3 API. return self._post('/users/%s/credentials/OS-EC2' % user_id, body={'tenant_id': project_id}, response_key="credential") def get(self, user_id, access): """Retrieve an access/secret pair for a given access key. :param user_id: the ID of the user whose access/secret pair will be retrieved from the server. :type user_id: str or :class:`keystoneclient.v3.users.User` :param str access: the access key whose access/secret pair will be retrieved from the server. :returns: the specified access/secret pair returned from server. :rtype: :class:`keystoneclient.v3.ec2.EC2` """ url = "/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access)) return self._get(url, response_key="credential") def list(self, user_id): """List access/secret pairs for a given user. :param str user_id: the ID of the user having access/secret pairs will be listed. :returns: a list of access/secret pairs. :rtype: list of :class:`keystoneclient.v3.ec2.EC2` """ return self._list("/users/%s/credentials/OS-EC2" % user_id, response_key="credentials") def delete(self, user_id, access): """Delete an access/secret pair. :param user_id: the ID of the user whose access/secret pair will be deleted on the server. :type user_id: str or :class:`keystoneclient.v3.users.User` :param str access: the access key whose access/secret pair will be deleted on the server. :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return self._delete("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access))) python-keystoneclient-4.0.0/keystoneclient/v3/groups.py0000664000175000017500000001075513644407055023415 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Group(base.Resource): """Represents an Identity user group. Attributes: * id: a uuid that identifies the group * name: group name * description: group description """ def update(self, name=None, description=None): kwargs = { 'name': name if name is not None else self.name, 'description': (description if description is not None else self.description), } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval class GroupManager(base.CrudManager): """Manager class for manipulating Identity groups.""" resource_class = Group collection_key = 'groups' key = 'group' def create(self, name, domain=None, description=None, **kwargs): """Create a group. :param str name: the name of the group. :param domain: the domain of the group. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: a description of the group. :param kwargs: any other attribute provided will be passed to the server. :returns: the created group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).create( name=name, domain_id=base.getid(domain), description=description, **kwargs) def list(self, user=None, domain=None, **kwargs): """List groups. :param user: the user of the groups to be filtered on. :type user: str or :class:`keystoneclient.v3.users.User` :param domain: the domain of the groups to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param kwargs: any other attribute provided will filter groups on. :returns: a list of groups. :rtype: list of :class:`keystoneclient.v3.groups.Group`. """ if user: base_url = '/users/%s' % base.getid(user) else: base_url = None return super(GroupManager, self).list( base_url=base_url, domain_id=base.getid(domain), **kwargs) def get(self, group): """Retrieve a group. :param group: the group to be retrieved from the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: the specified group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).get( group_id=base.getid(group)) def update(self, group, name=None, description=None, **kwargs): """Update a group. :param group: the group to be updated on the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :param str name: the new name of the group. :param str description: the new description of the group. :param kwargs: any other attribute provided will be passed to server. :returns: the updated group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).update( group_id=base.getid(group), name=name, description=description, **kwargs) def delete(self, group): """Delete a group. :param group: the group to be deleted on the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(GroupManager, self).delete( group_id=base.getid(group)) python-keystoneclient-4.0.0/keystoneclient/v3/regions.py0000664000175000017500000001130513644407055023534 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from keystoneclient import base class Region(base.Resource): """Represents a Catalog region. Attributes: * id: a string that identifies the region. * description: a string that describes the region. * parent_region_id: a pre-existing region in the backend or its ID field. Allows for hierarchical region organization. * enabled: determines whether the endpoint appears in the catalog. """ pass class RegionManager(base.CrudManager): """Manager class for manipulating Identity regions.""" resource_class = Region collection_key = 'regions' key = 'region' @removals.removed_kwarg( 'enabled', message='The enabled parameter is deprecated.', version='3.18.0', removal_version='4.0.0') def create(self, id=None, description=None, enabled=True, parent_region=None, **kwargs): """Create a region. :param str id: the unique identifier of the region. If not specified an ID will be created by the server. :param str description: the description of the region. :param bool enabled: whether the region is enabled or not, determining if it appears in the catalog. :param parent_region: the parent of the region in the hierarchy. :type parent_region: str or :class:`keystoneclient.v3.regions.Region` :param kwargs: any other attribute provided will be passed to the server. :returns: the created region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).create( id=id, description=description, enabled=enabled, parent_region_id=base.getid(parent_region), **kwargs) def get(self, region): """Retrieve a region. :param region: the region to be retrieved from the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: the specified region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).get( region_id=base.getid(region)) def list(self, **kwargs): """List regions. :param kwargs: any attributes provided will filter regions on. :returns: a list of regions. :rtype: list of :class:`keystoneclient.v3.regions.Region`. """ return super(RegionManager, self).list( **kwargs) @removals.removed_kwarg( 'enabled', message='The enabled parameter is deprecated.', version='3.18.0', removal_version='4.0.0') def update(self, region, description=None, enabled=None, parent_region=None, **kwargs): """Update a region. :param region: the region to be updated on the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :param str description: the new description of the region. :param bool enabled: determining if the region appears in the catalog by enabling or disabling it. :param parent_region: the new parent of the region in the hierarchy. :type parent_region: str or :class:`keystoneclient.v3.regions.Region` :param kwargs: any other attribute provided will be passed to server. :returns: the updated region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).update( region_id=base.getid(region), description=description, enabled=enabled, parent_region_id=base.getid(parent_region), **kwargs) def delete(self, region): """Delete a region. :param region: the region to be deleted on the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(RegionManager, self).delete( region_id=base.getid(region)) python-keystoneclient-4.0.0/keystoneclient/v3/registered_limits.py0000664000175000017500000001432713644407055025613 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class RegisteredLimit(base.Resource): """Represents a registered limit. Attributes: * id: a UUID that identifies the registered limit * service_id: a UUID that identifies the service for the limit * region_id: a UUID that identifies the region for the limit * resource_name: the name of the resource to limit * default_limit: the default limit for projects to assume * description: a description of the registered limit """ pass class RegisteredLimitManager(base.CrudManager): """Manager class for registered limits.""" resource_class = RegisteredLimit collection_key = 'registered_limits' key = 'registered_limit' def create(self, service, resource_name, default_limit, description=None, region=None, **kwargs): """Create a registered limit. :param service: a UUID that identifies the service for the limit. :type service: str :param resource_name: the name of the resource to limit. :type resource_name: str :param default_limit: the default limit for projects to assume. :type default_limit: int :param description: a string that describes the limit :type description: str :param region: a UUID that identifies the region for the limit. :type region: str :returns: a reference of the created registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ # NOTE(lbragstad): Keystone's registered limit API supports creation of # limits in batches. This client accepts a single limit and passes it # to the identity service as a list of a single item. limit_data = base.filter_none( service_id=base.getid(service), resource_name=resource_name, default_limit=default_limit, description=description, region_id=base.getid(region), **kwargs ) body = {self.collection_key: [limit_data]} resp, body = self.client.post('/registered_limits', body=body) registered_limit = body[self.collection_key].pop() return self._prepare_return_value(resp, self.resource_class( self, registered_limit)) def update(self, registered_limit, service=None, resource_name=None, default_limit=None, description=None, region=None, **kwargs): """Update a registered limit. :param registered_limit: the UUID or reference of the registered limit to update. :param registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :param service: a UUID that identifies the service for the limit. :type service: str :param resource_name: the name of the resource to limit. :type resource_name: str :param default_limit: the default limit for projects to assume. :type default_limit: int :param description: a string that describes the limit :type description: str :param region: a UUID that identifies the region for the limit. :type region: str :returns: a reference of the updated registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).update( registered_limit_id=base.getid(registered_limit), service_id=base.getid(service), resource_name=resource_name, default_limit=default_limit, description=description, region=region, **kwargs ) def get(self, registered_limit): """Retrieve a registered limit. :param registered_limit: the registered limit to get. :type registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :returns: a specific registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).get( registered_limit_id=base.getid(registered_limit)) def list(self, service=None, resource_name=None, region=None, **kwargs): """List registered limits. Any parameter provided will be passed to the server as a filter. :param service: filter registered limits by service :type service: a UUID or :class:`keystoneclient.v3.services.Service` :param resource_name: filter registered limits by resource name :type resource_name: str :param region: filter registered limits by region :type region: a UUID or :class:`keystoneclient.v3.regions.Region` :returns: a list of registered limits. :rtype: list of :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).list( service_id=base.getid(service), resource_name=resource_name, region_id=base.getid(region), **kwargs) def delete(self, registered_limit): """Delete a registered limit. :param registered_limit: the registered limit to delete. :type registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ registered_limit_id = base.getid(registered_limit) return super(RegisteredLimitManager, self).delete( registered_limit_id=registered_limit_id ) python-keystoneclient-4.0.0/keystoneclient/v3/users.py0000664000175000017500000002662413644407055023241 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from debtcollector import renames from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class User(base.Resource): """Represents an Identity user. Attributes: * id: a uuid that identifies the user """ pass class UserManager(base.CrudManager): """Manager class for manipulating Identity users.""" resource_class = User collection_key = 'users' key = 'user' def _require_user_and_group(self, user, group): if not (user and group): msg = _('Specify both a user and a group') raise exceptions.ValidationError(msg) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def create(self, name, domain=None, project=None, password=None, email=None, description=None, enabled=True, default_project=None, **kwargs): """Create a user. :param str name: the name of the user. :param domain: the domain of the user. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the default project of the user. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param str password: the password for the user. :param str email: the email address of the user. :param str description: a description of the user. :param bool enabled: whether the user is enabled. :param default_project: the default project of the user. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server. :returns: the created user returned from server. :rtype: :class:`keystoneclient.v3.users.User` .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), default_project_id=default_project_id, password=password, email=email, description=description, enabled=enabled, **kwargs) return self._post('/users', {'user': user_data}, 'user', log=not bool(password)) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def list(self, project=None, domain=None, group=None, default_project=None, **kwargs): """List users. :param project: the default project of the users to be filtered on. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param domain: the domain of the users to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param group: the group in which the users are member of. :type group: str or :class:`keystoneclient.v3.groups.Group` :param default_project: the default project of the users to be filtered on. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will filter users on. :returns: a list of users. :rtype: list of :class:`keystoneclient.v3.users.User`. .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) if group: base_url = '/groups/%s' % base.getid(group) else: base_url = None return super(UserManager, self).list( base_url=base_url, domain_id=base.getid(domain), default_project_id=default_project_id, **kwargs) def get(self, user): """Retrieve a user. :param user: the user to be retrieved from the server. :type user: str or :class:`keystoneclient.v3.users.User` :returns: the specified user returned from server. :rtype: :class:`keystoneclient.v3.users.User` """ return super(UserManager, self).get( user_id=base.getid(user)) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def update(self, user, name=None, domain=None, project=None, password=None, email=None, description=None, enabled=None, default_project=None, **kwargs): """Update a user. :param user: the user to be updated on the server. :type user: str or :class:`keystoneclient.v3.users.User` :param str name: the new name of the user. :param domain: the new domain of the user. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the new default project of the user. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param str password: the new password of the user. :param str email: the new email of the user. :param str description: the newdescription of the user. :param bool enabled: whether the user is enabled. :param default_project: the new default project of the user. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to server. :returns: the updated user returned from server. :rtype: :class:`keystoneclient.v3.users.User` .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), default_project_id=default_project_id, password=password, email=email, description=description, enabled=enabled, **kwargs) return self._update('/users/%s' % base.getid(user), {'user': user_data}, 'user', method='PATCH', log=False) def update_password(self, old_password, new_password): """Update the password for the user the token belongs to. :param str old_password: the user's old password :param str new_password: the user's new password :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ if not (old_password and new_password): msg = _('Specify both the current password and a new password') raise exceptions.ValidationError(msg) if old_password == new_password: msg = _('Old password and new password must be different.') raise exceptions.ValidationError(msg) params = {'user': {'password': new_password, 'original_password': old_password}} base_url = '/users/%s/password' % self.client.user_id return self._update(base_url, params, method='POST', log=False) def add_to_group(self, user, group): """Add the specified user as a member of the specified group. :param user: the user to be added to the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to put the user in. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).put( base_url=base_url, user_id=base.getid(user)) def check_in_group(self, user, group): """Check if the specified user is a member of the specified group. :param user: the user to be verified in the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to check the user in. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).head( base_url=base_url, user_id=base.getid(user)) def remove_from_group(self, user, group): """Remove the specified user from the specified group. :param user: the user to be removed from the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to remove the user from. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).delete( base_url=base_url, user_id=base.getid(user)) def delete(self, user): """Delete a user. :param user: the user to be deleted on the server. :type user: str or :class:`keystoneclient.v3.users.User` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(UserManager, self).delete( user_id=base.getid(user)) python-keystoneclient-4.0.0/keystoneclient/v3/endpoint_groups.py0000664000175000017500000001153113644407055025306 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class EndpointGroup(base.Resource): """Represents an identity endpoint group. Attributes: * id: a UUID that identifies the endpoint group * name: the endpoint group name * description: the endpoint group description * filters: representation of filters in the format of JSON that define what endpoint entities are part of the group """ pass class EndpointGroupManager(base.CrudManager): """Manager class for Endpoint Groups.""" resource_class = EndpointGroup collection_key = 'endpoint_groups' key = 'endpoint_group' base_url = 'OS-EP-FILTER' def create(self, name, filters, description=None, **kwargs): """Create an endpoint group. :param str name: the name of the endpoint group. :param str filters: representation of filters in the format of JSON that define what endpoint entities are part of the group. :param str description: a description of the endpoint group. :param kwargs: any other attribute provided will be passed to the server. :returns: the created endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).create( name=name, filters=filters, description=description, **kwargs) def get(self, endpoint_group): """Retrieve an endpoint group. :param endpoint_group: the endpoint group to be retrieved from the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: the specified endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).get( endpoint_group_id=base.getid(endpoint_group)) def check(self, endpoint_group): """Check if an endpoint group exists. :param endpoint_group: the endpoint group to be checked against the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: none if the specified endpoint group exists. """ return super(EndpointGroupManager, self).head( endpoint_group_id=base.getid(endpoint_group)) def list(self, **kwargs): """List endpoint groups. Any parameter provided will be passed to the server. :returns: a list of endpoint groups. :rtype: list of :class:`keystoneclient.v3.endpoint_groups.EndpointGroup`. """ return super(EndpointGroupManager, self).list(**kwargs) def update(self, endpoint_group, name=None, filters=None, description=None, **kwargs): """Update an endpoint group. :param str name: the new name of the endpoint group. :param str filters: the new representation of filters in the format of JSON that define what endpoint entities are part of the group. :param str description: the new description of the endpoint group. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).update( endpoint_group_id=base.getid(endpoint_group), name=name, filters=filters, description=description, **kwargs) def delete(self, endpoint_group): """Delete an endpoint group. :param endpoint_group: the endpoint group to be deleted on the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(EndpointGroupManager, self).delete( endpoint_group_id=base.getid(endpoint_group)) python-keystoneclient-4.0.0/keystoneclient/v3/role_assignments.py0000664000175000017500000001337013644407055025446 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class RoleAssignment(base.Resource): """Represents an Identity role assignment. Attributes: * role: an object which contains a role uuid * user or group: an object which contains either a user or group uuid * scope: an object which has either a project or domain object containing an uuid """ pass class RoleAssignmentManager(base.CrudManager): """Manager class for manipulating Identity roles assignments.""" resource_class = RoleAssignment collection_key = 'role_assignments' key = 'role_assignment' def _check_not_user_and_group(self, user, group): if user and group: msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) def _check_not_domain_and_project(self, domain, project): if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) def _check_not_system_and_domain(self, system, domain): if system and domain: msg = _('Specify either system or domain, not both') raise exceptions.ValidationError(msg) def _check_not_system_and_project(self, system, project): if system and project: msg = _('Specify either system or project, not both') raise exceptions.ValidationError(msg) def _check_system_value(self, system): if system and system != 'all': msg = _("Only a system scope of 'all' is currently supported") raise exceptions.ValidationError(msg) def list(self, user=None, group=None, project=None, domain=None, system=False, role=None, effective=False, os_inherit_extension_inherited_to=None, include_subtree=False, include_names=False): """List role assignments. If no arguments are provided, all role assignments in the system will be listed. If both user and group are provided, a ValidationError will be raised. If both domain and project are provided, it will also raise a ValidationError. :param user: User to be used as query filter. (optional) :param group: Group to be used as query filter. (optional) :param project: Project to be used as query filter. (optional) :param domain: Domain to be used as query filter. (optional) :param system: Boolean to be used to filter system assignments. (optional) :param role: Role to be used as query filter. (optional) :param boolean effective: return effective role assignments. (optional) :param string os_inherit_extension_inherited_to: return inherited role assignments for either 'projects' or 'domains'. (optional) :param boolean include_subtree: Include subtree (optional) :param boolean include_names: Display names instead of IDs. (optional) """ self._check_not_user_and_group(user, group) self._check_not_domain_and_project(domain, project) self._check_not_system_and_domain(system, domain) self._check_not_system_and_project(system, project) self._check_system_value(system) query_params = {} if user: query_params['user.id'] = base.getid(user) if group: query_params['group.id'] = base.getid(group) if project: query_params['scope.project.id'] = base.getid(project) if domain: query_params['scope.domain.id'] = base.getid(domain) if system: query_params['scope.system'] = system if role: query_params['role.id'] = base.getid(role) if effective: query_params['effective'] = effective if include_names: query_params['include_names'] = include_names if os_inherit_extension_inherited_to: query_params['scope.OS-INHERIT:inherited_to'] = ( os_inherit_extension_inherited_to) if include_subtree: query_params['include_subtree'] = include_subtree return super(RoleAssignmentManager, self).list(**query_params) def create(self, **kwargs): raise exceptions.MethodNotImplemented( _('Create not supported for role assignments')) def update(self, **kwargs): raise exceptions.MethodNotImplemented( _('Update not supported for role assignments')) def get(self, **kwargs): raise exceptions.MethodNotImplemented( _('Get not supported for role assignments')) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for role assignments')) def put(self, **kwargs): raise exceptions.MethodNotImplemented( _('Put not supported for role assignments')) def delete(self, **kwargs): raise exceptions.MethodNotImplemented( _('Delete not supported for role assignments')) python-keystoneclient-4.0.0/keystoneclient/v3/services.py0000664000175000017500000001120113644407055023704 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Service(base.Resource): """Represents an Identity service. Attributes: * id: a uuid that identifies the service * name: the user-facing name of the service (e.g. Keystone) * description: a description of the service * type: the type of the service (e.g. 'compute', 'identity') * enabled: determines whether the service appears in the catalog """ pass class ServiceManager(base.CrudManager): """Manager class for manipulating Identity services.""" resource_class = Service collection_key = 'services' key = 'service' def create(self, name, type=None, enabled=True, description=None, **kwargs): """Create a service. :param str name: the name of the service. :param str type: the type of the service. :param bool enabled: whether the service appears in the catalog. :param str description: the description of the service. :param kwargs: any other attribute provided will be passed to the server. :returns: the created service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).create( name=name, type=type_arg, description=description, enabled=enabled, **kwargs) def get(self, service): """Retrieve a service. :param service: the service to be retrieved from the server. :type service: str or :class:`keystoneclient.v3.services.Service` :returns: the specified service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ return super(ServiceManager, self).get( service_id=base.getid(service)) def list(self, name=None, type=None, **kwargs): """List services. :param str name: the name of the services to be filtered on. :param str type: the type of the services to be filtered on. :param kwargs: any other attribute provided will filter services on. :returns: a list of services. :rtype: list of :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).list( name=name, type=type_arg, **kwargs) def update(self, service, name=None, type=None, enabled=None, description=None, **kwargs): """Update a service. :param service: the service to be updated on the server. :type service: str or :class:`keystoneclient.v3.services.Service` :param str name: the new name of the service. :param str type: the new type of the service. :param bool enabled: whether the service appears in the catalog. :param str description: the new description of the service. :param kwargs: any other attribute provided will be passed to server. :returns: the updated service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).update( service_id=base.getid(service), name=name, type=type_arg, description=description, enabled=enabled, **kwargs) def delete(self, service=None, id=None): """Delete a service. :param service: the service to be deleted on the server. :type service: str or :class:`keystoneclient.v3.services.Service` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ if service: service_id = base.getid(service) else: service_id = id return super(ServiceManager, self).delete( service_id=service_id) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/0000775000175000017500000000000013644407143023152 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v3/contrib/simple_cert.py0000664000175000017500000000266013644407055026040 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class SimpleCertManager(object): """Manager for the OS-SIMPLE-CERT extension.""" def __init__(self, client): self._client = client self.mgr = base.Manager(self._client) def get_ca_certificates(self): """Get CA certificates. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/OS-SIMPLE-CERT/ca', authenticated=False) return self.mgr._prepare_return_value(resp, resp.text) def get_certificates(self): """Get signing certificates. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/OS-SIMPLE-CERT/certificates', authenticated=False) return self.mgr._prepare_return_value(resp, resp.text) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/__init__.py0000664000175000017500000000002313644407055025260 0ustar zuulzuul00000000000000 __all__ = tuple() python-keystoneclient-4.0.0/keystoneclient/v3/contrib/endpoint_policy.py0000664000175000017500000001445313644407055026734 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base from keystoneclient.i18n import _ from keystoneclient.v3 import endpoints from keystoneclient.v3 import policies class EndpointPolicyManager(base.Manager): """Manager class for manipulating endpoint-policy associations.""" OS_EP_POLICY_EXT = 'OS-ENDPOINT-POLICY' def _act_on_policy_association_for_endpoint( self, policy, endpoint, action): if not (policy and endpoint): raise ValueError(_('policy and endpoint are required')) policy_id = base.getid(policy) endpoint_id = base.getid(endpoint) url = ('/policies/%(policy_id)s/%(ext_name)s' '/endpoints/%(endpoint_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'endpoint_id': endpoint_id} return action(url=url) def create_policy_association_for_endpoint(self, policy, endpoint): """Create an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._put) def check_policy_association_for_endpoint(self, policy, endpoint): """Check an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._head) def delete_policy_association_for_endpoint(self, policy, endpoint): """Delete an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._delete) def _act_on_policy_association_for_service(self, policy, service, action): if not (policy and service): raise ValueError(_('policy and service are required')) policy_id = base.getid(policy) service_id = base.getid(service) url = ('/policies/%(policy_id)s/%(ext_name)s' '/services/%(service_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'service_id': service_id} return action(url=url) def create_policy_association_for_service(self, policy, service): """Create an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._put) def check_policy_association_for_service(self, policy, service): """Check an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._head) def delete_policy_association_for_service(self, policy, service): """Delete an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._delete) def _act_on_policy_association_for_region_and_service( self, policy, region, service, action): if not (policy and region and service): raise ValueError(_('policy, region and service are required')) policy_id = base.getid(policy) region_id = base.getid(region) service_id = base.getid(service) url = ('/policies/%(policy_id)s/%(ext_name)s' '/services/%(service_id)s/regions/%(region_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'service_id': service_id, 'region_id': region_id} return action(url=url) def create_policy_association_for_region_and_service( self, policy, region, service): """Create an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._put) def check_policy_association_for_region_and_service( self, policy, region, service): """Check an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._head) def delete_policy_association_for_region_and_service( self, policy, region, service): """Delete an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._delete) def get_policy_for_endpoint(self, endpoint): """Get the effective policy for an endpoint. :param endpoint: endpoint object or ID :returns: policies.Policy object """ if not endpoint: raise ValueError(_('endpoint is required')) endpoint_id = base.getid(endpoint) url = ('/endpoints/%(endpoint_id)s/%(ext_name)s/policy') % { 'endpoint_id': endpoint_id, 'ext_name': self.OS_EP_POLICY_EXT} resp, body = self.client.get(url) return self._prepare_return_value( resp, policies.Policy(self, body[policies.PolicyManager.key], loaded=True)) def list_endpoints_for_policy(self, policy): """List endpoints with the effective association to a policy. :param policy: policy object or ID :returns: list of endpoints that are associated with the policy """ if not policy: raise ValueError(_('policy is required')) policy_id = base.getid(policy) url = ('/policies/%(policy_id)s/%(ext_name)s/endpoints') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT} return self._list( url, endpoints.EndpointManager.collection_key, obj_class=endpoints.EndpointManager.resource_class) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/0000775000175000017500000000000013644407143025272 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/protocols.py0000664000175000017500000001246313644407055027700 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class Protocol(base.Resource): """An object representing federation protocol container. Attributes: * id: user-defined unique per Identity Provider string identifying federation protocol. """ pass class ProtocolManager(base.CrudManager): """Manager class for manipulating federation protocols.""" resource_class = Protocol collection_key = 'protocols' key = 'protocol' base_url = 'OS-FEDERATION/identity_providers' def build_url(self, dict_args_in_out=None): """Build URL for federation protocols.""" if dict_args_in_out is None: dict_args_in_out = {} identity_provider_id = dict_args_in_out.pop('identity_provider_id', None) if identity_provider_id: base_url = '/'.join([self.base_url, identity_provider_id]) else: base_url = self.base_url dict_args_in_out.setdefault('base_url', base_url) return super(ProtocolManager, self).build_url(dict_args_in_out) def _build_url_and_put(self, request_body=None, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: request_body} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, protocol_id, identity_provider, mapping, **kwargs): """Create federation protocol object and tie to the Identity Provider. Utilize Identity API operation: PUT /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param protocol_id: a string type parameter identifying a federation protocol :param identity_provider: a string type parameter identifying an Identity Provider :param mapping: a base.Resource object with federation mapping id """ return self._build_url_and_put( request_body={'mapping_id': base.getid(mapping)}, identity_provider_id=base.getid(identity_provider), protocol_id=protocol_id, **kwargs) def get(self, identity_provider, protocol, **kwargs): """Fetch federation protocol object tied to the Identity Provider. Utilize Identity API operation: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside """ return super(ProtocolManager, self).get( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol), **kwargs) def list(self, identity_provider, **kwargs): """List all federation protocol objects tied to the Identity Provider. Utilize Identity API operation: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols :param identity_provider: a base.Resource type object with Identity Provider id stored inside """ return super(ProtocolManager, self).list( identity_provider_id=base.getid(identity_provider), **kwargs) def update(self, identity_provider, protocol, mapping, **kwargs): """Update Protocol object tied to the Identity Provider. Utilize Identity API operation: PATCH /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside :param mapping: a base.Resource object with federation mapping id """ return super(ProtocolManager, self).update( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol), mapping_id=base.getid(mapping), **kwargs) def delete(self, identity_provider, protocol): """Delete Protocol object tied to the Identity Provider. Utilize Identity API operation: DELETE /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside """ return super(ProtocolManager, self).delete( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/projects.py0000664000175000017500000000144713644407055027505 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.v3.contrib.federation import base as federation_base from keystoneclient.v3 import projects class ProjectManager(federation_base.EntityManager): object_type = 'projects' resource_class = projects.Project python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/__init__.py0000664000175000017500000000117513644407055027411 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.v3.contrib.federation.core import * # noqa python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/service_providers.py0000664000175000017500000000645713644407055031417 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class ServiceProvider(base.Resource): """Object representing Service Provider container. Attributes: * id: user-defined unique string identifying Service Provider. * sp_url: the shibboleth endpoint of a Service Provider. * auth_url: the authentication url of Service Provider. """ pass class ServiceProviderManager(base.CrudManager): """Manager class for manipulating Service Providers.""" resource_class = ServiceProvider collection_key = 'service_providers' key = 'service_provider' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, id, **kwargs): """Create Service Provider object. Utilize Keystone URI: ``PUT /OS-FEDERATION/service_providers/{id}`` :param id: unique id of the service provider. """ return self._build_url_and_put(service_provider_id=id, **kwargs) def get(self, service_provider): """Fetch Service Provider object. Utilize Keystone URI: ``GET /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).get( service_provider_id=base.getid(service_provider)) def list(self, **kwargs): """List all Service Providers. Utilize Keystone URI: ``GET /OS-FEDERATION/service_providers`` """ return super(ServiceProviderManager, self).list(**kwargs) def update(self, service_provider, **kwargs): """Update the existing Service Provider object on the server. Only properties provided to the function are being updated. Utilize Keystone URI: ``PATCH /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).update( service_provider_id=base.getid(service_provider), **kwargs) def delete(self, service_provider): """Delete Service Provider object. Utilize Keystone URI: ``DELETE /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).delete( service_provider_id=base.getid(service_provider)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/base.py0000664000175000017500000000252713644407055026566 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneauth1 import exceptions from keystoneauth1 import plugin import six from keystoneclient import base @six.add_metaclass(abc.ABCMeta) class EntityManager(base.Manager): """Manager class for listing federated accessible objects.""" resource_class = None @abc.abstractproperty def object_type(self): raise exceptions.MethodNotImplemented def list(self): url = '/auth/%s' % self.object_type try: tenant_list = self._list(url, self.object_type) except exceptions.CatalogException: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} tenant_list = self._list(url, self.object_type, endpoint_filter=endpoint_filter) return tenant_list python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/mappings.py0000664000175000017500000001055613644407055027473 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class Mapping(base.Resource): """An object representing mapping container. Attributes: * id: user defined unique string identifying mapping. """ pass class MappingManager(base.CrudManager): """Manager class for manipulating federation mappings.""" resource_class = Mapping collection_key = 'mappings' key = 'mapping' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, mapping_id, **kwargs): """Create federation mapping. Utilize Identity API operation: PUT /OS-FEDERATION/mappings/$mapping_id :param mapping_id: user defined string identifier of the federation mapping. :param rules: a list of mapping rules. Example of the ``rules`` parameter:: [ { "local": [ { "group": { "id": "0cd5e9" } } ], "remote": [ { "type": "orgPersonType", "not_any_of": [ "Contractor", "Guest" ] } ] } ] """ return self._build_url_and_put( mapping_id=mapping_id, **kwargs) def get(self, mapping): """Fetch federation mapping identified by mapping id. Utilize Identity API operation: GET /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. """ return super(MappingManager, self).get( mapping_id=base.getid(mapping)) def list(self, **kwargs): """List all federation mappings. Utilize Identity API operation: GET /OS-FEDERATION/mappings """ return super(MappingManager, self).list(**kwargs) def update(self, mapping, **kwargs): """Update federation mapping identified by mapping id. Utilize Identity API operation: PATCH /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. :param rules: a list of mapping rules. Example of the ``rules`` parameter:: [ { "local": [ { "group": { "id": "0cd5e9" } } ], "remote": [ { "type": "orgPersonType", "not_any_of": [ "Contractor", "Guest" ] } ] } ] """ return super(MappingManager, self).update( mapping_id=base.getid(mapping), **kwargs) def delete(self, mapping): """Delete federation mapping identified by mapping id. Utilize Identity API operation: DELETE /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. """ return super(MappingManager, self).delete( mapping_id=base.getid(mapping)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/core.py0000664000175000017500000000271613644407055026604 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.v3.contrib.federation import domains from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3.contrib.federation import mappings from keystoneclient.v3.contrib.federation import projects from keystoneclient.v3.contrib.federation import protocols from keystoneclient.v3.contrib.federation import saml from keystoneclient.v3.contrib.federation import service_providers class FederationManager(object): def __init__(self, api): self.identity_providers = identity_providers.IdentityProviderManager( api) self.mappings = mappings.MappingManager(api) self.protocols = protocols.ProtocolManager(api) self.projects = projects.ProjectManager(api) self.domains = domains.DomainManager(api) self.saml = saml.SamlManager(api) self.service_providers = service_providers.ServiceProviderManager(api) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/identity_providers.py0000664000175000017500000000742513644407055031604 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class IdentityProvider(base.Resource): """Object representing Identity Provider container. Attributes: * id: user-defined unique string identifying Identity Provider. """ pass class IdentityProviderManager(base.CrudManager): """Manager class for manipulating Identity Providers.""" resource_class = IdentityProvider collection_key = 'identity_providers' key = 'identity_provider' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, id, **kwargs): """Create Identity Provider object. Utilize Keystone URI: PUT /OS-FEDERATION/identity_providers/$identity_provider :param id: unique id of the identity provider. :param kwargs: optional attributes: description (str), domain_id (str), enabled (boolean) and remote_ids (list). :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return self._build_url_and_put(identity_provider_id=id, **kwargs) def get(self, identity_provider): """Fetch Identity Provider object. Utilize Keystone URI: GET /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: an object with identity_provider_id stored inside. :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return super(IdentityProviderManager, self).get( identity_provider_id=base.getid(identity_provider)) def list(self, **kwargs): """List all Identity Providers. Utilize Keystone URI: GET /OS-FEDERATION/identity_providers :returns: a list of IdentityProvider resource objects. :rtype: List """ return super(IdentityProviderManager, self).list(**kwargs) def update(self, identity_provider, **kwargs): """Update Identity Provider object. Utilize Keystone URI: PATCH /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: an object with identity_provider_id stored inside. :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return super(IdentityProviderManager, self).update( identity_provider_id=base.getid(identity_provider), **kwargs) def delete(self, identity_provider): """Delete Identity Provider object. Utilize Keystone URI: DELETE /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: the Identity Provider ID itself or an object with it stored inside. """ return super(IdentityProviderManager, self).delete( identity_provider_id=base.getid(identity_provider)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/saml.py0000664000175000017500000000545313644407055026611 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base SAML2_ENDPOINT = '/auth/OS-FEDERATION/saml2' ECP_ENDPOINT = '/auth/OS-FEDERATION/saml2/ecp' class SamlManager(base.Manager): """Manager class for creating SAML assertions.""" def create_saml_assertion(self, service_provider, token_id): """Create a SAML assertion from a token. Equivalent Identity API call: POST /auth/OS-FEDERATION/saml2 :param service_provider: Service Provider resource. :type service_provider: string :param token_id: Token to transform to SAML assertion. :type token_id: string :returns: SAML representation of token_id :rtype: string """ headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(SAML2_ENDPOINT, json=body, headers=headers) return self._prepare_return_value(resp, resp.text) def create_ecp_assertion(self, service_provider, token_id): """Create an ECP wrapped SAML assertion from a token. Equivalent Identity API call: POST /auth/OS-FEDERATION/saml2/ecp :param service_provider: Service Provider resource. :type service_provider: string :param token_id: Token to transform to SAML assertion. :type token_id: string :returns: SAML representation of token_id, wrapped in ECP envelope :rtype: string """ headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(ECP_ENDPOINT, json=body, headers=headers) return self._prepare_return_value(resp, resp.text) def _create_common_request(self, service_provider, token_id): headers = {'Content-Type': 'application/json'} body = { 'auth': { 'identity': { 'methods': ['token'], 'token': { 'id': token_id } }, 'scope': { 'service_provider': { 'id': base.getid(service_provider) } } } } return (headers, body) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/federation/domains.py0000664000175000017500000000144213644407055027301 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.v3.contrib.federation import base as federation_base from keystoneclient.v3 import domains class DomainManager(federation_base.EntityManager): object_type = 'domains' resource_class = domains.Domain python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/0000775000175000017500000000000013644407143024353 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/request_tokens.py0000664000175000017500000000546113644407055030010 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 __future__ import unicode_literals from keystoneauth1 import plugin from six.moves.urllib import parse as urlparse from keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils try: from oauthlib import oauth1 except ImportError: oauth1 = None class RequestToken(base.Resource): def authorize(self, roles): try: retval = self.manager.authorize(self.id, roles) self = retval except Exception: retval = None return retval class RequestTokenManager(base.CrudManager): """Manager class for manipulating identity OAuth request tokens.""" resource_class = RequestToken def authorize(self, request_token, roles): """Authorize a request token with specific roles. Utilize Identity API operation: PUT /OS-OAUTH1/authorize/$request_token_id :param request_token: a request token that will be authorized, and can be exchanged for an access token. :param roles: a list of roles, that will be delegated to the user. """ request_id = urlparse.quote(base.getid(request_token)) endpoint = utils.OAUTH_PATH + '/authorize/%s' % (request_id) body = {'roles': [{'id': base.getid(r_id)} for r_id in roles]} return self._put(endpoint, body, "token") def create(self, consumer_key, consumer_secret, project): endpoint = utils.OAUTH_PATH + '/request_token' headers = {'requested-project-id': base.getid(project)} oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, signature_method=oauth1.SIGNATURE_HMAC, callback_uri="oob") url = self.client.get_endpoint(interface=plugin.AUTH_INTERFACE).rstrip( "/") url, headers, body = oauth_client.sign(url + endpoint, http_method='POST', headers=headers) resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) return self._prepare_return_value(resp, self.resource_class(self, token)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/__init__.py0000664000175000017500000000113613644407055026467 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.v3.contrib.oauth1.core import * # noqa python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/access_tokens.py0000664000175000017500000000373413644407055027562 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 __future__ import unicode_literals from keystoneauth1 import plugin from keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils try: from oauthlib import oauth1 except ImportError: oauth1 = None class AccessToken(base.Resource): pass class AccessTokenManager(base.CrudManager): """Manager class for manipulating identity OAuth access tokens.""" resource_class = AccessToken def create(self, consumer_key, consumer_secret, request_key, request_secret, verifier): endpoint = utils.OAUTH_PATH + '/access_token' oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, resource_owner_key=request_key, resource_owner_secret=request_secret, signature_method=oauth1.SIGNATURE_HMAC, verifier=verifier) url = self.client.get_endpoint(interface=plugin.AUTH_INTERFACE).rstrip( '/') url, headers, body = oauth_client.sign(url + endpoint, http_method='POST') resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) return self._prepare_return_value(resp, self.resource_class(self, token)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/core.py0000664000175000017500000000512013644407055025655 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 keystoneclient.i18n import _ from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens def create_oauth_manager(self): # NOTE(stevemar): Attempt to import the oauthlib package at this point. try: import oauthlib # noqa # NOTE(stevemar): Return an object instead of raising an exception here, # this will allow users to see an exception only when trying to access the # oauth portions of client. Otherwise an exception would be raised # when the client is created. except ImportError: return OAuthManagerOptionalImportProxy() else: return OAuthManager(self) class OAuthManager(object): def __init__(self, api): self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) self.request_tokens = request_tokens.RequestTokenManager(api) class OAuthManagerOptionalImportProxy(object): """Act as a proxy manager in case oauthlib is no installed. This class will only be created if oauthlib is not in the system, trying to access any of the attributes in name (access_tokens, consumers, request_tokens), will result in a NotImplementedError, and a message. >>> manager.access_tokens.blah NotImplementedError: To use 'access_tokens' oauthlib must be installed Otherwise, if trying to access an attribute other than the ones in name, the manager will state that the attribute does not exist. >>> manager.dne.blah AttributeError: 'OAuthManagerOptionalImportProxy' object has no attribute 'dne' """ def __getattribute__(self, name): """Return error when name is related to oauthlib and not exist.""" if name in ('access_tokens', 'consumers', 'request_tokens'): raise NotImplementedError( _('To use %r oauthlib must be installed') % name) return super(OAuthManagerOptionalImportProxy, self).__getattribute__(name) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/auth.py0000664000175000017500000000402413644407055025670 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 keystoneclient.auth.identity import v3 try: from oauthlib import oauth1 except ImportError: oauth1 = None class OAuthMethod(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 __init__(self, **kwargs): super(OAuthMethod, self).__init__(**kwargs) if oauth1 is None: raise NotImplementedError('optional package oauthlib' ' is not installed') 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', {} class OAuth(v3.AuthConstructor): _auth_method_class = OAuthMethod python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/utils.py0000664000175000017500000000241313644407055026067 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 six.moves.urllib import parse as urlparse OAUTH_PATH = '/OS-OAUTH1' def get_oauth_token_from_body(body): """Parse the URL response body to retrieve the oauth token key and secret. The response body will look like: 'oauth_token=12345&oauth_token_secret=67890' with 'oauth_expires_at=2013-03-30T05:27:19.463201' possibly there, too. """ if six.PY3: body = body.decode('utf-8') credentials = urlparse.parse_qs(body) key = credentials['oauth_token'][0] secret = credentials['oauth_token_secret'][0] token = {'key': key, 'id': key, 'secret': secret} expires_at = credentials.get('oauth_expires_at') if expires_at: token['expires'] = expires_at[0] return token python-keystoneclient-4.0.0/keystoneclient/v3/contrib/oauth1/consumers.py0000664000175000017500000000324413644407055026750 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils class Consumer(base.Resource): """Represents an OAuth consumer. Attributes: * id: a uuid that identifies the consumer * description: a short description of the consumer """ pass class ConsumerManager(base.CrudManager): """Manager class for manipulating identity consumers.""" resource_class = Consumer collection_key = 'consumers' key = 'consumer' base_url = utils.OAUTH_PATH def create(self, description=None, **kwargs): return super(ConsumerManager, self).create( description=description, **kwargs) def get(self, consumer): return super(ConsumerManager, self).get( consumer_id=base.getid(consumer)) def update(self, consumer, description=None, **kwargs): return super(ConsumerManager, self).update( consumer_id=base.getid(consumer), description=description, **kwargs) def delete(self, consumer): return super(ConsumerManager, self).delete( consumer_id=base.getid(consumer)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/trusts.py0000664000175000017500000000743213644407055025100 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import utils class Trust(base.Resource): """Represents a Trust. Attributes: * id: a uuid that identifies the trust * impersonation: allow explicit impersonation * project_id: project ID * trustee_user_id: a uuid that identifies the trustee * trustor_user_id: a uuid that identifies the trustor """ pass class TrustManager(base.CrudManager): """Manager class for manipulating Trusts.""" resource_class = Trust collection_key = 'trusts' key = 'trust' base_url = '/OS-TRUST' def create(self, trustee_user, trustor_user, role_names=None, role_ids=None, project=None, impersonation=False, expires_at=None, remaining_uses=None, **kwargs): """Create a Trust. :param string trustee_user: user who is capable of consuming the trust :param string trustor_user: user who's authorization is being delegated :param string role_names: subset of trustor's roles to be granted :param string role_ids: subset of trustor's roles to be granted :param string project: project which the trustor is delegating :param boolean impersonation: enable explicit impersonation :param datetime.datetime expires_at: expiry time :param integer remaining_uses: how many times this trust can be used to generate a token. None means unlimited tokens. """ # Convert role_names list into list-of-dict API format roles = [] if role_names: roles.extend([{'name': n} for n in role_names]) if role_ids: roles.extend([{'id': i} for i in role_ids]) if not roles: roles = None # Convert datetime.datetime expires_at to iso format string if expires_at: expires_str = utils.isotime(at=expires_at, subsecond=True) else: expires_str = None return super(TrustManager, self).create( expires_at=expires_str, impersonation=impersonation, project_id=base.getid(project), remaining_uses=remaining_uses, roles=roles, trustee_user_id=base.getid(trustee_user), trustor_user_id=base.getid(trustor_user), **kwargs) def update(self): raise exceptions.MethodNotImplemented( _('Update not supported for trusts')) def list(self, trustee_user=None, trustor_user=None, **kwargs): """List Trusts.""" trustee_user_id = base.getid(trustee_user) trustor_user_id = base.getid(trustor_user) return super(TrustManager, self).list(trustee_user_id=trustee_user_id, trustor_user_id=trustor_user_id, **kwargs) def get(self, trust): """Get a specific trust.""" return super(TrustManager, self).get(trust_id=base.getid(trust)) def delete(self, trust): """Delete a trust.""" return super(TrustManager, self).delete(trust_id=base.getid(trust)) python-keystoneclient-4.0.0/keystoneclient/v3/contrib/endpoint_filter.py0000664000175000017500000001536113644407055026721 0ustar zuulzuul00000000000000# Copyright 2014 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoints from keystoneclient.v3 import projects class EndpointFilterManager(base.Manager): """Manager class for manipulating project-endpoint associations. Project-endpoint associations can be with endpoints directly or via endpoint groups. """ OS_EP_FILTER_EXT = '/OS-EP-FILTER' def _build_base_url(self, project=None, endpoint=None): project_id = base.getid(project) endpoint_id = base.getid(endpoint) if project_id and endpoint_id: api_path = '/projects/%s/endpoints/%s' % (project_id, endpoint_id) elif project_id: api_path = '/projects/%s/endpoints' % (project_id) elif endpoint_id: api_path = '/endpoints/%s/projects' % (endpoint_id) else: msg = _('Must specify a project, an endpoint, or both') raise exceptions.ValidationError(msg) return self.OS_EP_FILTER_EXT + api_path def _build_group_base_url(self, project=None, endpoint_group=None): project_id = base.getid(project) endpoint_group_id = base.getid(endpoint_group) if project_id and endpoint_group_id: api_path = '/endpoint_groups/%s/projects/%s' % ( endpoint_group_id, project_id) elif project_id: api_path = '/projects/%s/endpoint_groups' % (project_id) elif endpoint_group_id: api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id) else: msg = _('Must specify a project, an endpoint group, or both') raise exceptions.ValidationError(msg) return self.OS_EP_FILTER_EXT + api_path def add_endpoint_to_project(self, project, endpoint): """Create a project-endpoint association.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._put(url=base_url) def delete_endpoint_from_project(self, project, endpoint): """Remove a project-endpoint association.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._delete(url=base_url) def check_endpoint_in_project(self, project, endpoint): """Check if project-endpoint association exists.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._head(url=base_url) def list_endpoints_for_project(self, project): """List all endpoints for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, endpoints.EndpointManager.collection_key, obj_class=endpoints.EndpointManager.resource_class) def list_projects_for_endpoint(self, endpoint): """List all projects for a given endpoint.""" if not endpoint: raise ValueError(_('endpoint is required')) base_url = self._build_base_url(endpoint=endpoint) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class) def add_endpoint_group_to_project(self, endpoint_group, project): """Create a project-endpoint group association.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._put(url=base_url) def delete_endpoint_group_from_project(self, endpoint_group, project): """Remove a project-endpoint group association.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._delete(url=base_url) def check_endpoint_group_in_project(self, endpoint_group, project): """Check if project-endpoint group association exists.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._head(url=base_url) def list_endpoint_groups_for_project(self, project): """List all endpoint groups for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_group_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, 'endpoint_groups', obj_class=endpoint_groups.EndpointGroupManager.resource_class) def list_projects_for_endpoint_group(self, endpoint_group): """List all projects associated with a given endpoint group.""" if not endpoint_group: raise ValueError(_('endpoint_group is required')) base_url = self._build_group_base_url(endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class) python-keystoneclient-4.0.0/keystoneclient/v3/auth.py0000664000175000017500000000460313644407055023032 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 plugin from keystoneclient import base from keystoneclient.v3 import domains from keystoneclient.v3 import projects Domain = domains.Domain Project = projects.Project class AuthManager(base.Manager): """Retrieve auth context specific information. The information returned by the auth routes is entirely dependent on the authentication information provided by the user. """ _PROJECTS_URL = '/auth/projects' _DOMAINS_URL = '/auth/domains' def projects(self): """List projects that the specified token can be rescoped to. :returns: a list of projects. :rtype: list of :class:`keystoneclient.v3.projects.Project` """ try: return self._list(self._PROJECTS_URL, 'projects', obj_class=Project) except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} return self._list(self._PROJECTS_URL, 'projects', obj_class=Project, endpoint_filter=endpoint_filter) def domains(self): """List Domains that the specified token can be rescoped to. :returns: a list of domains. :rtype: list of :class:`keystoneclient.v3.domains.Domain`. """ try: return self._list(self._DOMAINS_URL, 'domains', obj_class=Domain) except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} return self._list(self._DOMAINS_URL, 'domains', obj_class=Domain, endpoint_filter=endpoint_filter) python-keystoneclient-4.0.0/keystoneclient/v3/domain_configs.py0000664000175000017500000001050213644407055025043 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class DomainConfig(base.Resource): """An object representing a domain config association. This resource object does not necessarily contain fixed attributes, as new attributes are added in the server, they are supported here directly. The currently supported configs are `identity` and `ldap`. """ pass class DomainConfigManager(base.Manager): """Manager class for manipulating domain config associations.""" resource_class = DomainConfig key = 'config' def build_url(self, domain): return '/domains/%s/config' % base.getid(domain) def create(self, domain, config): """Create a config for a domain. :param domain: the domain where the config is going to be applied. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :param dict config: a dictionary of domain configurations. Example of the ``config`` parameter:: { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://myldap.com:389/", "user_tree_dn": "ou=Users,dc=my_new_root,dc=org" } } :returns: the created domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) body = {self.key: config} return super(DomainConfigManager, self)._put( base_url, body=body, response_key=self.key) def get(self, domain): """Get a config for a domain. :param domain: the domain for which the config is defined. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :returns: the domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) return super(DomainConfigManager, self)._get(base_url, self.key) def update(self, domain, config): """Update a config for a domain. :param domain: the domain where the config is going to be updated. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :param dict config: a dictionary of domain configurations. Example of the ``config`` parameter:: { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://myldap.com:389/", "user_tree_dn": "ou=Users,dc=my_new_root,dc=org" } } :returns: the updated domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) body = {self.key: config} return super(DomainConfigManager, self)._patch( base_url, body=body, response_key=self.key) def delete(self, domain): """Delete a config for a domain. :param domain: the domain which the config will be deleted on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ base_url = self.build_url(domain) return super(DomainConfigManager, self)._delete(url=base_url) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for domain configs')) def list(self, **kwargs): raise exceptions.MethodNotImplemented( _('List not supported for domain configs')) python-keystoneclient-4.0.0/keystoneclient/v3/endpoints.py0000664000175000017500000001540113644407055024072 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ VALID_INTERFACES = ['public', 'admin', 'internal'] class Endpoint(base.Resource): """Represents an Identity endpoint. Attributes: * id: a uuid that identifies the endpoint * interface: 'public', 'admin' or 'internal' network interface * region: geographic location of the endpoint * service_id: service to which the endpoint belongs * url: fully qualified service endpoint * enabled: determines whether the endpoint appears in the service catalog """ pass class EndpointManager(base.CrudManager): """Manager class for manipulating Identity endpoints.""" resource_class = Endpoint collection_key = 'endpoints' key = 'endpoint' def _validate_interface(self, interface): if interface is not None and interface not in VALID_INTERFACES: msg = _('"interface" must be one of: %s') msg %= ', '.join(VALID_INTERFACES) raise exceptions.ValidationError(msg) def create(self, service, url, interface=None, region=None, enabled=True, **kwargs): """Create an endpoint. :param service: the service to which the endpoint belongs. :type service: str or :class:`keystoneclient.v3.services.Service` :param str url: the URL of the fully qualified service endpoint. :param str interface: the network interface of the endpoint. Valid values are: ``public``, ``admin`` or ``internal``. :param region: the region to which the endpoint belongs. :type region: str or :class:`keystoneclient.v3.regions.Region` :param bool enabled: whether the endpoint is enabled or not, determining if it appears in the service catalog. :param kwargs: any other attribute provided will be passed to the server. :returns: the created endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ self._validate_interface(interface) return super(EndpointManager, self).create( service_id=base.getid(service), interface=interface, url=url, region=region, enabled=enabled, **kwargs) def get(self, endpoint): """Retrieve an endpoint. :param endpoint: the endpoint to be retrieved from the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :returns: the specified endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ return super(EndpointManager, self).get( endpoint_id=base.getid(endpoint)) def list(self, service=None, interface=None, region=None, enabled=None, region_id=None, **kwargs): """List endpoints. :param service: the service of the endpoints to be filtered on. :type service: str or :class:`keystoneclient.v3.services.Service` :param str interface: the network interface of the endpoints to be filtered on. Valid values are: ``public``, ``admin`` or ``internal``. :param bool enabled: whether to return enabled or disabled endpoints. :param str region_id: filter endpoints by the region_id attribute. If both region and region_id are specified, region takes precedence. :param kwargs: any other attribute provided will filter endpoints on. :returns: a list of endpoints. :rtype: list of :class:`keystoneclient.v3.endpoints.Endpoint` """ # NOTE(lhcheng): region filter is not supported by keystone, # region_id should be used instead. Consider removing the # region argument in the next release. self._validate_interface(interface) return super(EndpointManager, self).list( service_id=base.getid(service), interface=interface, region_id=region_id or base.getid(region), enabled=enabled, **kwargs) def update(self, endpoint, service=None, url=None, interface=None, region=None, enabled=None, **kwargs): """Update an endpoint. :param endpoint: the endpoint to be updated on the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :param service: the new service to which the endpoint belongs. :type service: str or :class:`keystoneclient.v3.services.Service` :param str url: the new URL of the fully qualified service endpoint. :param str interface: the new network interface of the endpoint. Valid values are: ``public``, ``admin`` or ``internal``. :param region: the new region to which the endpoint belongs. :type region: str or :class:`keystoneclient.v3.regions.Region` :param bool enabled: determining if the endpoint appears in the service catalog by enabling or disabling it. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ self._validate_interface(interface) return super(EndpointManager, self).update( endpoint_id=base.getid(endpoint), service_id=base.getid(service), interface=interface, url=url, region=region, enabled=enabled, **kwargs) def delete(self, endpoint): """Delete an endpoint. :param endpoint: the endpoint to be deleted on the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(EndpointManager, self).delete( endpoint_id=base.getid(endpoint)) python-keystoneclient-4.0.0/keystoneclient/v3/roles.py0000664000175000017500000006036413644407055023223 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from debtcollector import removals from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class Role(base.Resource): """Represents an Identity role. Attributes: * id: a uuid that identifies the role * name: user-facing identifier * domain: optional domain for the role """ pass class InferenceRule(base.Resource): """Represents a rule that states one role implies another. Attributes: * prior_role: this role implies the other * implied_role: this role is implied by the other """ pass class RoleManager(base.CrudManager): """Manager class for manipulating Identity roles.""" resource_class = Role collection_key = 'roles' key = 'role' deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager' def _role_grants_base_url(self, user, group, system, domain, project, use_inherit_extension): # When called, we have already checked that only one of user & group # and one of domain & project have been specified params = {} if project: params['project_id'] = base.getid(project) base_url = '/projects/%(project_id)s' elif domain: params['domain_id'] = base.getid(domain) base_url = '/domains/%(domain_id)s' elif system: if system == 'all': base_url = '/system' else: # NOTE(lbragstad): If we've made it this far, a user is # attempting to do something with system scope that isn't # supported yet (e.g. 'all' is currently the only supported # system scope). In the future that may change but until then # we should fail like we would if a user provided a bogus # project name or domain ID. msg = _("Only a system scope of 'all' is currently supported") raise exceptions.ValidationError(msg) if use_inherit_extension: base_url = '/OS-INHERIT' + base_url if user: params['user_id'] = base.getid(user) base_url += '/users/%(user_id)s' elif group: params['group_id'] = base.getid(group) base_url += '/groups/%(group_id)s' return base_url % params def _enforce_mutually_exclusive_group(self, system, domain, project): if not system: if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) elif not (domain or project): msg = _('Must specify either system, domain, or project') raise exceptions.ValidationError(msg) elif system: if domain and project: msg = _( 'Specify either system, domain, or project, not all three.' ) raise exceptions.ValidationError(msg) if domain: msg = _('Specify either system or a domain, not both') raise exceptions.ValidationError(msg) if project: msg = _('Specify either a system or project, not both') raise exceptions.ValidationError(msg) def _require_user_xor_group(self, user, group): if user and group: msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) elif not (user or group): msg = _('Must specify either a user or group') raise exceptions.ValidationError(msg) def create(self, name, domain=None, **kwargs): """Create a role. :param str name: the name of the role. :param domain: the domain of the role. If a value is passed it is a domain-scoped role, otherwise it's a global role. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param kwargs: any other attribute provided will be passed to the server. :returns: the created role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ domain_id = None if domain: domain_id = base.getid(domain) return super(RoleManager, self).create( name=name, domain_id=domain_id, **kwargs) def get(self, role): """Retrieve a role. :param role: the role to be retrieved from the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ return super(RoleManager, self).get(role_id=base.getid(role)) def list(self, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """List roles and role grants. :param user: filter in role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: filter in role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param domain: filter in role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: filter in role grants on the specified project. Either user or group must be specified. Project, domain and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will filter roles on. :returns: a list of roles. :rtype: list of :class:`keystoneclient.v3.roles.Role` """ if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' if user or group: self._require_user_xor_group(user, group) self._enforce_mutually_exclusive_group(system, domain, project) base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited ) return super(RoleManager, self).list(base_url=base_url, **kwargs) return super(RoleManager, self).list(**kwargs) def update(self, role, name=None, **kwargs): """Update a role. :param role: the role to be updated on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :param str name: the new name of the role. :param kwargs: any other attribute provided will be passed to server. :returns: the updated role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ return super(RoleManager, self).update( role_id=base.getid(role), name=name, **kwargs) def delete(self, role): """Delete a role. When a role is deleted all the role inferences that have deleted role as prior role will be deleted as well. :param role: the role to be deleted on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(RoleManager, self).delete( role_id=base.getid(role)) def grant(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Grant a role to a user or group on a domain or project. :param role: the role to be granted on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :param user: the specified user to have the role granted on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the specified group to have the role granted on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: system information to grant the role on. Project, domain, and system are mutually exclusive. :type system: str :param domain: the domain in which the role will be granted. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the project in which the role will be granted. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the granted role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).put(base_url=base_url, role_id=base.getid(role), **kwargs) def check(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Check if a user or group has a role on a domain or project. :param user: check for role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: check for role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: check for role grants on the system. Project, domain, and system are mutually exclusive. :type system: str :param domain: check for role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: check for role grants on the specified project. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the specified role returned from server if it exists. :rtype: :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status if specified role doesn't exist. :rtype: :class:`requests.models.Response` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).head( base_url=base_url, role_id=base.getid(role), os_inherit_extension_inherited=os_inherit_extension_inherited, **kwargs) def revoke(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Revoke a role from a user or group on a domain or project. :param user: revoke role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: revoke role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: revoke role grants on the system. Project, domain, and system are mutually exclusive. :type system: str :param domain: revoke role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: revoke role grants on the specified project. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the revoked role returned from server. :rtype: list of :class:`keystoneclient.v3.roles.Role` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).delete( base_url=base_url, role_id=base.getid(role), os_inherit_extension_inherited=os_inherit_extension_inherited, **kwargs) @removals.remove(message='Use %s.create instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def create_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).create(prior_role, implied_role) @removals.remove(message='Use %s.delete instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def delete_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).delete(prior_role, implied_role) @removals.remove(message='Use %s.get instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def get_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).get(prior_role, implied_role) @removals.remove(message='Use %s.check instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def check_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).check(prior_role, implied_role) @removals.remove(message='Use %s.list_inference_roles' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def list_role_inferences(self, **kwargs): return InferenceRuleManager(self.client).list_inference_roles() class InferenceRuleManager(base.CrudManager): """Manager class for manipulating Identity inference rules.""" resource_class = InferenceRule collection_key = 'role_inferences' key = 'role_inference' def _implied_role_url_tail(self, prior_role, implied_role): base_url = ('/%(prior_role_id)s/implies/%(implied_role_id)s' % {'prior_role_id': base.getid(prior_role), 'implied_role_id': base.getid(implied_role)}) return base_url def create(self, prior_role, implied_role): """Create an inference rule. An inference rule is comprised of two roles, a prior role and an implied role. The prior role will imply the implied role. Valid HTTP return codes: * 201: Resource is created successfully * 404: A role cannot be found * 409: The inference rule already exists :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: a newly created role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) _resp, body = self.client.put("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def delete(self, prior_role, implied_role): """Delete an inference rule. When deleting an inference rule, both roles are required. Note that neither role is deleted, only the inference relationship is dissolved. Valid HTTP return codes: * 204: Delete request is accepted * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) return self._delete("/roles" + url_tail) def get(self, prior_role, implied_role): """Retrieve an inference rule. Valid HTTP return codes: * 200: Inference rule is returned * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) _resp, body = self.client.get("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def list(self, prior_role): """List all roles that a role may imply. Valid HTTP return codes: * 200: List of inference rules are returned * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = ('/%s/implies' % base.getid(prior_role)) _resp, body = self.client.get("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def check(self, prior_role, implied_role): """Check if an inference rule exists. Valid HTTP return codes: * 204: The rule inference exists * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: response object with 204 status returned from server. :rtype: :class:`requests.models.Response` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) return self._head("/roles" + url_tail) def list_inference_roles(self): """List all rule inferences. Valid HTTP return codes: * 200: All inference rules are returned :param kwargs: attributes provided will be passed to the server. :returns: a list of inference rules. :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule` """ return super(InferenceRuleManager, self).list() def update(self, **kwargs): raise exceptions.MethodNotImplemented( _('Update not supported for rule inferences')) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for rule inferences')) def put(self, **kwargs): raise exceptions.MethodNotImplemented( _('Put not supported for rule inferences')) python-keystoneclient-4.0.0/keystoneclient/v3/application_credentials.py0000664000175000017500000001450613644407055026754 0ustar zuulzuul00000000000000# Copyright 2018 SUSE Linux GmbH # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import utils class ApplicationCredential(base.Resource): """Represents an Identity application credential. Attributes: * id: a uuid that identifies the application credential * user: the user who owns the application credential * name: application credential name * secret: application credential secret * description: application credential description * expires_at: expiry time * roles: role assignments on the project * unrestricted: whether the application credential has restrictions applied * access_rules: a list of access rules defining what API requests the application credential may be used for """ pass class ApplicationCredentialManager(base.CrudManager): """Manager class for manipulating Identity application credentials.""" resource_class = ApplicationCredential collection_key = 'application_credentials' key = 'application_credential' def create(self, name, user=None, secret=None, description=None, expires_at=None, roles=None, unrestricted=False, access_rules=None, **kwargs): """Create a credential. :param string name: application credential name :param string user: User ID :param secret: application credential secret :param description: application credential description :param datetime.datetime expires_at: expiry time :param List roles: list of roles on the project. Maybe a list of IDs or a list of dicts specifying role name and domain :param bool unrestricted: whether the application credential has restrictions applied :param List access_rules: a list of dicts representing access rules :returns: the created application credential :rtype: :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} # Convert roles list into list-of-dict API format role_list = [] if roles: if not isinstance(roles, list): roles = [roles] for role in roles: if isinstance(role, six.string_types): role_list.extend([{'id': role}]) elif isinstance(role, dict): role_list.extend([role]) else: msg = (_("Roles must be a list of IDs or role dicts.")) raise exceptions.CommandError(msg) if not role_list: role_list = None # Convert datetime.datetime expires_at to iso format string if expires_at: expires_str = utils.isotime(at=expires_at, subsecond=True) else: expires_str = None return super(ApplicationCredentialManager, self).create( name=name, secret=secret, description=description, expires_at=expires_str, roles=role_list, unrestricted=unrestricted, access_rules=access_rules, **kwargs) def get(self, application_credential, user=None): """Retrieve an application credential. :param application_credential: the credential to be retrieved from the server :type applicationcredential: str or :class:`keystoneclient.v3.application_credentials.ApplicationCredential` :returns: the specified application credential :rtype: :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).get( application_credential_id=base.getid(application_credential)) def list(self, user=None, **kwargs): """List application credentials. :param string user: User ID :returns: a list of application credentials :rtype: list of :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).list(**kwargs) def find(self, user=None, **kwargs): """Find an application credential with attributes matching ``**kwargs``. :param string user: User ID :returns: a list of matching application credentials :rtype: list of :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).find(**kwargs) def delete(self, application_credential, user=None): """Delete an application credential. :param application_credential: the application credential to be deleted :type credential: str or :class:`keystoneclient.v3.application_credentials.ApplicationCredential` :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).delete( application_credential_id=base.getid(application_credential)) def update(self): raise exceptions.MethodNotImplemented( _('Application credentials are immutable, updating is not' ' supported.')) python-keystoneclient-4.0.0/keystoneclient/v3/credentials.py0000664000175000017500000001211513644407055024363 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Credential(base.Resource): """Represents an Identity credential. Attributes: * id: a uuid that identifies the credential * user_id: user ID to which credential belongs * type: the type of credential * blob: the text that represents the credential * project_id: project ID which limits the scope of the credential """ pass class CredentialManager(base.CrudManager): """Manager class for manipulating Identity credentials.""" resource_class = Credential collection_key = 'credentials' key = 'credential' def create(self, user, type, blob, project=None, **kwargs): """Create a credential. :param user: the user to which the credential belongs :type user: str or :class:`keystoneclient.v3.users.User` :param str type: the type of the credential, valid values are: ``ec2``, ``cert`` or ``totp`` :param str blob: the arbitrary blob of the credential data, to be parsed according to the type :param project: the project which limits the scope of the credential, this attribbute is mandatory if the credential type is ec2 :type project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server :returns: the created credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).create( user_id=base.getid(user), type=type, blob=blob, project_id=base.getid(project), **kwargs) def get(self, credential): """Retrieve a credential. :param credential: the credential to be retrieved from the server :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :returns: the specified credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).get( credential_id=base.getid(credential)) def list(self, **kwargs): """List credentials. :param kwargs: If user_id or type is specified then credentials will be filtered accordingly. :returns: a list of credentials :rtype: list of :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).list(**kwargs) def update(self, credential, user, type=None, blob=None, project=None, **kwargs): """Update a credential. :param credential: the credential to be updated on the server :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :param user: the new user to which the credential belongs :type user: str or :class:`keystoneclient.v3.users.User` :param str type: the new type of the credential, valid values are: ``ec2``, ``cert`` or ``totp`` :param str blob: the new blob of the credential data and may be removed in the future release. :param project: the new project which limits the scope of the credential, this attribute is mandatory if the credential type is ec2 :type project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server :returns: the updated credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).update( credential_id=base.getid(credential), user_id=base.getid(user), type=type, blob=blob, project_id=base.getid(project), **kwargs) def delete(self, credential): """Delete a credential. :param credential: the credential to be deleted :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ return super(CredentialManager, self).delete( credential_id=base.getid(credential)) python-keystoneclient-4.0.0/keystoneclient/v3/limits.py0000664000175000017500000001275413644407055023400 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class Limit(base.Resource): """Represents a project limit. Attributes: * id: a UUID that identifies the project limit * service_id: a UUID that identifies the service for the limit * region_id: a UUID that identifies the region for the limit * project_id: a UUID that identifies the project for the limit * resource_name: the name of the resource to limit * resource_limit: the limit to apply to the project * description: a description for the project limit """ pass class LimitManager(base.CrudManager): """Manager class for project limits.""" resource_class = Limit collection_key = 'limits' key = 'limit' def create(self, project, service, resource_name, resource_limit, description=None, region=None, **kwargs): """Create a project-specific limit. :param project: the project to create a limit for. :type project: str or :class:`keystoneclient.v3.projects.Project` :param service: the service that owns the resource to limit. :type service: str or :class:`keystoneclient.v3.services.Service` :param resource_name: the name of the resource to limit :type resource_name: str :param resource_limit: the quantity of the limit :type resource_limit: int :param description: a description of the limit :type description: str :param region: region the limit applies to :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: a reference of the created limit :rtype: :class:`keystoneclient.v3.limits.Limit` """ limit_data = base.filter_none( project_id=base.getid(project), service_id=base.getid(service), resource_name=resource_name, resource_limit=resource_limit, description=description, region_id=base.getid(region), **kwargs ) body = {self.collection_key: [limit_data]} resp, body = self.client.post('/limits', body=body) limit = body[self.collection_key].pop() return self._prepare_return_value(resp, self.resource_class( self, limit)) def update(self, limit, project=None, service=None, resource_name=None, resource_limit=None, description=None, **kwargs): """Update a project-specific limit. :param limit: a limit to update :param project: the project ID of the limit to update :type project: str or :class:`keystoneclient.v3.projects.Project` :param resource_limit: the limit of the limit's resource to update :type: resource_limit: int :param description: a description of the limit :type description: str :returns: a reference of the updated limit. :rtype: :class:`keystoneclient.v3.limits.Limit` """ return super(LimitManager, self).update( limit_id=base.getid(limit), project_id=base.getid(project), service_id=base.getid(service), resource_name=resource_name, resource_limit=resource_limit, description=description, **kwargs ) def get(self, limit): """Retrieve a project limit. :param limit: the project-specific limit to be retrieved. :type limit: str or :class:`keystoneclient.v3.limit.Limit` :returns: a project-specific limit :rtype: :class:`keystoneclient.v3.limit.Limit` """ return super(LimitManager, self).get(limit_id=base.getid(limit)) def list(self, service=None, region=None, resource_name=None, **kwargs): """List project-specific limits. Any parameter provided will be passed to the server as a filter :param service: service to filter limits by :type service: UUID or :class:`keystoneclient.v3.services.Service` :param region: region to filter limits by :type region: UUID or :class:`keystoneclient.v3.regions.Region` :param resource_name: the name of the resource to filter limits by :type resource_name: str :returns: a list of project-specific limits. :rtype: list of :class:`keystoneclient.v3.limits.Limit` """ return super(LimitManager, self).list( service_id=base.getid(service), region_id=base.getid(region), resource_name=resource_name, **kwargs ) def delete(self, limit): """Delete a project-specific limit. :param limit: the project-specific limit to be deleted. :type limit: str or :class:`keystoneclient.v3.limit.Limit` :returns: Response object with 204 status :rtype: :class:`requests.models.Response` """ return super(LimitManager, self).delete(limit_id=base.getid(limit)) python-keystoneclient-4.0.0/keystoneclient/v3/access_rules.py0000664000175000017500000000745713644407055024556 0ustar zuulzuul00000000000000# Copyright 2019 SUSE LLC # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class AccessRule(base.Resource): """Represents an Identity access rule for application credentials. Attributes: * id: a uuid that identifies the access rule * method: The request method that the application credential is permitted to use for a given API endpoint * path: The API path that the application credential is permitted to access * service: The service type identifier for the service that the application credential is permitted to access """ pass class AccessRuleManager(base.CrudManager): """Manager class for manipulating Identity access rules.""" resource_class = AccessRule collection_key = 'access_rules' key = 'access_rule' def get(self, access_rule, user=None): """Retrieve an access rule. :param access_rule: the access rule to be retrieved from the server :type access_rule: str or :class:`keystoneclient.v3.access_rules.AccessRule` :param string user: User ID :returns: the specified access rule :rtype: :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).get( access_rule_id=base.getid(access_rule)) def list(self, user=None, **kwargs): """List access rules. :param string user: User ID :returns: a list of access rules :rtype: list of :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).list(**kwargs) def find(self, user=None, **kwargs): """Find an access rule with attributes matching ``**kwargs``. :param string user: User ID :returns: a list of matching access rules :rtype: list of :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).find(**kwargs) def delete(self, access_rule, user=None): """Delete an access rule. :param access_rule: the access rule to be deleted :type access_rule: str or :class:`keystoneclient.v3.access_rules.AccessRule` :param string user: User ID :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).delete( access_rule_id=base.getid(access_rule)) def update(self): raise exceptions.MethodNotImplemented( _('Access rules are immutable, updating is not' ' supported.')) def create(self): raise exceptions.MethodNotImplemented( _('Access rules can only be created as attributes of application ' 'credentials.')) python-keystoneclient-4.0.0/keystoneclient/v3/domains.py0000664000175000017500000000775313644407055023534 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Domain(base.Resource): """Represents an Identity domain. Attributes: * id: a uuid that identifies the domain * name: the name of the domain * description: a description of the domain * enabled: determines whether the domain is enabled """ pass class DomainManager(base.CrudManager): """Manager class for manipulating Identity domains.""" resource_class = Domain collection_key = 'domains' key = 'domain' def create(self, name, description=None, enabled=True, **kwargs): """Create a domain. :param str name: the name of the domain. :param str description: a description of the domain. :param bool enabled: whether the domain is enabled. :param kwargs: any other attribute provided will be passed to the server. :returns: the created domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).create( name=name, description=description, enabled=enabled, **kwargs) def get(self, domain): """Retrieve a domain. :param domain: the domain to be retrieved from the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: the specified domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).get( domain_id=base.getid(domain)) def list(self, **kwargs): """List domains. :param kwargs: allows filter criteria to be passed where supported by the server. :returns: a list of domains. :rtype: list of :class:`keystoneclient.v3.domains.Domain`. """ # Ref bug #1267530 we have to pass 0 for False to get the expected # results on all keystone versions if kwargs.get('enabled') is False: kwargs['enabled'] = 0 return super(DomainManager, self).list(**kwargs) def update(self, domain, name=None, description=None, enabled=None, **kwargs): """Update a domain. :param domain: the domain to be updated on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str name: the new name of the domain. :param str description: the new description of the domain. :param bool enabled: whether the domain is enabled. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).update( domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def delete(self, domain): """"Delete a domain. :param domain: the domain to be deleted on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(DomainManager, self).delete( domain_id=base.getid(domain)) python-keystoneclient-4.0.0/keystoneclient/__init__.py0000664000175000017500000000450713644407055023303 0ustar zuulzuul00000000000000# Copyright 2012 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. """The python bindings for the OpenStack Identity (Keystone) project. A Client object will allow you to communicate with the Identity server. The recommended way to get a Client object is to use :py:func:`keystoneclient.client.Client()`. :py:func:`~.Client()` uses version discovery to create a V3 or V2 client depending on what versions the Identity server supports and what version is requested. Identity V2 and V3 clients can also be created directly. See :py:class:`keystoneclient.v3.client.Client` for the V3 client and :py:class:`keystoneclient.v2_0.client.Client` for the V2 client. """ import importlib import sys import pbr.version __version__ = pbr.version.VersionInfo('python-keystoneclient').version_string() __all__ = ( # Modules 'generic', 'v2_0', 'v3', # Packages 'access', 'client', 'exceptions', 'httpclient', 'service_catalog', ) class _LazyImporter(object): def __init__(self, module): self._module = module def __getattr__(self, name): # NB: this is only called until the import has been done. # These submodules are part of the API without explicit importing, but # expensive to load, so we load them on-demand rather than up-front. lazy_submodules = [ 'access', 'client', 'exceptions', 'generic', 'httpclient', 'service_catalog', 'v2_0', 'v3', ] if name in lazy_submodules: return importlib.import_module('keystoneclient.%s' % name) # Return module attributes like __all__ etc. return getattr(self._module, name) sys.modules[__name__] = _LazyImporter(sys.modules[__name__]) python-keystoneclient-4.0.0/keystoneclient/client.py0000664000175000017500000000555413644407055023025 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from keystoneclient import discover from keystoneclient import httpclient from keystoneclient import session as client_session @removals.remove(message='Use keystoneclient.httpclient.HTTPClient instead', version='1.7.0', removal_version='2.0.0') class HTTPClient(httpclient.HTTPClient): """Deprecated alias for httpclient.HTTPClient. This class is deprecated as of the 1.7.0 release in favor of :class:`keystoneclient.httpclient.HTTPClient` and may be removed in the 2.0.0 release. """ def Client(version=None, unstable=False, session=None, **kwargs): """Factory function to create a new identity service client. The returned client will be either a V3 or V2 client. Check the version using the :py:attr:`~keystoneclient.v3.client.Client.version` property or the instance's class (with instanceof). :param tuple version: The required version of the identity API. If specified the client will be selected such that the major version is equivalent and an endpoint provides at least the specified minor version. For example to specify the 3.1 API use ``(3, 1)``. (optional) :param bool unstable: Accept endpoints not marked as 'stable'. (optional) :param session: A session object to be used for communication. If one is not provided it will be constructed from the provided kwargs. (optional) :type session: keystoneclient.session.Session :param kwargs: Additional arguments are passed through to the client that is being created. :returns: New keystone client object. :rtype: :py:class:`keystoneclient.v3.client.Client` or :py:class:`keystoneclient.v2_0.client.Client` :raises keystoneclient.exceptions.DiscoveryFailure: if the server's response is invalid. :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client cannot be found. """ if not session: session = client_session.Session._construct(kwargs) d = discover.Discover(session=session, **kwargs) return d.create_client(version=version, unstable=unstable) python-keystoneclient-4.0.0/keystoneclient/fixture/0000775000175000017500000000000013644407143022650 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/fixture/exception.py0000664000175000017500000000141113644407055025217 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.fixture import exception FixtureValidationError = exception.FixtureValidationError """The token you created is not legitimate. An alias of :py:exc:`keystoneauth1.fixture.exception.FixtureValidationError`` """ python-keystoneclient-4.0.0/keystoneclient/fixture/__init__.py0000664000175000017500000000351613644407055024770 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 testing. The generators in this directory produce keystone compliant structures for use in testing. They should be considered 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 (keystoneclient or other). Because of this there may be dependencies from this module on libraries that are only available in testing. .. warning:: The keystoneclient.fixture package is deprecated in favor of keystoneauth1.fixture and will not be supported. """ # flake8: noqa: F405 import warnings from keystoneclient.fixture.discovery import * # noqa from keystoneclient.fixture import exception from keystoneclient.fixture import v2 from keystoneclient.fixture import v3 warnings.warn( "The keystoneclient.fixture package is deprecated in favor of " "keystoneauth1.fixture and will not be supported.", DeprecationWarning) FixtureValidationError = exception.FixtureValidationError V2Token = v2.Token V3Token = v3.Token V3FederationToken = v3.V3FederationToken __all__ = ('DiscoveryList', 'FixtureValidationError', 'V2Discovery', 'V3Discovery', 'V2Token', 'V3Token', 'V3FederationToken', ) python-keystoneclient-4.0.0/keystoneclient/fixture/v3.py0000664000175000017500000000157113644407055023560 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.fixture import v3 Token = v3.Token """A V3 Keystone token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v3.Token` """ V3FederationToken = v3.V3FederationToken """A V3 Keystone Federation token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v3.V3FederationToken` """ python-keystoneclient-4.0.0/keystoneclient/fixture/v2.py0000664000175000017500000000131113644407055023547 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.fixture import v2 Token = v2.Token """A V2 Keystone token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v2.Token` """ python-keystoneclient-4.0.0/keystoneclient/fixture/discovery.py0000664000175000017500000000221113644407055025227 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.fixture import discovery __all__ = ('DiscoveryList', 'V2Discovery', 'V3Discovery', ) V2Discovery = discovery.V2Discovery """A Version element for a V2 identity service endpoint. An alias of :py:exc:`keystoneauth1.fixture.discovery.V2Discovery` """ V3Discovery = discovery.V3Discovery """A Version element for a V3 identity service endpoint. An alias of :py:exc:`keystoneauth1.fixture.discovery.V3Discovery` """ DiscoveryList = discovery.DiscoveryList """A List of version elements. An alias of :py:exc:`keystoneauth1.fixture.discovery.DiscoveryList` """ python-keystoneclient-4.0.0/keystoneclient/base.py0000664000175000017500000005263213644407055022460 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # 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. """Base utilities to build API operation managers and objects on top of.""" import abc import copy import functools import warnings from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import plugin from oslo_utils import strutils import six from six.moves import urllib from keystoneclient import exceptions as ksc_exceptions from keystoneclient.i18n import _ class Response(object): def __init__(self, http_response, data): self.request_ids = [] if isinstance(http_response, list): # http_response is a list of in case # of pagination for resp_obj in http_response: # Extract 'x-openstack-request-id' from headers self.request_ids.append(resp_obj.headers.get( 'x-openstack-request-id')) else: self.request_ids.append(http_response.headers.get( 'x-openstack-request-id')) self.data = data def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ if getattr(obj, 'uuid', None): return obj.uuid else: return getattr(obj, 'id', obj) def filter_none(**kwargs): """Remove any entries from a dictionary where the value is None.""" return dict((k, v) for k, v in kwargs.items() if v is not None) def filter_kwargs(f): @functools.wraps(f) def func(*args, **kwargs): new_kwargs = {} for key, ref in kwargs.items(): if ref is None: # drop null values continue id_value = getid(ref) if id_value != ref: # If an object with an id was passed, then use the id, e.g.: # user: user(id=1) becomes user_id: 1 key = '%s_id' % key new_kwargs[key] = id_value return f(*args, **new_kwargs) return func class Manager(object): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. :param client: instance of BaseClient descendant for HTTP requests """ resource_class = None def __init__(self, client): super(Manager, self).__init__() self.client = client @property def api(self): """The client. .. warning:: This property is deprecated as of the 1.7.0 release in favor of :meth:`client` and may be removed in the 2.0.0 release. """ warnings.warn( 'api is deprecated as of the 1.7.0 release in favor of client and ' 'may be removed in the 2.0.0 release', DeprecationWarning) return self.client def _prepare_return_value(self, http_response, data): if self.client.include_metadata: return Response(http_response, data) return data def _list(self, url, response_key, obj_class=None, body=None, **kwargs): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param kwargs: Additional arguments will be passed to the request. """ if body: resp, body = self.client.post(url, body=body, **kwargs) else: resp, body = self.client.get(url, **kwargs) if obj_class is None: obj_class = self.resource_class data = body[response_key] # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): # nosec(cjschaef): keystone data values # not as expected (see comment above), assumption is that values # are already returned in a list (so simply utilize that list) pass return self._prepare_return_value( resp, [obj_class(self, res, loaded=True) for res in data if res]) def _get(self, url, response_key, **kwargs): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.get(url, **kwargs) return self._prepare_return_value( resp, self.resource_class(self, body[response_key], loaded=True)) def _head(self, url, **kwargs): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.head(url, **kwargs) return self._prepare_return_value(resp, resp.status_code == 204) def _post(self, url, body, response_key, return_raw=False, **kwargs): """Create an object. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.post(url, body=body, **kwargs) if return_raw: return body[response_key] return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) def _put(self, url, body=None, response_key=None, **kwargs): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.put(url, body=body, **kwargs) # PUT requests may not return a body if body is not None: if response_key is not None: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value( resp, self.resource_class(self, body)) # In some cases (e.g. 'add_endpoint_to_project' from endpoint_filters # resource), PUT request may not return a body so return None as # response along with request_id if include_metadata is True. return self._prepare_return_value(resp, body) def _patch(self, url, body=None, response_key=None, **kwargs): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.patch(url, body=body, **kwargs) if response_key is not None: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value( resp, self.resource_class(self, body)) def _delete(self, url, **kwargs): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.delete(url, **kwargs) return resp, self._prepare_return_value(resp, body) def _update(self, url, body=None, response_key=None, method="PUT", **kwargs): methods = {"PUT": self.client.put, "POST": self.client.post, "PATCH": self.client.patch} try: resp, body = methods[method](url, body=body, **kwargs) except KeyError: raise ksc_exceptions.ClientException(_("Invalid update method: %s") % method) # PUT requests may not return a body if body: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value(resp, body) @six.add_metaclass(abc.ABCMeta) class ManagerWithFind(Manager): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass # pragma: no cover def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ rl = self.findall(**kwargs) if self.client.include_metadata: base_response = rl rl = rl.data base_response.data = rl[0] if len(rl) == 0: msg = _("No %(name)s matching %(kwargs)s.") % { 'name': self.resource_class.__name__, 'kwargs': kwargs} raise ksa_exceptions.NotFound(404, msg) elif len(rl) > 1: raise ksc_exceptions.NoUniqueMatch else: return base_response if self.client.include_metadata else rl[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() def _extract_data(objs, response_data): for obj in objs: try: if all(getattr(obj, attr) == value for (attr, value) in searches): response_data.append(obj) except AttributeError: continue return response_data objs = self.list() if self.client.include_metadata: # 'objs' is the object of 'Response' class. objs.data = _extract_data(objs.data, found) return objs return _extract_data(objs, found) class CrudManager(Manager): """Base manager class for manipulating Keystone entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None base_url = None def build_url(self, dict_args_in_out=None): """Build a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} If a `base_url` is provided, the generated URL will be appended to it. If a 'tail' is provided, it will be appended to the end of the URL. """ if dict_args_in_out is None: dict_args_in_out = {} url = dict_args_in_out.pop('base_url', None) or self.base_url or '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = dict_args_in_out.pop('%s_id' % self.key, None) if entity_id is not None: url += '/%s' % entity_id if dict_args_in_out.get('tail'): url += dict_args_in_out['tail'] return url @filter_kwargs def create(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) return self._post( url, {self.key: kwargs}, self.key) @filter_kwargs def get(self, **kwargs): return self._get( self.build_url(dict_args_in_out=kwargs), self.key) @filter_kwargs def head(self, **kwargs): return self._head(self.build_url(dict_args_in_out=kwargs)) def _build_query(self, params): if params is None: return '' else: # NOTE(spilla) Since the manager cannot take in a hyphen as a # key in the kwarg, it is passed in with a _. This needs to be # replaced with a proper hyphen for the URL to work properly. tags_params = ('tags_any', 'not_tags', 'not_tags_any') for tag_param in tags_params: if tag_param in params: params[tag_param.replace('_', '-')] = params.pop(tag_param) return '?%s' % urllib.parse.urlencode(params, doseq=True) def build_key_only_query(self, params_list): """Build a query that does not include values, just keys. The Identity API has some calls that define queries without values, this can not be accomplished by using urllib.parse.urlencode(). This method builds a query using only the keys. """ return '?%s' % '&'.join(params_list) if params_list else '' @filter_kwargs def list(self, fallback_to_auth=False, **kwargs): def return_resp(resp, include_metadata=False): base_response = None list_data = resp if include_metadata: base_response = resp list_data = resp.data base_response.data = list_data return base_response if include_metadata else list_data if 'id' in kwargs.keys(): # Ensure that users are not trying to call things like # ``domains.list(id='default')`` when they should have used # ``[domains.get(domain_id='default')]`` instead. Keystone supports # ``GET /v3/domains/{domain_id}``, not ``GET # /v3/domains?id={domain_id}``. raise TypeError( _("list() got an unexpected keyword argument 'id'. To " "retrieve a single object using a globally unique " "identifier, try using get() instead.")) url = self.build_url(dict_args_in_out=kwargs) try: query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % {'url': url, 'query': query} list_resp = self._list(url_query, self.collection_key) return return_resp(list_resp, include_metadata=self.client.include_metadata) except ksa_exceptions.EmptyCatalog: if fallback_to_auth: list_resp = self._list(url_query, self.collection_key, endpoint_filter={ 'interface': plugin.AUTH_INTERFACE}) return return_resp( list_resp, include_metadata=self.client.include_metadata) else: raise @filter_kwargs def put(self, **kwargs): return self._update( self.build_url(dict_args_in_out=kwargs), method='PUT') @filter_kwargs def update(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) return self._update( url, {self.key: kwargs}, self.key, method='PATCH') @filter_kwargs def delete(self, **kwargs): return self._delete( self.build_url(dict_args_in_out=kwargs)) @filter_kwargs def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``.""" url = self.build_url(dict_args_in_out=kwargs) query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % { 'url': url, 'query': query } elements = self._list( url_query, self.collection_key) if self.client.include_metadata: base_response = elements elements = elements.data base_response.data = elements[0] if not elements: msg = _("No %(name)s matching %(kwargs)s.") % { 'name': self.resource_class.__name__, 'kwargs': kwargs} raise ksa_exceptions.NotFound(404, msg) elif len(elements) > 1: raise ksc_exceptions.NoUniqueMatch else: return (base_response if self.client.include_metadata else elements[0]) class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): """Return string representation of resource attributes.""" reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def human_id(self): """Human-readable ID which can be used for bash completion.""" if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): try: try: setattr(self, k, v) except UnicodeEncodeError: # This happens when we're running with Python version that # does not support Unicode identifiers (e.g. Python 2.7). # In that case we can't help but not set this attrubute; # it'll be available in a dict representation though pass self._info[k] = v except AttributeError: # nosec(cjschaef): we already defined the # attribute on the class pass def __getattr__(self, k): """Checking attrbiute existence.""" if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) def __eq__(self, other): """Define equality for resources.""" if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False return self._info == other._info def __ne__(self, other): """Define inequality for resources.""" return not self == other def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) def delete(self): return self.manager.delete(self) python-keystoneclient-4.0.0/keystoneclient/access.py0000664000175000017500000006150413644407055023005 0ustar zuulzuul00000000000000# 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 datetime import warnings from oslo_utils import timeutils from keystoneclient.i18n import _ from keystoneclient import service_catalog # gap, in seconds, to determine whether the given token is about to expire STALE_TOKEN_DURATION = 30 class AccessInfo(dict): """Encapsulates a raw authentication token from keystone. Provides helper methods for extracting useful values from that token. """ @classmethod def factory(cls, resp=None, body=None, region_name=None, auth_token=None, **kwargs): """Factory function to create a new AccessInfo object. Create AccessInfo object given a successful auth response & body or a user-provided dict. .. warning:: Use of the region_name argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. """ if region_name: warnings.warn( 'Use of the region_name argument is deprecated as of the ' '1.7.0 release and may be removed in the 2.0.0 release.', DeprecationWarning) if body is not None or len(kwargs): if AccessInfoV3.is_valid(body, **kwargs): if resp and not auth_token: auth_token = resp.headers['X-Subject-Token'] # NOTE(jamielennox): these return AccessInfo because they # already have auth_token installed on them. if body: if region_name: body['token']['region_name'] = region_name return AccessInfoV3(auth_token, **body['token']) else: return AccessInfoV3(auth_token, **kwargs) elif AccessInfoV2.is_valid(body, **kwargs): if body: if region_name: body['access']['region_name'] = region_name auth_ref = AccessInfoV2(**body['access']) else: auth_ref = AccessInfoV2(**kwargs) else: raise NotImplementedError(_('Unrecognized auth response')) else: auth_ref = AccessInfoV2(**kwargs) if auth_token: auth_ref.auth_token = auth_token return auth_ref def __init__(self, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, region_name=self._region_name) @property def _region_name(self): return self.get('region_name') def will_expire_soon(self, stale_duration=None): """Determine if expiration is about to occur. :returns: true if expiration is within the given duration :rtype: boolean """ stale_duration = (STALE_TOKEN_DURATION if stale_duration is None else stale_duration) norm_expires = timeutils.normalize_time(self.expires) # (gyee) should we move auth_token.will_expire_soon() to timeutils # instead of duplicating code here? soon = (timeutils.utcnow() + datetime.timedelta( seconds=stale_duration)) return norm_expires < soon @classmethod def is_valid(cls, body, **kwargs): """Determine if processing valid v2 or v3 token. Validates from the auth body or a user-provided dict. :returns: true if auth body matches implementing class :rtype: boolean """ raise NotImplementedError() def has_service_catalog(self): """Return true if the authorization 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'] @auth_token.setter def auth_token(self, value): self['auth_token'] = value @auth_token.deleter def auth_token(self): try: del self['auth_token'] except KeyError: # nosec(cjschaef): 'auth_token' is not in the dict pass @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. For v2, it always returns 'default' which may be different from the Keystone configuration. :returns: str """ raise NotImplementedError() @property def user_domain_name(self): """Return the user's domain name associated with the auth request. For v2, it always returns 'Default' which may be different from the Keystone configuration. :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. Return true if scoped to a tenant(project) or domain, and contains a populated service catalog. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_scoped and may be removed in the 2.0.0 release. :returns: bool """ raise NotImplementedError() @property def project_scoped(self): """Return true if the auth token was scoped to a tenant(project). :returns: bool """ raise NotImplementedError() @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. For v2, it returns 'default' if a project is scoped or None which may be different from the keystone configuration. :returns: str """ raise NotImplementedError() @property def project_domain_name(self): """Return the project's domain name associated with the auth request. For v2, it returns 'Default' if a project is scoped or None which may be different from the keystone configuration. :returns: str """ raise NotImplementedError() @property def auth_url(self): """Return a tuple of identity URLs. The identity URLs are from publicURL and adminURL for the service 'identity' from the service catalog associated with the authorization request. If the authentication request wasn't scoped to a tenant (project), this property will return None. DEPRECATED: this doesn't correctly handle region name. You should fetch it from the service catalog yourself. This may be removed in the 2.0.0 release. :returns: tuple of urls """ raise NotImplementedError() @property def management_url(self): """Return the first adminURL of the identity endpoint. The identity endpoint is from the service catalog associated with the authorization request, or None if the authentication request wasn't scoped to a tenant (project). DEPRECATED: this doesn't correctly handle region name. You should fetch it from the service catalog yourself. This may be removed in the 2.0.0 release. :returns: tuple of urls """ raise NotImplementedError() @property def version(self): """Return the version of the auth token from identity service. :returns: str """ return self.get('version') @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 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 class AccessInfoV2(AccessInfo): """An object for encapsulating raw v2 auth token from identity service.""" def __init__(self, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.update(version='v2.0') self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, token=self['token']['id'], region_name=self._region_name) @classmethod def is_valid(cls, body, **kwargs): if body: return 'access' in body elif kwargs: return kwargs.get('version') == 'v2.0' else: return False def has_service_catalog(self): return 'serviceCatalog' in self @AccessInfo.auth_token.getter def auth_token(self): try: return super(AccessInfoV2, self).auth_token except KeyError: return self['token']['id'] @property def expires(self): return timeutils.parse_isotime(self['token']['expires']) @property def issued(self): return timeutils.parse_isotime(self['token']['issued_at']) @property def username(self): return self['user'].get('name', self['user'].get('username')) @property def user_id(self): return self['user']['id'] @property def user_domain_id(self): return 'default' @property def user_domain_name(self): return 'Default' @property def role_ids(self): return self.get('metadata', {}).get('roles', []) @property 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: # nosec(cjschaef): no 'token' key or 'tenant' key in # token, return the name of the tenant or None pass else: return tenant_dict.get('name') # pre grizzly try: return self['user']['tenantName'] except KeyError: # nosec(cjschaef): no 'user' key or 'tenantName' in # 'user', attempt 'tenantId' or return None pass # pre diablo, keystone only provided a tenantId try: return self['token']['tenantId'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenantName' or # 'tenantId' could be found, return None pass @property def scoped(self): """Deprecated as of the 1.7.0 release. Use project_scoped instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'scoped is deprecated as of the 1.7.0 release in favor of ' 'project_scoped and may be removed in the 2.0.0 release.', DeprecationWarning) if ('serviceCatalog' in self and self['serviceCatalog'] and 'tenant' in self['token']): return True return False @property def project_scoped(self): return 'tenant' in self['token'] @property def domain_scoped(self): return False @property def trust_id(self): return self.get('trust', {}).get('id') @property def trust_scoped(self): return 'trust' in self @property def trustee_user_id(self): return self.get('trust', {}).get('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: # nosec(cjschaef): no 'token' key or 'tenant' dict, # attempt to return 'tenantId' or return None pass else: return tenant_dict.get('id') # pre grizzly try: return self['user']['tenantId'] except KeyError: # nosec(cjschaef): no 'user' key or 'tenantId' in # 'user', attempt to retrieve from 'token' or return None pass # pre diablo try: return self['token']['tenantId'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenantId' # could be found, return None pass @property def project_domain_id(self): if self.project_id: return 'default' @property def project_domain_name(self): if self.project_id: return 'Default' @property def auth_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'auth_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='publicURL', region_name=self._region_name) else: return None @property def management_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'management_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='adminURL', region_name=self._region_name) else: 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 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 class AccessInfoV3(AccessInfo): """An object encapsulating raw v3 auth token from identity service.""" def __init__(self, token, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.update(version='v3') self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, token=token, region_name=self._region_name) if token: self.auth_token = token @classmethod def is_valid(cls, body, **kwargs): if body: return 'token' in body elif kwargs: return kwargs.get('version') == 'v3' else: return False def has_service_catalog(self): return 'catalog' in self @property def is_federated(self): return 'OS-FEDERATION' in self['user'] @property def expires(self): return timeutils.parse_isotime(self['expires_at']) @property def issued(self): return timeutils.parse_isotime(self['issued_at']) @property 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 @property def role_ids(self): return [r['id'] for r in self.get('roles', [])] @property def role_names(self): return [r['name'] for r in self.get('roles', [])] @property def username(self): return self['user']['name'] @property def domain_name(self): domain = self.get('domain') if domain: return domain['name'] @property def domain_id(self): domain = self.get('domain') if domain: return domain['id'] @property def project_id(self): project = self.get('project') if project: return project['id'] @property def project_domain_id(self): project = self.get('project') if project: return project['domain']['id'] @property def project_domain_name(self): project = self.get('project') if project: return project['domain']['name'] @property def project_name(self): project = self.get('project') if project: return project['name'] @property def scoped(self): """Deprecated as of the 1.7.0 release. Use project_scoped instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'scoped is deprecated as of the 1.7.0 release in favor of ' 'project_scoped and may be removed in the 2.0.0 release.', DeprecationWarning) return ('catalog' in self and self['catalog'] and 'project' in self) @property def project_scoped(self): return 'project' in self @property def domain_scoped(self): return 'domain' in self @property def trust_id(self): return self.get('OS-TRUST:trust', {}).get('id') @property def trust_scoped(self): return 'OS-TRUST:trust' in self @property def trustee_user_id(self): return self.get('OS-TRUST:trust', {}).get('trustee_user', {}).get('id') @property def trustor_user_id(self): return self.get('OS-TRUST:trust', {}).get('trustor_user', {}).get('id') @property def auth_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'auth_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='public', region_name=self._region_name) else: return None @property def management_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'management_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='admin', region_name=self._region_name) else: return None @property def oauth_access_token_id(self): return self.get('OS-OAUTH1', {}).get('access_token_id') @property def oauth_consumer_id(self): return self.get('OS-OAUTH1', {}).get('consumer_id') @property def audit_id(self): try: return self.get('audit_ids', [])[0] except IndexError: return None @property def audit_chain_id(self): try: return self.get('audit_ids', [])[1] except IndexError: return None python-keystoneclient-4.0.0/keystoneclient/discover.py0000664000175000017500000003735313644407055023367 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 warnings from debtcollector import removals from keystoneauth1 import plugin from keystoneclient import _discover from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client _CLIENT_VERSIONS = {2: v2_client.Client, 3: v3_client.Client} # functions needed from the private file that can be made public def normalize_version_number(version): """Turn a version representation into a tuple. Takes a string, tuple or float which represent version formats we can handle and converts them into a (major, minor) version tuple that we can actually use for discovery. e.g. 'v3.3' gives (3, 3) 3.1 gives (3, 1) :param version: Inputted version number to try and convert. :returns: A usable version tuple :rtype: tuple :raises TypeError: if the inputted version cannot be converted to tuple. """ return _discover.normalize_version_number(version) 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 """ return _discover.version_match(required, candidate) def available_versions(url, session=None, **kwargs): """Retrieve raw version data from a url.""" if not session: session = client_session.Session._construct(kwargs) return _discover.get_version_data(session, url) class Discover(_discover.Discover): """A means to discover and create clients. Clients are created depending on the supported API versions on the server. Querying the server is done on object creation and every subsequent method operates upon the data that was retrieved. The connection parameters associated with this method are the same format and name as those used by a client (see :py:class:`keystoneclient.v2_0.client.Client` and :py:class:`keystoneclient.v3.client.Client`). If not overridden in subsequent methods they will also be what is passed to the constructed client. In the event that auth_url and endpoint is provided then auth_url will be used in accordance with how the client operates. .. warning:: Creating an instance of this class without using the session argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param session: A session object that will be used for communication. Clients will also be constructed with this session. :type session: keystoneclient.session.Session :param string auth_url: Identity service endpoint for authorization. (optional) :param string endpoint: A user-supplied endpoint URL for the identity service. (optional) :param string original_ip: The original IP of the requesting user which will be sent to identity service in a 'Forwarded' header. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param boolean debug: Enables debug logging of all request and responses to the identity service. default False (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string cacert: Path to the Privacy Enhanced Mail (PEM) file which contains the trusted authority X.509 certificates needed to established SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string key: Path to the Privacy Enhanced Mail (PEM) file which contains the unencrypted client private key needed to established two-way SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string cert: Path to the Privacy Enhanced Mail (PEM) file which contains the corresponding X.509 client certificate needed to established two-way SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param boolean insecure: Does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param bool authenticated: Should a token be used to perform the initial discovery operations. default: None (attach a token if an auth plugin is available). """ def __init__(self, session=None, authenticated=None, **kwargs): if not session: warnings.warn( 'Constructing a Discover instance without using a session is ' 'deprecated as of the 1.7.0 release and may be removed in the ' '2.0.0 release.', DeprecationWarning) session = client_session.Session._construct(kwargs) kwargs['session'] = session url = None endpoint = kwargs.pop('endpoint', None) auth_url = kwargs.pop('auth_url', None) if endpoint: self._use_endpoint = True url = endpoint elif auth_url: self._use_endpoint = False url = auth_url elif session.auth: self._use_endpoint = False url = session.get_endpoint(interface=plugin.AUTH_INTERFACE) if not url: raise exceptions.DiscoveryFailure( _('Not enough information to determine URL. Provide' ' either a Session, or auth_url or endpoint')) self._client_kwargs = kwargs super(Discover, self).__init__(session, url, authenticated=authenticated) @removals.remove(message='Use raw_version_data instead.', version='1.7.0', removal_version='2.0.0') def available_versions(self, **kwargs): """Return a list of identity APIs available on the server. The list returned includes the data associated with them. .. warning:: This method is deprecated as of the 1.7.0 release in favor of :meth:`raw_version_data` and may be removed in the 2.0.0 release. :param bool unstable: Accept endpoints not marked 'stable'. (optional) Equates to setting allow_experimental and allow_unknown to True. :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: A List of dictionaries as presented by the server. Each dict will contain the version and the URL to use for the version. It is a direct representation of the layout presented by the identity API. """ return self.raw_version_data(**kwargs) @removals.removed_kwarg( 'unstable', message='Use allow_experimental and allow_unknown instead.', version='1.7.0', removal_version='2.0.0') def raw_version_data(self, unstable=False, **kwargs): """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 unstable: equates to setting allow_experimental and allow_unknown. This argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :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 Example:: >>> from keystoneclient import discover >>> disc = discover.Discovery(auth_url='http://localhost:5000') >>> disc.raw_version_data() [{'id': 'v3.0', 'links': [{'href': u'http://127.0.0.1:5000/v3/', 'rel': u'self'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v3+json'}, {'base': 'application/xml', 'type': 'application/vnd.openstack.identity-v3+xml'}], 'status': 'stable', 'updated': '2013-03-06T00:00:00Z'}, {'id': 'v2.0', 'links': [{'href': u'http://127.0.0.1:5000/v2.0/', 'rel': u'self'}, {'href': u'...', 'rel': u'describedby', 'type': u'application/pdf'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v2.0+json'}, {'base': 'application/xml', 'type': 'application/vnd.openstack.identity-v2.0+xml'}], 'status': 'stable', 'updated': '2013-03-06T00:00:00Z'}] """ if unstable: kwargs.setdefault('allow_experimental', True) kwargs.setdefault('allow_unknown', True) return super(Discover, self).raw_version_data(**kwargs) def _calculate_version(self, version, unstable): version_data = None if version: version_data = self.data_for(version) else: # if no version specified pick the latest one all_versions = self.version_data(unstable=unstable) if all_versions: version_data = all_versions[-1] if not version_data: msg = _('Could not find a suitable endpoint') if version: msg = _('Could not find a suitable endpoint for client ' 'version: %s') % str(version) raise exceptions.VersionNotAvailable(msg) return version_data def _create_client(self, version_data, **kwargs): # Get the client for the version requested that was returned try: client_class = _CLIENT_VERSIONS[version_data['version'][0]] except KeyError: version = '.'.join(str(v) for v in version_data['version']) msg = _('No client available for version: %s') % version raise exceptions.DiscoveryFailure(msg) # kwargs should take priority over stored kwargs. for k, v in self._client_kwargs.items(): kwargs.setdefault(k, v) # restore the url to either auth_url or endpoint depending on what # was initially given if self._use_endpoint: kwargs['auth_url'] = None kwargs['endpoint'] = version_data['url'] else: kwargs['auth_url'] = version_data['url'] kwargs['endpoint'] = None return client_class(**kwargs) def create_client(self, version=None, unstable=False, **kwargs): """Factory function to create a new identity service client. :param tuple version: The required version of the identity API. If specified the client will be selected such that the major version is equivalent and an endpoint provides at least the specified minor version. For example to specify the 3.1 API use (3, 1). (optional) :param bool unstable: Accept endpoints not marked 'stable'. (optional) :param kwargs: Additional arguments will override those provided to this object's constructor. :returns: An instantiated identity client object. :raises keystoneclient.exceptions.DiscoveryFailure: if the server response is invalid :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client cannot be found. """ version_data = self._calculate_version(version, unstable) return self._create_client(version_data, **kwargs) 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. """ _discover._VERSION_HACKS.add_discover_hack(service_type, old, new) python-keystoneclient-4.0.0/keystoneclient/httpclient.py0000664000175000017500000011413213644407055023716 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # 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. """OpenStack Client interface. Handles the REST calls and responses.""" import logging import warnings from debtcollector import removals from debtcollector import renames from keystoneauth1 import adapter from oslo_serialization import jsonutils import pkg_resources import requests try: import pickle # nosec(cjschaef): see bug 1534288 for details # NOTE(sdague): The conditional keyring import needs to only # trigger if it's a version of keyring that's supported in global # requirements. Update _min and _bad when that changes. keyring_v = pkg_resources.parse_version( pkg_resources.get_distribution("keyring").version) keyring_min = pkg_resources.parse_version('5.5.1') # This is a list of versions, e.g., pkg_resources.parse_version('3.3') keyring_bad = [] if keyring_v >= keyring_min and keyring_v not in keyring_bad: import keyring else: keyring = None except (ImportError, pkg_resources.DistributionNotFound): keyring = None pickle = None from keystoneclient import _discover from keystoneclient import access from keystoneclient.auth import base from keystoneclient import baseclient from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session _logger = logging.getLogger(__name__) USER_AGENT = client_session.USER_AGENT """Default user agent string. This property is deprecated as of the 1.7.0 release in favor of :data:`keystoneclient.session.USER_AGENT` and may be removed in the 2.0.0 release. """ @removals.remove(message='Use keystoneclient.session.request instead.', version='1.7.0', removal_version='2.0.0') def request(*args, **kwargs): """Make a request. This function is deprecated as of the 1.7.0 release in favor of :func:`keystoneclient.session.request` and may be removed in the 2.0.0 release. """ return client_session.request(*args, **kwargs) class _FakeRequestSession(object): """This object is a temporary hack that should be removed later. Keystoneclient has a cyclical dependency with its managers which is preventing it from being cleaned up correctly. This is always bad but when we switched to doing connection pooling this object wasn't getting cleaned either and so we had left over TCP connections hanging around. Until we can fix the client cleanup we rollback the use of a requests session and do individual connections like we used to. """ def request(self, *args, **kwargs): return requests.request(*args, **kwargs) class _KeystoneAdapter(adapter.LegacyJsonAdapter): """A wrapper layer to interface keystoneclient with a session. An adapter provides a generic interface between a client and the session to provide client specific defaults. This object is passed to the managers. Keystoneclient managers have some additional requirements of variables that they expect to be present on the passed object. Subclass the existing adapter to provide those values that keystoneclient managers expect. """ @property def user_id(self): """Best effort to retrieve the user_id from the plugin. Some managers rely on being able to get the currently authenticated user id. This is a problem when we are trying to abstract away the details of an auth plugin. For example changing a user's password can require access to the currently authenticated user_id. Perform a best attempt to fetch this data. It will work in the legacy case and with identity plugins and be None otherwise which is the same as the historical behavior. """ # the identity plugin case try: return self.session.auth.get_access(self.session).user_id except AttributeError: # nosec(cjschaef): attempt legacy retrival, or # return None pass # there is a case that we explicitly allow (tested by our unit tests) # that says you should be able to set the user_id on a legacy client # and it should overwrite the one retrieved via authentication. If it's # a legacy then self.session.auth is a client and we retrieve user_id. try: return self.session.auth.user_id except AttributeError: # nosec(cjschaef): retrivals failed, return # None pass return None class HTTPClient(baseclient.Client, base.BaseAuthPlugin): """HTTP client. .. warning:: Creating an instance of this class without using the session argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string user_id: User ID for authentication. (optional) :param string username: Username for authentication. (optional) :param string user_domain_id: User's domain ID for authentication. (optional) :param string user_domain_name: User's domain name for authentication. (optional) :param string password: Password for authentication. (optional) :param string domain_id: Domain ID for domain scoping. (optional) :param string domain_name: Domain name for domain scoping. (optional) :param string project_id: Project ID for project scoping. (optional) :param string project_name: Project name for project scoping. (optional) :param string project_domain_id: Project's domain ID for project scoping. (optional) :param string project_domain_name: Project's domain name for project scoping. (optional) :param string auth_url: Identity service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param integer timeout: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string endpoint: A user-supplied endpoint URL for the identity service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation. (optional) :param string token: Token for authentication. (optional) :param string cacert: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string key: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string cert: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param boolean insecure: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string original_ip: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param dict auth_ref: To allow for consumers of the client to manage their own caching strategy, you may initialize a client with a previously captured auth_reference (token). If there are keyword arguments passed that also exist in auth_ref, the value from the argument will take precedence. :param boolean use_keyring: Enables caching auth_ref into keyring. default: False (optional) :param boolean force_new_token: Keyring related parameter, forces request for new token. default: False (optional) :param integer stale_duration: Gap in seconds to determine if token from keyring is about to expire. default: 30 (optional) :param string tenant_name: Tenant name. (optional) The tenant_name keyword argument is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. :param string tenant_id: Tenant id. (optional) The tenant_id keyword argument is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. :param string trust_id: Trust ID for trust scoping. (optional) :param object session: A Session object to be used for communicating with the identity service. :type session: keystoneclient.session.Session :param string service_name: The default service_name for URL discovery. default: None (optional) :param string interface: The default interface for URL discovery. default: admin (optional) :param string endpoint_override: Always use this endpoint URL for requests for this client. (optional) :param auth: An auth plugin to use instead of the session one. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :param string user_agent: The User-Agent string to set. default: python-keystoneclient (optional) :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. (optional) """ version = None @renames.renamed_kwarg('tenant_name', 'project_name', version='1.7.0', removal_version='2.0.0') @renames.renamed_kwarg('tenant_id', 'project_id', version='1.7.0', removal_version='2.0.0') def __init__(self, username=None, tenant_id=None, tenant_name=None, password=None, auth_url=None, region_name=None, endpoint=None, token=None, auth_ref=None, use_keyring=False, force_new_token=False, stale_duration=None, user_id=None, user_domain_id=None, user_domain_name=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, trust_id=None, session=None, service_name=None, interface='admin', endpoint_override=None, auth=None, user_agent=USER_AGENT, connect_retries=None, **kwargs): # set baseline defaults self.user_id = None self.username = None self.user_domain_id = None self.user_domain_name = None self.domain_id = None self.domain_name = None self.project_id = None self.project_name = None self.project_domain_id = None self.project_domain_name = None self.auth_url = None self._endpoint = None self._management_url = None self.trust_id = None # if loading from a dictionary passed in via auth_ref, # load values from AccessInfo parsing that dictionary if auth_ref: self.auth_ref = access.AccessInfo.factory(**auth_ref) self.version = self.auth_ref.version self.user_id = self.auth_ref.user_id self.username = self.auth_ref.username self.user_domain_id = self.auth_ref.user_domain_id self.domain_id = self.auth_ref.domain_id self.domain_name = self.auth_ref.domain_name self.project_id = self.auth_ref.project_id self.project_name = self.auth_ref.project_name self.project_domain_id = self.auth_ref.project_domain_id auth_urls = self.auth_ref.service_catalog.get_urls( service_type='identity', endpoint_type='public', region_name=region_name) self.auth_url = auth_urls[0] management_urls = self.auth_ref.service_catalog.get_urls( service_type='identity', endpoint_type='admin', region_name=region_name) self._management_url = management_urls[0] self.auth_token_from_user = self.auth_ref.auth_token self.trust_id = self.auth_ref.trust_id # TODO(blk-u): Using self.auth_ref.service_catalog._region_name is # deprecated and this code must be removed when the property is # actually removed. if self.auth_ref.has_service_catalog() and not region_name: region_name = self.auth_ref.service_catalog._region_name else: self.auth_ref = None # allow override of the auth_ref defaults from explicit # values provided to the client # apply deprecated variables first, so modern variables override them if tenant_id: self.project_id = tenant_id if tenant_name: self.project_name = tenant_name # user-related attributes self.password = password if user_id: self.user_id = user_id if username: self.username = username if user_domain_id: self.user_domain_id = user_domain_id elif not (user_id or user_domain_name): self.user_domain_id = 'default' if user_domain_name: self.user_domain_name = user_domain_name # domain-related attributes if domain_id: self.domain_id = domain_id if domain_name: self.domain_name = domain_name # project-related attributes if project_id: self.project_id = project_id if project_name: self.project_name = project_name if project_domain_id: self.project_domain_id = project_domain_id elif not (project_id or project_domain_name): self.project_domain_id = 'default' if project_domain_name: self.project_domain_name = project_domain_name # trust-related attributes if trust_id: self.trust_id = trust_id # endpoint selection if auth_url: self.auth_url = auth_url.rstrip('/') if token: self.auth_token_from_user = token else: self.auth_token_from_user = None if endpoint: self._endpoint = endpoint.rstrip('/') self._auth_token = None if not session: warnings.warn( 'Constructing an HTTPClient instance without using a session ' 'is deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) kwargs['session'] = _FakeRequestSession() session = client_session.Session._construct(kwargs) session.auth = self self.session = session self.domain = '' # NOTE(jamielennox): unfortunately we can't just use **kwargs here as # it would incompatibly limit the kwargs that can be passed to __init__ # try and keep this list in sync with adapter.Adapter.__init__ version = ( _discover.normalize_version_number(self.version) if self.version else None) self._adapter = _KeystoneAdapter(session, service_type='identity', service_name=service_name, interface=interface, region_name=region_name, endpoint_override=endpoint_override, version=version, auth=auth, user_agent=user_agent, connect_retries=connect_retries) # NOTE(dstanek): This allows me to not have to change keystoneauth or # to write an adapter to the adapter here. Splitting thing into # multiple project isn't always all sunshine and roses. self._adapter.include_metadata = kwargs.pop('include_metadata', False) # keyring setup if use_keyring and keyring is None: _logger.warning('Failed to load keyring modules.') self.use_keyring = use_keyring and keyring is not None self.force_new_token = force_new_token self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION self.stale_duration = int(self.stale_duration) def get_token(self, session, **kwargs): return self.auth_token @property def auth_token(self): if self._auth_token: return self._auth_token if self.auth_ref: if self.auth_ref.will_expire_soon(self.stale_duration): self.authenticate() return self.auth_ref.auth_token if self.auth_token_from_user: return self.auth_token_from_user def get_endpoint(self, session, interface=None, **kwargs): if interface == 'public' or interface is base.AUTH_INTERFACE: return self.auth_url else: return self.management_url def get_user_id(self, session, **kwargs): return self.auth_ref.user_id def get_project_id(self, session, **kwargs): return self.auth_ref.project_id @auth_token.setter def auth_token(self, value): """Override the auth_token. If an application sets auth_token explicitly then it will always be used and override any past or future retrieved token. """ self._auth_token = value @auth_token.deleter def auth_token(self): self._auth_token = None self.auth_token_from_user = None @property def service_catalog(self): """Return this client's service catalog.""" try: return self.auth_ref.service_catalog except AttributeError: return None def has_service_catalog(self): """Return True if this client provides a service catalog.""" return self.auth_ref and self.auth_ref.has_service_catalog() @property def tenant_id(self): """Provide read-only backwards compatibility for tenant_id. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. """ warnings.warn( 'tenant_id is deprecated as of the 1.7.0 release in favor of ' 'project_id and may be removed in the 2.0.0 release.', DeprecationWarning) return self.project_id @property def tenant_name(self): """Provide read-only backwards compatibility for tenant_name. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. """ warnings.warn( 'tenant_name is deprecated as of the 1.7.0 release in favor of ' 'project_name and may be removed in the 2.0.0 release.', DeprecationWarning) return self.project_name def authenticate(self, username=None, password=None, tenant_name=None, tenant_id=None, auth_url=None, token=None, user_id=None, domain_name=None, domain_id=None, project_name=None, project_id=None, user_domain_id=None, user_domain_name=None, project_domain_id=None, project_domain_name=None, trust_id=None, region_name=None): """Authenticate user. Uses the data provided at instantiation to authenticate against the Identity server. This may use either a username and password or token for authentication. If a tenant name or id was provided then the resulting authenticated client will be scoped to that tenant and contain a service catalog of available endpoints. With the v2.0 API, if a tenant name or ID is not provided, the authentication token returned will be 'unscoped' and limited in capabilities until a fully-scoped token is acquired. With the v3 API, if a domain name or id was provided then the resulting authenticated client will be scoped to that domain. If a project name or ID is not provided, and the authenticating user has a default project configured, the authentication token returned will be 'scoped' to the default project. Otherwise, the authentication token returned will be 'unscoped' and limited in capabilities until a fully-scoped token is acquired. With the v3 API, with the OS-TRUST extension enabled, the trust_id can be provided to allow project-specific role delegation between users If successful, sets the self.auth_ref and self.auth_token with the returned token. If not already set, will also set self.management_url from the details provided in the token. :returns: ``True`` if authentication was successful. :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token :raises keystoneclient.exceptions.ValueError: if insufficient parameters are used. If keyring is used, token is retrieved from keyring instead. Authentication will only be necessary if any of the following conditions are met: * keyring is not used * if token is not found in keyring * if token retrieved from keyring is expired or about to expired (as determined by stale_duration) * if force_new_token is true """ auth_url = auth_url or self.auth_url user_id = user_id or self.user_id username = username or self.username password = password or self.password user_domain_id = user_domain_id or self.user_domain_id user_domain_name = user_domain_name or self.user_domain_name domain_id = domain_id or self.domain_id domain_name = domain_name or self.domain_name project_id = project_id or tenant_id or self.project_id project_name = project_name or tenant_name or self.project_name project_domain_id = project_domain_id or self.project_domain_id project_domain_name = project_domain_name or self.project_domain_name trust_id = trust_id or self.trust_id region_name = region_name or self._adapter.region_name if not token: token = self.auth_token_from_user if (not token and self.auth_ref and not self.auth_ref.will_expire_soon(self.stale_duration)): token = self.auth_ref.auth_token kwargs = { 'auth_url': auth_url, 'user_id': user_id, 'username': username, 'user_domain_id': user_domain_id, 'user_domain_name': user_domain_name, '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, 'token': token, 'trust_id': trust_id, } (keyring_key, auth_ref) = self.get_auth_ref_from_keyring(**kwargs) new_token_needed = False if auth_ref is None or self.force_new_token: new_token_needed = True kwargs['password'] = password resp = self.get_raw_token_from_identity_service(**kwargs) if isinstance(resp, access.AccessInfo): self.auth_ref = resp else: self.auth_ref = access.AccessInfo.factory(*resp) # NOTE(jamielennox): The original client relies on being able to # push the region name into the service catalog but new auth # it in. if region_name: self.auth_ref.service_catalog._region_name = region_name else: self.auth_ref = auth_ref self.process_token(region_name=region_name) if new_token_needed: self.store_auth_ref_into_keyring(keyring_key) return True def _build_keyring_key(self, **kwargs): """Create a unique key for keyring. Used to store and retrieve auth_ref from keyring. Return a slash-separated string of values ordered by key name. """ return '/'.join([kwargs[k] or '?' for k in sorted(kwargs)]) def get_auth_ref_from_keyring(self, **kwargs): """Retrieve auth_ref from keyring. If auth_ref is found in keyring, (keyring_key, auth_ref) is returned. Otherwise, (keyring_key, None) is returned. :returns: (keyring_key, auth_ref) or (keyring_key, None) :returns: or (None, None) if use_keyring is not set in the object """ keyring_key = None auth_ref = None if self.use_keyring: keyring_key = self._build_keyring_key(**kwargs) try: auth_ref = keyring.get_password("keystoneclient_auth", keyring_key) if auth_ref: auth_ref = pickle.loads(auth_ref) # nosec(cjschaef): see # bug 1534288 if auth_ref.will_expire_soon(self.stale_duration): # token has expired, don't use it auth_ref = None except Exception as e: auth_ref = None _logger.warning('Unable to retrieve token from keyring %s', e) return (keyring_key, auth_ref) def store_auth_ref_into_keyring(self, keyring_key): """Store auth_ref into keyring.""" if self.use_keyring: try: keyring.set_password("keystoneclient_auth", keyring_key, pickle.dumps(self.auth_ref)) # nosec # (cjschaef): see bug 1534288 except Exception as e: _logger.warning("Failed to store token into keyring %s", e) def _process_management_url(self, region_name): try: self._management_url = self.auth_ref.service_catalog.url_for( service_type='identity', endpoint_type='admin', region_name=region_name) except exceptions.EndpointNotFound as e: _logger.debug("Failed to find endpoint for management url %s", e) def process_token(self, region_name=None): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ # if we got a response without a service catalog, set the local # list of tenants for introspection, and leave to client user # to determine what to do. Otherwise, load up the service catalog if self.auth_ref.project_scoped: if not self.auth_ref.tenant_id: raise exceptions.AuthorizationFailure( _("Token didn't provide tenant_id")) self._process_management_url(region_name) self.project_name = self.auth_ref.tenant_name self.project_id = self.auth_ref.tenant_id if not self.auth_ref.user_id: raise exceptions.AuthorizationFailure( _("Token didn't provide user_id")) self.user_id = self.auth_ref.user_id self.auth_domain_id = self.auth_ref.domain_id self.auth_tenant_id = self.auth_ref.tenant_id self.auth_user_id = self.auth_ref.user_id @property def management_url(self): return self._endpoint or self._management_url @management_url.setter def management_url(self, value): # NOTE(jamielennox): it's debatable here whether we should set # _endpoint or _management_url. As historically management_url was set # permanently setting _endpoint would better match that behaviour. self._endpoint = value def get_raw_token_from_identity_service(self, auth_url, username=None, password=None, tenant_name=None, tenant_id=None, token=None, user_id=None, user_domain_id=None, user_domain_name=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, trust_id=None): """Authenticate against the Identity API and get a token. Not implemented here because auth protocols should be API version-specific. Expected to authenticate or validate an existing authentication reference already associated with the client. Invoking this call *always* makes a call to the Identity service. :returns: (``resp``, ``body``) """ raise NotImplementedError def serialize(self, entity): return jsonutils.dumps(entity) @removals.remove(version='1.7.0', removal_version='2.0.0') def request(self, *args, **kwargs): """Send an http request with the specified characteristics. Wrapper around requests.request to handle tasks such as setting headers, JSON encoding/decoding, and error handling. .. warning:: *DEPRECATED*: This function is no longer used. It was designed to be used only by the managers and the managers now receive an adapter so this function is no longer on the standard request path. This may be removed in the 2.0.0 release. """ return self._request(*args, **kwargs) def _request(self, *args, **kwargs): kwargs.setdefault('authenticated', False) return self._adapter.request(*args, **kwargs) def _cs_request(self, url, method, management=True, **kwargs): """Make an authenticated request to keystone endpoint. Request are made to keystone endpoint by concatenating self.management_url and url and passing in method and any associated kwargs. """ if not management: endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', 'public') kwargs.setdefault('authenticated', None) return self._request(url, method, **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def get(self, url, **kwargs): """Perform an authenticated GET request. This calls :py:meth:`.request()` with ``method`` set to ``GET`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'GET', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def head(self, url, **kwargs): """Perform an authenticated HEAD request. This calls :py:meth:`.request()` with ``method`` set to ``HEAD`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'HEAD', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def post(self, url, **kwargs): """Perform an authenticate POST request. This calls :py:meth:`.request()` with ``method`` set to ``POST`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'POST', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def put(self, url, **kwargs): """Perform an authenticate PUT request. This calls :py:meth:`.request()` with ``method`` set to ``PUT`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'PUT', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def patch(self, url, **kwargs): """Perform an authenticate PATCH request. This calls :py:meth:`.request()` with ``method`` set to ``PATCH`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'PATCH', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def delete(self, url, **kwargs): """Perform an authenticate DELETE request. This calls :py:meth:`.request()` with ``method`` set to ``DELETE`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'DELETE', **kwargs) deprecated_session_variables = {'original_ip': None, 'cert': None, 'timeout': None, 'verify_cert': 'verify'} deprecated_adapter_variables = {'region_name': None} def __getattr__(self, name): """Fetch deprecated session variables.""" try: var_name = self.deprecated_session_variables[name] except KeyError: # nosec(cjschaef): try adapter variable or raise # an AttributeError pass else: warnings.warn( 'The %s session variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return getattr(self.session, var_name or name) try: var_name = self.deprecated_adapter_variables[name] except KeyError: # nosec(cjschaef): raise an AttributeError pass else: warnings.warn( 'The %s adapter variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return getattr(self._adapter, var_name or name) raise AttributeError(_("Unknown Attribute: %s") % name) def __setattr__(self, name, val): """Assign value to deprecated seesion variables.""" try: var_name = self.deprecated_session_variables[name] except KeyError: # nosec(cjschaef): try adapter variable or call # parent class's __setattr__ pass else: warnings.warn( 'The %s session variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return setattr(self.session, var_name or name) try: var_name = self.deprecated_adapter_variables[name] except KeyError: # nosec(cjschaef): call parent class's __setattr__ pass else: warnings.warn( 'The %s adapter variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return setattr(self._adapter, var_name or name) super(HTTPClient, self).__setattr__(name, val) python-keystoneclient-4.0.0/keystoneclient/adapter.py0000664000175000017500000002113713644407055023162 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 warnings from oslo_serialization import jsonutils 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. :param session: The session object to wrap. :type session: keystoneclient.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 tuple version: The version that this API targets. :param auth: An auth plugin to use instead of the session one. :type auth: keystoneclient.auth.base.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 """ 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): warnings.warn( 'keystoneclient.adapter.Adapter is deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1.adapter.Adapter. It will be ' 'removed in future releases.', DeprecationWarning) # NOTE(jamielennox): when adding new parameters to adapter please also # add them to the adapter call in httpclient.HTTPClient.__init__ 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 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) 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) 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: :class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :returns: A valid token. :rtype: string """ 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: :class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: An endpoint if available or None. :rtype: string """ 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 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: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Current `user_id` or None if not supported by plugin. :rtype: string """ 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: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Current `project_id` or None if not supported by plugin. :rtype: string """ 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) 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: # nosec(cjschaef): kwargs doesn't contain a 'body' # key, while 'json' is an optional argument for Session.request pass resp = super(LegacyJsonAdapter, self).request(*args, **kwargs) body = None if resp.text: try: body = jsonutils.loads(resp.text) except ValueError: # nosec(cjschaef): return None for body as # expected pass return resp, body python-keystoneclient-4.0.0/keystoneclient/generic/0000775000175000017500000000000013644407143022576 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/generic/__init__.py0000664000175000017500000000003513644407055024707 0ustar zuulzuul00000000000000 __all__ = ( 'client', ) python-keystoneclient-4.0.0/keystoneclient/generic/client.py0000664000175000017500000001777213644407055024446 0ustar zuulzuul00000000000000# Copyright 2010 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. import logging from debtcollector import removals from six.moves.urllib import parse as urlparse from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ _logger = logging.getLogger(__name__) # NOTE(jamielennox): To be removed after Pike. @removals.removed_class('keystoneclient.generic.client.Client', message='Use keystoneauth discovery', version='3.9.0', removal_version='4.0.0') class Client(httpclient.HTTPClient): """Client for the OpenStack Keystone pre-version calls API. :param string endpoint: A user-supplied endpoint URL for the keystone service. :param integer timeout: Allows customization of the timeout for client http requests. (optional) Example:: >>> from keystoneclient.generic import client >>> root = client.Client(auth_url=KEYSTONE_URL) >>> versions = root.discover() ... >>> from keystoneclient.v2_0 import client as v2client >>> keystone = v2client.Client(auth_url=versions['v2.0']['url']) ... >>> user = keystone.users.get(USER_ID) >>> user.delete() """ def __init__(self, endpoint=None, **kwargs): """Initialize a new client for the Keystone v2.0 API.""" super(Client, self).__init__(endpoint=endpoint, **kwargs) self.endpoint = endpoint def discover(self, url=None): """Discover Keystone servers and return API versions supported. :param url: optional url to test (without version) Returns:: { 'message': 'Keystone found at http://127.0.0.1:5000/', 'v2.0': { 'status': 'beta', 'url': 'http://127.0.0.1:5000/v2.0/', 'id': 'v2.0' }, } """ if url: return self._check_keystone_versions(url) else: return self._local_keystone_exists() def _local_keystone_exists(self): """Check if Keystone is available on default local port 35357.""" results = self._check_keystone_versions("http://localhost:35357") if results is None: results = self._check_keystone_versions("https://localhost:35357") return results def _check_keystone_versions(self, url): """Call Keystone URL and detects the available API versions.""" try: resp, body = self._request(url, "GET", headers={'Accept': 'application/json'}) # Multiple Choices status code is returned by the root # identity endpoint, with references to one or more # Identity API versions -- v3 spec # some cases we get No Content if resp.status_code in (200, 204, 300): try: results = {} if 'version' in body: results['message'] = _("Keystone found at %s") % url version = body['version'] # Stable/diablo incorrect format id, status, version_url = ( self._get_version_info(version, url)) results[str(id)] = {"id": id, "status": status, "url": version_url} return results elif 'versions' in body: # Correct format results['message'] = _("Keystone found at %s") % url for version in body['versions']['values']: id, status, version_url = ( self._get_version_info(version, url)) results[str(id)] = {"id": id, "status": status, "url": version_url} return results else: results['message'] = ( _("Unrecognized response from %s") % url) return results except KeyError: raise exceptions.AuthorizationFailure() elif resp.status_code == 305: return self._check_keystone_versions(resp['location']) else: raise exceptions.from_response(resp, "GET", url) except Exception: _logger.exception('Failed to detect available versions.') def discover_extensions(self, url=None): """Discover Keystone extensions supported. :param url: optional url to test (should have a version in it) Returns:: { 'message': 'Keystone extensions at http://127.0.0.1:35357/v2', 'OS-KSEC2': 'OpenStack EC2 Credentials Extension', } """ if url: return self._check_keystone_extensions(url) def _check_keystone_extensions(self, url): """Call Keystone URL and detects the available extensions.""" try: if not url.endswith("/"): url += '/' resp, body = self._request("%sextensions" % url, "GET", headers={'Accept': 'application/json'}) if resp.status_code in (200, 204): # some cases we get No Content if 'extensions' in body and 'values' in body['extensions']: # Parse correct format (per contract) extensions = body['extensions']['values'] elif 'extensions' in body: # Support incorrect, but prevalent format extensions = body['extensions'] else: return dict(message=( _('Unrecognized extensions response from %s') % url)) return dict(self._get_extension_info(e) for e in extensions) elif resp.status_code == 305: return self._check_keystone_extensions(resp['location']) else: raise exceptions.from_response( resp, "GET", "%sextensions" % url) except Exception: _logger.exception('Failed to check keystone extensions.') @staticmethod def _get_version_info(version, root_url): """Parse version information. :param version: a dict of a Keystone version response :param root_url: string url used to construct the version if no URL is provided. :returns: tuple - (verionId, versionStatus, versionUrl) """ id = version['id'] status = version['status'] ref = urlparse.urljoin(root_url, id) if 'links' in version: for link in version['links']: if link['rel'] == 'self': ref = link['href'] break return (id, status, ref) @staticmethod def _get_extension_info(extension): """Parse extension information. :param extension: a dict of a Keystone extension response :returns: tuple - (alias, name) """ alias = extension['alias'] name = extension['name'] return (alias, name) python-keystoneclient-4.0.0/keystoneclient/v2_0/0000775000175000017500000000000013644407143021730 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/v2_0/__init__.py0000664000175000017500000000012413644407055024040 0ustar zuulzuul00000000000000from keystoneclient.v2_0.client import Client # noqa __all__ = ( 'client', ) python-keystoneclient-4.0.0/keystoneclient/v2_0/client.py0000664000175000017500000002337013644407055023567 0ustar zuulzuul00000000000000# 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 logging import warnings from keystoneclient.auth.identity import v2 as v2_auth from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ from keystoneclient.v2_0 import certificates from keystoneclient.v2_0 import ec2 from keystoneclient.v2_0 import endpoints from keystoneclient.v2_0 import extensions from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import services from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import tokens from keystoneclient.v2_0 import users _logger = logging.getLogger(__name__) class Client(httpclient.HTTPClient): """Client for the OpenStack Keystone v2.0 API. :param string username: Username for authentication. (optional) :param string password: Password for authentication. (optional) :param string token: Token for authentication. (optional) :param string tenant_id: Tenant id. (optional) :param string tenant_name: Tenant name. (optional) :param string auth_url: Keystone service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param string endpoint: A user-supplied endpoint URL for the keystone service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation.(optional) :param integer timeout: Allows customization of the timeout for client http requests. (optional) :param string original_ip: The original IP of the requesting user which will be sent to Keystone in a 'Forwarded' header. (optional) :param string cert: Path to the Privacy Enhanced Mail (PEM) file which contains the corresponding X.509 client certificate needed to established two-way SSL connection with the identity service. (optional) :param string key: Path to the Privacy Enhanced Mail (PEM) file which contains the unencrypted client private key needed to established two-way SSL connection with the identity service. (optional) :param string cacert: Path to the Privacy Enhanced Mail (PEM) file which contains the trusted authority X.509 certificates needed to established SSL connection with the identity service. (optional) :param boolean insecure: Does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) :param dict auth_ref: To allow for consumers of the client to manage their own caching strategy, you may initialize a client with a previously captured auth_reference (token) :param boolean debug: Enables debug logging of all request and responses to keystone. default False (option) .. warning:: If debug is enabled, it may show passwords in plain text as a part of its output. .. warning:: Constructing an instance of this class without a session is deprecated as of the 1.7.0 release and will be removed in the 2.0.0 release. The client can be created and used like a user or in a strictly bootstrap mode. Normal operation expects a username, password, auth_url, and tenant_name or id to be provided. Other values will be lazily loaded as needed from the service catalog. Example:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Password(auth_url=KEYSTONE_URL, ... username=USER, ... password=PASS, ... tenant_name=TENANT_NAME) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.tenants.list() ... >>> user = keystone.users.get(USER_ID) >>> user.delete() Once authenticated, you can store and attempt to re-use the authenticated token. the auth_ref property on the client returns as a dictionary-like-object so that you can export and cache it, re-using it when initiating another client:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Password(auth_url=KEYSTONE_URL, ... username=USER, ... password=PASS, ... tenant_name=TENANT_NAME) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> auth_ref = keystone.auth_ref >>> # pickle or whatever you like here >>> new_client = client.Client(auth_ref=auth_ref) Alternatively, you can provide the administrative token configured in keystone and an endpoint to communicate with directly. See (``admin_token`` in ``keystone.conf``) In this case, authenticate() is not needed, and no service catalog will be loaded. Example:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Token(auth_url='http://localhost:35357/v2.0', ... token='12345secret7890') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.tenants.list() """ version = 'v2.0' def __init__(self, **kwargs): """Initialize a new client for the Keystone v2.0 API.""" if not kwargs.get('session'): warnings.warn( 'Constructing an instance of the ' 'keystoneclient.v2_0.client.Client class without a session is ' 'deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) super(Client, self).__init__(**kwargs) self.certificates = certificates.CertificatesManager(self._adapter) self.endpoints = endpoints.EndpointManager(self._adapter) self.extensions = extensions.ExtensionManager(self._adapter) self.roles = roles.RoleManager(self._adapter) self.services = services.ServiceManager(self._adapter) self.tokens = tokens.TokenManager(self._adapter) self.users = users.UserManager(self._adapter, self.roles) self.tenants = tenants.TenantManager(self._adapter, self.roles, self.users) # extensions self.ec2 = ec2.CredentialsManager(self._adapter) # DEPRECATED: if session is passed then we go to the new behaviour of # authenticating on the first required call. if not kwargs.get('session') and self.management_url is None: self.authenticate() def get_raw_token_from_identity_service(self, auth_url, username=None, password=None, tenant_name=None, tenant_id=None, token=None, project_name=None, project_id=None, trust_id=None, **kwargs): """Authenticate against the v2 Identity API. If a token is provided it will be used in preference over username and password. :returns: access.AccessInfo if authentication was successful. :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token """ try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) new_kwargs = {'trust_id': trust_id, 'tenant_id': project_id or tenant_id, 'tenant_name': project_name or tenant_name} if token: plugin = v2_auth.Token(auth_url, token, **new_kwargs) elif username and password: plugin = v2_auth.Password(auth_url, username, password, **new_kwargs) else: msg = _('A username and password or token is required.') raise exceptions.AuthorizationFailure(msg) return plugin.get_auth_ref(self.session) except (exceptions.AuthorizationFailure, exceptions.Unauthorized): _logger.debug("Authorization Failed.") raise except exceptions.EndpointNotFound: msg = ( _('There was no suitable authentication url for this request')) raise exceptions.AuthorizationFailure(msg) except Exception as e: raise exceptions.AuthorizationFailure( _("Authorization Failed: %s") % e) python-keystoneclient-4.0.0/keystoneclient/v2_0/tokens.py0000664000175000017500000000774613644407055023625 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 plugin from keystoneclient import access from keystoneclient import base from keystoneclient.i18n import _ class Token(base.Resource): def __repr__(self): """Return string representation of resource information.""" return "" % self._info @property def id(self): return self._info['token']['id'] @property def expires(self): return self._info['token']['expires'] @property def tenant(self): return self._info['token'].get('tenant') class TokenManager(base.Manager): resource_class = Token def authenticate(self, username=None, tenant_id=None, tenant_name=None, password=None, token=None, return_raw=False): if token: params = {"auth": {"token": {"id": token}}} elif username and password: params = {"auth": {"passwordCredentials": {"username": username, "password": password}}} else: raise ValueError( _('A username and password or token is required.')) if tenant_id: params['auth']['tenantId'] = tenant_id elif tenant_name: params['auth']['tenantName'] = tenant_name args = ['/tokens', params, 'access'] kwargs = {'return_raw': return_raw, 'log': False} # NOTE(jamielennox): try doing a regular admin query first. If there is # no endpoint that can satisfy the request (eg an unscoped token) then # issue it against the auth_url. try: token_ref = self._post(*args, **kwargs) except exceptions.EndpointNotFound: kwargs['endpoint_filter'] = {'interface': plugin.AUTH_INTERFACE} token_ref = self._post(*args, **kwargs) return token_ref def delete(self, token): return self._delete("/tokens/%s" % base.getid(token)) def endpoints(self, token): return self._get("/tokens/%s/endpoints" % base.getid(token), "token") def validate(self, token): """Validate a token. :param token: Token to be validated. :rtype: :py:class:`.Token` """ return self._get('/tokens/%s' % base.getid(token), 'access') def get_token_data(self, token): """Fetch the data about a token from the identity server. :param str token: The token id. :rtype: dict """ url = '/tokens/%s' % token resp, body = self.client.get(url) return body def validate_access_info(self, token): """Validate a token. :param token: Token to be validated. This can be an instance of :py:class:`keystoneclient.access.AccessInfo` or a string token_id. :rtype: :py:class:`keystoneclient.access.AccessInfoV2` """ def calc_id(token): if isinstance(token, access.AccessInfo): return token.auth_token return base.getid(token) token_id = calc_id(token) body = self.get_token_data(token_id) return access.AccessInfo.factory(auth_token=token_id, body=body) def get_revoked(self): """Return the revoked tokens response. The response will be a dict containing 'signed' which is a CMS-encoded document. """ resp, body = self.client.get('/tokens/revoked') return body python-keystoneclient-4.0.0/keystoneclient/v2_0/ec2.py0000664000175000017500000000400413644407055022753 0ustar zuulzuul00000000000000# Copyright 2011 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. from keystoneclient import base class EC2(base.Resource): def __repr__(self): """Return string representation of EC2 resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) class CredentialsManager(base.ManagerWithFind): resource_class = EC2 def create(self, user_id, tenant_id): """Create a new access/secret pair for the user/tenant pair. :rtype: object of type :class:`EC2` """ params = {'tenant_id': tenant_id} return self._post('/users/%s/credentials/OS-EC2' % user_id, params, "credential") def list(self, user_id): """Get a list of access/secret pairs for a user_id. :rtype: list of :class:`EC2` """ return self._list("/users/%s/credentials/OS-EC2" % user_id, "credentials") def get(self, user_id, access): """Get the access/secret pair for a given access key. :rtype: object of type :class:`EC2` """ return self._get("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access)), "credential") def delete(self, user_id, access): """Delete an access/secret pair for a user.""" return self._delete("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access))) python-keystoneclient-4.0.0/keystoneclient/v2_0/certificates.py0000664000175000017500000000233413644407055024753 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 CertificatesManager(object): """Manager for certificates.""" def __init__(self, client): self._client = client def get_ca_certificate(self): """Get CA certificate. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/certificates/ca', authenticated=False) return resp.text def get_signing_certificate(self): """Get signing certificate. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/certificates/signing', authenticated=False) return resp.text python-keystoneclient-4.0.0/keystoneclient/v2_0/tenants.py0000664000175000017500000001427213644407055023766 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneauth1 import plugin from six.moves import urllib from keystoneclient import base from keystoneclient import exceptions class Tenant(base.Resource): """Represents a Keystone tenant. Attributes: * id: a uuid that identifies the tenant * name: tenant name * description: tenant description * enabled: boolean to indicate if tenant is enabled """ def __repr__(self): """Return string representation of tenant resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) def update(self, name=None, description=None, enabled=None): # Preserve the existing settings; keystone legacy resets these? new_name = name if name else self.name if description is not None: new_description = description else: new_description = self.description new_enabled = enabled if enabled is not None else self.enabled try: retval = self.manager.update(self.id, tenant_name=new_name, description=new_description, enabled=new_enabled) self = retval except Exception: retval = None return retval def add_user(self, user, role): return self.manager.role_manager.add_user_role(base.getid(user), base.getid(role), self.id) def remove_user(self, user, role): return self.manager.role_manager.remove_user_role(base.getid(user), base.getid(role), self.id) def list_users(self): return self.manager.list_users(self.id) class TenantManager(base.ManagerWithFind): """Manager class for manipulating Keystone tenants.""" resource_class = Tenant def __init__(self, client, role_manager, user_manager): super(TenantManager, self).__init__(client) self.role_manager = role_manager self.user_manager = user_manager def get(self, tenant_id): return self._get("/tenants/%s" % tenant_id, "tenant") def create(self, tenant_name, description=None, enabled=True, **kwargs): """Create a new tenant.""" params = {"tenant": {"name": tenant_name, "description": description, "enabled": enabled}} # Allow Extras Passthru and ensure we don't clobber primary arguments. for k, v in kwargs.items(): if k not in params['tenant']: params['tenant'][k] = v return self._post('/tenants', params, "tenant") def list(self, limit=None, marker=None): """Get a list of tenants. :param integer limit: maximum number to return. (optional) :param string marker: use when specifying a limit and making multiple calls for querying. (optional) :rtype: list of :class:`Tenant` """ params = {} if limit: params['limit'] = limit if marker: params['marker'] = marker query = "" if params: query = "?" + urllib.parse.urlencode(params) # NOTE(jamielennox): try doing a regular admin query first. If there is # no endpoint that can satisfy the request (eg an unscoped token) then # issue it against the auth_url. try: tenant_list = self._list('/tenants%s' % query, 'tenants') except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} tenant_list = self._list('/tenants%s' % query, 'tenants', endpoint_filter=endpoint_filter) return tenant_list def update(self, tenant_id, tenant_name=None, description=None, enabled=None, **kwargs): """Update a tenant with a new name and description.""" body = {"tenant": {'id': tenant_id}} if tenant_name is not None: body['tenant']['name'] = tenant_name if enabled is not None: body['tenant']['enabled'] = enabled if description is not None: body['tenant']['description'] = description # Allow Extras Passthru and ensure we don't clobber primary arguments. for k, v in kwargs.items(): if k not in body['tenant']: body['tenant'][k] = v # Keystone's API uses a POST rather than a PUT here. return self._post("/tenants/%s" % tenant_id, body, "tenant") def delete(self, tenant): """Delete a tenant.""" return self._delete("/tenants/%s" % (base.getid(tenant))) def list_users(self, tenant): """List users for a tenant.""" return self.user_manager.list(base.getid(tenant)) def add_user(self, tenant, user, role): """Add a user to a tenant with the given role.""" return self.role_manager.add_user_role(base.getid(user), base.getid(role), base.getid(tenant)) def remove_user(self, tenant, user, role): """Remove the specified role from the user on the tenant.""" return self.role_manager.remove_user_role(base.getid(user), base.getid(role), base.getid(tenant)) python-keystoneclient-4.0.0/keystoneclient/v2_0/users.py0000664000175000017500000001103213644407055023442 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from six.moves import urllib from keystoneclient import base class User(base.Resource): """Represents a Keystone user.""" def __repr__(self): """Return string representation of user resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) def list_roles(self, tenant=None): return self.manager.list_roles(self.id, base.getid(tenant)) class UserManager(base.ManagerWithFind): """Manager class for manipulating Keystone users.""" resource_class = User def __init__(self, client, role_manager): super(UserManager, self).__init__(client) self.role_manager = role_manager def get(self, user): return self._get("/users/%s" % base.getid(user), "user") def update(self, user, **kwargs): """Update user data. Supported arguments include ``name``, ``email``, and ``enabled``. """ # FIXME(gabriel): "tenantId" seems to be accepted by the API but # fails to actually update the default tenant. params = {"user": kwargs} url = "/users/%s" % base.getid(user) return self._update(url, params, "user") def update_enabled(self, user, enabled): """Update enabled-ness.""" params = {"user": {"enabled": enabled}} self._update("/users/%s/OS-KSADM/enabled" % base.getid(user), params, "user") def update_password(self, user, password): """Update password.""" params = {"user": {"password": password}} return self._update("/users/%s/OS-KSADM/password" % base.getid(user), params, "user", log=False) def update_own_password(self, origpasswd, passwd): """Update password.""" params = {"user": {"password": passwd, "original_password": origpasswd}} return self._update("/OS-KSCRUD/users/%s" % self.client.user_id, params, response_key="access", method="PATCH", endpoint_filter={'interface': 'public'}, log=False) def update_tenant(self, user, tenant): """Update default tenant.""" params = {"user": {"tenantId": base.getid(tenant)}} # FIXME(ja): seems like a bad url - default tenant is an attribute # not a subresource!??? return self._update("/users/%s/OS-KSADM/tenant" % base.getid(user), params, "user") def create(self, name, password=None, email=None, tenant_id=None, enabled=True): """Create a user.""" params = {"user": {"name": name, "password": password, "tenantId": tenant_id, "email": email, "enabled": enabled}} return self._post('/users', params, "user", log=not bool(password)) def delete(self, user): """Delete a user.""" return self._delete("/users/%s" % base.getid(user)) def list(self, tenant_id=None, limit=None, marker=None): """Get a list of users (optionally limited to a tenant). :rtype: list of :class:`User` """ params = {} if limit: params['limit'] = int(limit) if marker: params['marker'] = marker query = "" if params: query = "?" + urllib.parse.urlencode(params) if not tenant_id: return self._list("/users%s" % query, "users") else: return self._list("/tenants/%s/users%s" % (tenant_id, query), "users") def list_roles(self, user, tenant=None): return self.role_manager.roles_for_user(base.getid(user), base.getid(tenant)) python-keystoneclient-4.0.0/keystoneclient/v2_0/services.py0000664000175000017500000000336713644407055024140 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Service(base.Resource): """Represents a Keystone service.""" def __repr__(self): """Return string representation of service resource information.""" return "" % self._info class ServiceManager(base.ManagerWithFind): """Manager class for manipulating Keystone services.""" resource_class = Service def list(self): """List available services.""" return self._list("/OS-KSADM/services", "OS-KSADM:services") def get(self, id): """Retrieve a service by id.""" return self._get("/OS-KSADM/services/%s" % id, "OS-KSADM:service") def create(self, name, service_type, description=None): """Create a new service.""" body = {"OS-KSADM:service": {'name': name, 'type': service_type, 'description': description}} return self._post("/OS-KSADM/services", body, "OS-KSADM:service") def delete(self, id): """Delete a service.""" return self._delete("/OS-KSADM/services/%s" % id) python-keystoneclient-4.0.0/keystoneclient/v2_0/extensions.py0000664000175000017500000000211113644407055024476 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import base class Extension(base.Resource): """Represents an Identity API extension.""" def __repr__(self): """Return string representation of extension resource information.""" return "" % self._info class ExtensionManager(base.ManagerWithFind): """Manager class for listing Identity API extensions.""" resource_class = Extension def list(self): """List all available extensions.""" return self._list('/extensions', 'extensions') python-keystoneclient-4.0.0/keystoneclient/v2_0/endpoints.py0000664000175000017500000000326013644407055024310 0ustar zuulzuul00000000000000# Copyright 2012 Canonical Ltd. # 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. from keystoneclient import base class Endpoint(base.Resource): """Represents a Keystone endpoint.""" def __repr__(self): """Return string representation of endpoint resource information.""" return "" % self._info class EndpointManager(base.ManagerWithFind): """Manager class for manipulating Keystone endpoints.""" resource_class = Endpoint def list(self): """List all available endpoints.""" return self._list('/endpoints', 'endpoints') def create(self, region, service_id, publicurl, adminurl=None, internalurl=None): """Create a new endpoint.""" body = {'endpoint': {'region': region, 'service_id': service_id, 'publicurl': publicurl, 'adminurl': adminurl, 'internalurl': internalurl}} return self._post('/endpoints', body, 'endpoint') def delete(self, id): """Delete an endpoint.""" return self._delete('/endpoints/%s' % id) python-keystoneclient-4.0.0/keystoneclient/v2_0/roles.py0000664000175000017500000000620713644407055023435 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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. from keystoneclient import base class Role(base.Resource): """Represents a Keystone role.""" def __repr__(self): """Return string representation of role resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) class RoleManager(base.ManagerWithFind): """Manager class for manipulating Keystone roles.""" resource_class = Role def get(self, role): return self._get("/OS-KSADM/roles/%s" % base.getid(role), "role") def create(self, name): """Create a role.""" params = {"role": {"name": name}} return self._post('/OS-KSADM/roles', params, "role") def delete(self, role): """Delete a role.""" return self._delete("/OS-KSADM/roles/%s" % base.getid(role)) def list(self): """List all available roles.""" return self._list("/OS-KSADM/roles", "roles") def roles_for_user(self, user, tenant=None): user_id = base.getid(user) if tenant: tenant_id = base.getid(tenant) route = "/tenants/%s/users/%s/roles" return self._list(route % (tenant_id, user_id), "roles") else: return self._list("/users/%s/roles" % user_id, "roles") def add_user_role(self, user, role, tenant=None): """Add a role to a user. If tenant is specified, the role is added just for that tenant, otherwise the role is added globally. """ user_id = base.getid(user) role_id = base.getid(role) if tenant: route = "/tenants/%s/users/%s/roles/OS-KSADM/%s" params = (base.getid(tenant), user_id, role_id) return self._update(route % params, None, "role") else: route = "/users/%s/roles/OS-KSADM/%s" return self._update(route % (user_id, role_id), None, "roles") def remove_user_role(self, user, role, tenant=None): """Remove a role from a user. If tenant is specified, the role is removed just for that tenant, otherwise the role is removed from the user's global roles. """ user_id = base.getid(user) role_id = base.getid(role) if tenant: route = "/tenants/%s/users/%s/roles/OS-KSADM/%s" params = (base.getid(tenant), user_id, role_id) return self._delete(route % params) else: route = "/users/%s/roles/OS-KSADM/%s" return self._delete(route % (user_id, role_id)) python-keystoneclient-4.0.0/keystoneclient/service_catalog.py0000664000175000017500000004056613644407055024703 0ustar zuulzuul00000000000000# 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 warnings import six from keystoneclient import exceptions from keystoneclient.i18n import _ @six.add_metaclass(abc.ABCMeta) class ServiceCatalog(object): """Helper methods for dealing with a Keystone Service Catalog. .. warning:: Setting region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ @classmethod def factory(cls, resource_dict, token=None, region_name=None): """Create ServiceCatalog object given an auth token. .. warning:: Setting region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ if ServiceCatalogV3.is_valid(resource_dict): return ServiceCatalogV3(token, resource_dict, region_name) elif ServiceCatalogV2.is_valid(resource_dict): return ServiceCatalogV2(resource_dict, region_name) else: raise NotImplementedError(_('Unrecognized auth response')) def __init__(self, region_name=None): if region_name: warnings.warn( 'Setting region_name on the service catalog is deprecated in ' 'favor of passing the region name as a parameter to calls ' 'made to the service catalog as of the 1.7.0 release and may ' 'be removed in the 2.0.0 release.', DeprecationWarning) self._region_name = region_name @property def region_name(self): """Region name. .. warning:: region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ warnings.warn( 'region_name is deprecated in favor of passing the region name as ' 'a parameter to calls made to the service catalog as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release.', DeprecationWarning) return self._region_name def _get_endpoint_region(self, endpoint): return endpoint.get('region_id') or endpoint.get('region') @abc.abstractmethod def get_token(self): """Fetch token details from service catalog. Returns a dictionary containing the following:: - `id`: Token's ID - `expires`: Token's expiration - `user_id`: Authenticated user's ID - `tenant_id`: Authorized project's ID - `domain_id`: Authorized domain's ID """ raise NotImplementedError() # pragma: no cover @abc.abstractmethod def _is_endpoint_type_match(self, endpoint, endpoint_type): """Helper function to normalize endpoint matching across v2 and v3. :returns: True if the provided endpoint matches the required endpoint_type otherwise False. """ pass # pragma: no cover @abc.abstractmethod def _normalize_endpoint_type(self, endpoint_type): """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' endpoint_type and v3 must be able to handle a 'publicURL' endpoint_type. :returns: the endpoint string in the format appropriate for this service catalog. """ pass # pragma: no cover def get_endpoints(self, service_type=None, endpoint_type=None, region_name=None, service_name=None): """Fetch and filter endpoints 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. """ endpoint_type = self._normalize_endpoint_type(endpoint_type) region_name = region_name or self._region_name sc = {} for service in (self.get_data() or []): try: st = service['type'] except KeyError: continue if service_type and service_type != st: 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. if service_name: try: sn = service['name'] except KeyError: # nosec(cjschaef) # assume that we're in v3.0-v3.2 and don't have the name in # the catalog. Skip the check. pass else: if service_name != sn: continue endpoints = sc.setdefault(st, []) for endpoint in service.get('endpoints', []): if (endpoint_type and not self._is_endpoint_type_match(endpoint, endpoint_type)): continue if (region_name and region_name != self._get_endpoint_region(endpoint)): continue endpoints.append(endpoint) return sc def _get_service_endpoints(self, attr, filter_value, service_type, endpoint_type, region_name, service_name): """Fetch the endpoints of a particular service_type. Endpoints returned are also filtered based on the attr and filter_value provided. """ sc_endpoints = self.get_endpoints(service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) try: endpoints = sc_endpoints[service_type] except KeyError: return if attr and not filter_value: warnings.warn( 'Providing attr without filter_value to get_urls() is ' 'deprecated as of the 1.7.0 release and may be removed in the ' '2.0.0 release. Either both should be provided or neither ' 'should be provided.') if filter_value: return [endpoint for endpoint in endpoints if endpoint.get(attr) == filter_value] return endpoints @abc.abstractmethod def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): """Fetch endpoint urls from the service catalog. 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. :param string attr: Endpoint attribute name. :param string filter_value: Endpoint attribute value. :param string service_type: Service type of the endpoint. :param string endpoint_type: Type of endpoint. Possible values: public or publicURL, internal or internalURL, admin or adminURL :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. :returns: tuple of urls or None (if no match found) """ raise NotImplementedError() # pragma: no cover def url_for(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=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 endpoint types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` :param string attr: Endpoint attribute name. :param string filter_value: Endpoint attribute value. :param string service_type: Service type of the endpoint. :param string endpoint_type: Type of endpoint. :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. """ if not self.get_data(): raise exceptions.EmptyCatalog(_('The service catalog is empty.')) urls = self.get_urls(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) try: return urls[0] except Exception: if service_name and region_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service named %(service_name)s in %(region_name)s ' 'region not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'service_name': service_name, 'region_name': region_name}) elif service_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service named %(service_name)s not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'service_name': service_name}) elif region_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service in %(region_name)s region not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'region_name': region_name}) else: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service not found') % {'endpoint_type': endpoint_type, 'service_type': service_type}) raise exceptions.EndpointNotFound(msg) @abc.abstractmethod def get_data(self): """Get the raw catalog structure. Get the version dependent catalog structure as it is presented within the resource. :returns: list containing raw catalog data entries or None """ raise NotImplementedError() # pragma: no cover class ServiceCatalogV2(ServiceCatalog): """An object for encapsulating the v2 service catalog. The object is created using raw v2 auth token from Keystone. """ def __init__(self, resource_dict, region_name=None): self.catalog = resource_dict super(ServiceCatalogV2, self).__init__(region_name=region_name) @classmethod def is_valid(cls, resource_dict): # This class is also used for reading token info of an unscoped token. # Unscoped token does not have 'serviceCatalog' in V2, checking this # will not work. Use 'token' attribute instead. return 'token' in resource_dict def _normalize_endpoint_type(self, endpoint_type): if endpoint_type and 'URL' not in endpoint_type: endpoint_type += 'URL' return endpoint_type def _is_endpoint_type_match(self, endpoint, endpoint_type): return endpoint_type in endpoint def get_data(self): return self.catalog.get('serviceCatalog') def get_token(self): token = {'id': self.catalog['token']['id'], 'expires': self.catalog['token']['expires']} try: token['user_id'] = self.catalog['user']['id'] token['tenant_id'] = self.catalog['token']['tenant']['id'] except KeyError: # nosec(cjschaef) # just leave the tenant and user out if it doesn't exist pass return token def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): endpoint_type = self._normalize_endpoint_type(endpoint_type) endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) if endpoints: return tuple([endpoint[endpoint_type] for endpoint in endpoints]) else: return None class ServiceCatalogV3(ServiceCatalog): """An object for encapsulating the v3 service catalog. The object is created using raw v3 auth token from Keystone. """ def __init__(self, token, resource_dict, region_name=None): super(ServiceCatalogV3, self).__init__(region_name=region_name) self._auth_token = token self.catalog = resource_dict @classmethod def is_valid(cls, resource_dict): # This class is also used for reading token info of an unscoped token. # Unscoped token does not have 'catalog', checking this # will not work. Use 'methods' attribute instead. return 'methods' in resource_dict def _normalize_endpoint_type(self, endpoint_type): if endpoint_type: endpoint_type = endpoint_type.rstrip('URL') return endpoint_type def _is_endpoint_type_match(self, endpoint, endpoint_type): try: return endpoint_type == endpoint['interface'] except KeyError: return False def get_data(self): return self.catalog.get('catalog') def get_token(self): token = {'id': self._auth_token, 'expires': self.catalog['expires_at']} try: token['user_id'] = self.catalog['user']['id'] domain = self.catalog.get('domain') if domain: token['domain_id'] = domain['id'] project = self.catalog.get('project') if project: token['tenant_id'] = project['id'] except KeyError: # nosec(cjschaef) # just leave the domain, project and user out if it doesn't exist pass return token def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='public', region_name=None, service_name=None): endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) if endpoints: return tuple([endpoint['url'] for endpoint in endpoints]) else: return None python-keystoneclient-4.0.0/keystoneclient/contrib/0000775000175000017500000000000013644407143022622 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/ec2/0000775000175000017500000000000013644407143023273 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/ec2/__init__.py0000664000175000017500000000000013644407055025374 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/ec2/utils.py0000664000175000017500000002715013644407055025014 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 - 2012 Justin Santa Barbara # 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 base64 import hashlib import hmac import re import six from six.moves import urllib from keystoneclient.i18n import _ class Ec2Signer(object): """Utility class for EC2 signing of request. This allows a request to be signed with an AWS style signature, which can then be used for authentication via the keystone ec2 authentication extension. """ def __init__(self, secret_key): self.secret_key = secret_key.encode() self.hmac = hmac.new(self.secret_key, digestmod=hashlib.sha1) if hashlib.sha256: self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256) def _v4_creds(self, credentials): """Detect if the credentials are for a v4 signed request. Check is needed since AWS removed the SignatureVersion field from the v4 request spec... This expects a dict of the request headers to be passed in the credentials dict, since the recommended way to pass v4 creds is via the 'Authorization' header see http://docs.aws.amazon.com/general/latest/gr/ sigv4-signed-request-examples.html Alternatively X-Amz-Algorithm can be specified as a query parameter, and the authentication data can also passed as query parameters. Note a hash of the request body is also required in the credentials for v4 auth to work in the body_hash key, calculated via: hashlib.sha256(req.body).hexdigest() """ try: auth_str = credentials['headers']['Authorization'] if auth_str.startswith('AWS4-HMAC-SHA256'): return True except KeyError: # Alternatively the Authorization data can be passed via # the query params list, check X-Amz-Algorithm=AWS4-HMAC-SHA256 try: if (credentials['params']['X-Amz-Algorithm'] == 'AWS4-HMAC-SHA256'): return True except KeyError: # nosec(cjschaef): in cases of not finding # entries, simply return False pass return False def generate(self, credentials): """Generate auth string according to what SignatureVersion is given.""" signature_version = credentials['params'].get('SignatureVersion') if signature_version == '0': return self._calc_signature_0(credentials['params']) if signature_version == '1': return self._calc_signature_1(credentials['params']) if signature_version == '2': return self._calc_signature_2(credentials['params'], credentials['verb'], credentials['host'], credentials['path']) if self._v4_creds(credentials): return self._calc_signature_4(credentials['params'], credentials['verb'], credentials['host'], credentials['path'], credentials['headers'], credentials['body_hash']) if signature_version is not None: raise Exception(_('Unknown signature version: %s') % signature_version) else: raise Exception(_('Unexpected signature format')) @staticmethod def _get_utf8_value(value): """Get the UTF8-encoded version of a value.""" if not isinstance(value, (six.binary_type, six.text_type)): value = str(value) if isinstance(value, six.text_type): return value.encode('utf-8') else: return value def _calc_signature_0(self, params): """Generate AWS signature version 0 string.""" s = (params['Action'] + params['Timestamp']).encode('utf-8') self.hmac.update(s) return base64.b64encode(self.hmac.digest()).decode('utf-8') def _calc_signature_1(self, params): """Generate AWS signature version 1 string.""" keys = list(params) keys.sort(key=six.text_type.lower) for key in keys: self.hmac.update(key.encode('utf-8')) val = self._get_utf8_value(params[key]) self.hmac.update(val) return base64.b64encode(self.hmac.digest()).decode('utf-8') @staticmethod def _canonical_qs(params): """Construct a sorted, correctly encoded query string. This is required for _calc_signature_2 and _calc_signature_4. """ keys = list(params) keys.sort() pairs = [] for key in keys: val = Ec2Signer._get_utf8_value(params[key]) val = urllib.parse.quote(val, safe='-_~') pairs.append(urllib.parse.quote(key, safe='') + '=' + val) qs = '&'.join(pairs) return qs def _calc_signature_2(self, params, verb, server_string, path): """Generate AWS signature version 2 string.""" string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path) if self.hmac_256: current_hmac = self.hmac_256 params['SignatureMethod'] = 'HmacSHA256' else: current_hmac = self.hmac params['SignatureMethod'] = 'HmacSHA1' string_to_sign += self._canonical_qs(params) current_hmac.update(string_to_sign.encode('utf-8')) b64 = base64.b64encode(current_hmac.digest()).decode('utf-8') return b64 def _calc_signature_4(self, params, verb, server_string, path, headers, body_hash): """Generate AWS signature version 4 string.""" def sign(key, msg): return hmac.new(key, self._get_utf8_value(msg), hashlib.sha256).digest() def signature_key(datestamp, region_name, service_name): """Signature key derivation. See http://docs.aws.amazon.com/general/latest/gr/ signature-v4-examples.html#signature-v4-examples-python """ k_date = sign(self._get_utf8_value(b"AWS4" + self.secret_key), datestamp) k_region = sign(k_date, region_name) k_service = sign(k_region, service_name) k_signing = sign(k_service, "aws4_request") return k_signing def auth_param(param_name): """Get specified auth parameter. Provided via one of: - the Authorization header - the X-Amz-* query parameters """ try: auth_str = headers['Authorization'] param_str = auth_str.partition( '%s=' % param_name)[2].split(',')[0] except KeyError: param_str = params.get('X-Amz-%s' % param_name) return param_str def date_param(): """Get the X-Amz-Date' value. The value can be either a header or parameter. Note AWS supports parsing the Date header also, but this is not currently supported here as it will require some format mangling So the X-Amz-Date value must be YYYYMMDDTHHMMSSZ format, then it can be used to match against the YYYYMMDD format provided in the credential scope. see: http://docs.aws.amazon.com/general/latest/gr/ sigv4-date-handling.html """ try: return headers['X-Amz-Date'] except KeyError: return params.get('X-Amz-Date') def canonical_header_str(): # Get the list of headers to include, from either # - the Authorization header (SignedHeaders key) # - the X-Amz-SignedHeaders query parameter headers_lower = dict((k.lower().strip(), v.strip()) for (k, v) in headers.items()) # Boto versions < 2.9.3 strip the port component of the host:port # header, so detect the user-agent via the header and strip the # port if we detect an old boto version. FIXME: remove when all # distros package boto >= 2.9.3, this is a transitional workaround user_agent = headers_lower.get('user-agent', '') strip_port = re.match(r'Boto/2\.[0-9]\.[0-2]', user_agent) header_list = [] sh_str = auth_param('SignedHeaders') for h in sh_str.split(';'): if h not in headers_lower: continue if h == 'host' and strip_port: header_list.append('%s:%s' % (h, headers_lower[h].split(':')[0])) continue header_list.append('%s:%s' % (h, headers_lower[h])) return '\n'.join(header_list) + '\n' def canonical_query_str(verb, params): # POST requests pass parameters in through the request body canonical_qs = '' if verb.upper() != 'POST': canonical_qs = self._canonical_qs(params) return canonical_qs # Create canonical request: # http://docs.aws.amazon.com/general/latest/gr/ # sigv4-create-canonical-request.html # Get parameters and headers in expected string format cr = "\n".join((verb.upper(), path, canonical_query_str(verb, params), canonical_header_str(), auth_param('SignedHeaders'), body_hash)) # Check the date, reject any request where the X-Amz-Date doesn't # match the credential scope credential = auth_param('Credential') credential_split = credential.split('/') credential_scope = '/'.join(credential_split[1:]) credential_date = credential_split[1] param_date = date_param() if not param_date.startswith(credential_date): raise Exception(_('Request date mismatch error')) # Create the string to sign # http://docs.aws.amazon.com/general/latest/gr/ # sigv4-create-string-to-sign.html cr = cr.encode('utf-8') string_to_sign = '\n'.join(('AWS4-HMAC-SHA256', param_date, credential_scope, hashlib.sha256(cr).hexdigest())) # Calculate the derived key, this requires a datestamp, region # and service, which can be extracted from the credential scope (req_region, req_service) = credential_split[2:4] s_key = signature_key(credential_date, req_region, req_service) # Finally calculate the signature! signature = hmac.new(s_key, self._get_utf8_value(string_to_sign), hashlib.sha256).hexdigest() return signature python-keystoneclient-4.0.0/keystoneclient/contrib/__init__.py0000664000175000017500000000000013644407055024723 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/auth/0000775000175000017500000000000013644407143023563 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/auth/v3/0000775000175000017500000000000013644407143024113 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/auth/v3/saml2.py0000664000175000017500000011202313644407055025504 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 lxml import etree # nosec(cjschaef): used to create xml, not parse it from oslo_config import cfg from six.moves import urllib from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient import exceptions from keystoneclient.i18n import _ class _BaseSAMLPlugin(v3.AuthConstructor): HTTP_MOVED_TEMPORARILY = 302 HTTP_SEE_OTHER = 303 PROTOCOL = 'saml2' @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) @property def token_url(self): """Return 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 @classmethod def get_options(cls): options = super(_BaseSAMLPlugin, cls).get_options() options.extend([ cfg.StrOpt('identity-provider', help="Identity Provider's name"), cfg.StrOpt('identity-provider-url', help="Identity Provider's URL"), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('password', secret=True, help='Password') ]) return options class Saml2UnscopedTokenAuthMethod(v3.AuthMethod): _method_parameters = [] def get_auth_data(self, session, auth, headers, **kwargs): raise exceptions.MethodNotImplemented(_('This method should never ' 'be called')) class Saml2UnscopedToken(_BaseSAMLPlugin): 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 """ _auth_method_class = Saml2UnscopedTokenAuthMethod SAML2_HEADER_INDEX = 0 ECP_SP_EMPTY_REQUEST_HEADERS = { 'Accept': 'text/html, application/vnd.paos+xml', 'PAOS': ('ver="urn:liberty:paos:2003-08";"urn:oasis:names:tc:' 'SAML:2.0:profiles:SSO:ecp"') } ECP_SP_SAML2_REQUEST_HEADERS = { 'Content-Type': 'application/vnd.paos+xml' } ECP_SAML2_NAMESPACES = { 'ecp': 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp', 'S': 'http://schemas.xmlsoap.org/soap/envelope/', 'paos': 'urn:liberty:paos:2003-08' } ECP_RELAY_STATE = '//ecp:RelayState' ECP_SERVICE_PROVIDER_CONSUMER_URL = ('/S:Envelope/S:Header/paos:Request/' '@responseConsumerURL') ECP_IDP_CONSUMER_URL = ('/S:Envelope/S:Header/ecp:Response/' '@AssertionConsumerServiceURL') SOAP_FAULT = """ S:Server responseConsumerURL from SP and assertionConsumerServiceURL from IdP do not match """ def __init__(self, auth_url, identity_provider, identity_provider_url, username, password, **kwargs): super(Saml2UnscopedToken, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.identity_provider_url = identity_provider_url self._username, self._password = username, password @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value def _handle_http_ecp_redirect(self, session, response, method, **kwargs): if response.status_code not in (self.HTTP_MOVED_TEMPORARILY, self.HTTP_SEE_OTHER): return response location = response.headers['location'] return session.request(location, method, authenticated=False, **kwargs) def _prepare_idp_saml2_request(self, saml2_authn_request): header = saml2_authn_request[self.SAML2_HEADER_INDEX] saml2_authn_request.remove(header) def _check_consumer_urls(self, session, sp_response_consumer_url, idp_sp_response_consumer_url): """Check if consumer URLs issued by SP and IdP are equal. In the initial SAML2 authn Request issued by a Service Provider there is a url called ``consumer url``. A trusted Identity Provider should issue identical url. If the URLs are not equal the federated authn process should be interrupted and the user should be warned. :param session: session object to send out HTTP requests. :type session: keystoneclient.session.Session :param sp_response_consumer_url: consumer URL issued by a SP :type sp_response_consumer_url: string :param idp_sp_response_consumer_url: consumer URL issued by an IdP :type idp_sp_response_consumer_url: string """ if sp_response_consumer_url != idp_sp_response_consumer_url: # send fault message to the SP, discard the response session.post(sp_response_consumer_url, data=self.SOAP_FAULT, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, authenticated=False) # 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': self.token_url, 'sp_consumer_url': sp_response_consumer_url, 'identity_provider': self.identity_provider, 'idp_consumer_url': idp_sp_response_consumer_url } raise exceptions.ValidationError(msg) def _send_service_provider_request(self, session): """Initial HTTP GET request to the SAML2 protected endpoint. It's crucial to include HTTP headers indicating that the client is willing to take advantage of the ECP SAML2 extension and receive data as the SOAP. Unlike standard authentication methods in the OpenStack Identity, the client accesses:: ``/v3/OS-FEDERATION/identity_providers/{identity_providers}/ protocols/{protocol}/auth`` After a successful HTTP call the HTTP response should include SAML2 authn request in the XML format. If a HTTP response contains ``X-Subject-Token`` in the headers and the response body is a valid JSON assume the user was already authenticated and Keystone returned a valid unscoped token. Return True indicating the user was already authenticated. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session """ sp_response = session.get(self.token_url, headers=self.ECP_SP_EMPTY_REQUEST_HEADERS, authenticated=False) if 'X-Subject-Token' in sp_response.headers: self.authenticated_response = sp_response return True try: self.saml2_authn_request = etree.XML(sp_response.content) except etree.XMLSyntaxError as e: msg = _("SAML2: Error parsing XML returned " "from Service Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) relay_state = self.saml2_authn_request.xpath( self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES) self.relay_state = self._first(relay_state) sp_response_consumer_url = self.saml2_authn_request.xpath( self.ECP_SERVICE_PROVIDER_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.sp_response_consumer_url = self._first(sp_response_consumer_url) return False def _send_idp_saml2_authn_request(self, session): """Present modified SAML2 authn assertion from the Service Provider.""" self._prepare_idp_saml2_request(self.saml2_authn_request) idp_saml2_authn_request = self.saml2_authn_request # Currently HTTPBasicAuth method is hardcoded into the plugin idp_response = session.post( self.identity_provider_url, headers={'Content-type': 'text/xml'}, data=etree.tostring(idp_saml2_authn_request), requests_auth=(self.username, self.password), authenticated=False, log=False) try: self.saml2_idp_authn_response = etree.XML(idp_response.content) except etree.XMLSyntaxError as e: msg = _("SAML2: Error parsing XML returned " "from Identity Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) idp_response_consumer_url = self.saml2_idp_authn_response.xpath( self.ECP_IDP_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.idp_response_consumer_url = self._first(idp_response_consumer_url) self._check_consumer_urls(session, self.idp_response_consumer_url, self.sp_response_consumer_url) def _send_service_provider_saml2_authn_response(self, session): """Present SAML2 assertion to the Service Provider. The assertion is issued by a trusted Identity Provider for the authenticated user. This function directs the HTTP request to SP managed URL, for instance: ``https://:/Shibboleth.sso/ SAML2/ECP``. Upon success there's a session created and access to the protected resource is granted. Many implementations of the SP return HTTP 302/303 status code pointing to the protected URL (``https://:/v3/ OS-FEDERATION/identity_providers/{identity_provider}/protocols/ {protocol_id}/auth`` in this case). Saml2 plugin should point to that URL again, with HTTP GET method, expecting an unscoped token. :param session: a session object to send out HTTP requests. """ self.saml2_idp_authn_response[0][0] = self.relay_state response = session.post( self.idp_response_consumer_url, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, data=etree.tostring(self.saml2_idp_authn_response), 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. response = self._handle_http_ecp_redirect( session, response, method='GET', headers=self.ECP_SP_SAML2_REQUEST_HEADERS) self.authenticated_response = response def _get_unscoped_token(self, session): """Get unscoped OpenStack token after federated authentication. This is a multi-step process including multiple HTTP requests. The federated authentication consists of:: * HTTP GET request to the Identity Service (acting as a Service Provider). Client utilizes URL:: ``/v3/OS-FEDERATION/identity_providers/{identity_provider}/ protocols/saml2/auth``. It's crucial to include HTTP headers indicating we are expecting SOAP message in return. Service Provider should respond with such SOAP message. This step is handed by a method ``Saml2UnscopedToken_send_service_provider_request()`` * 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. This step is handed by a method ``Saml2UnscopedToken_send_idp_saml2_authn_request(session)`` * 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. This step is handed by a method ``Saml2UnscopedToken_send_service_provider_saml2_authn_response()`` Unscoped token example:: { "token": { "methods": [ "saml2" ], "user": { "id": "username%40example.com", "name": "username@example.com", "OS-FEDERATION": { "identity_provider": "ACME", "protocol": "saml2", "groups": [ {"id": "abc123"}, {"id": "bcd234"} ] } } } } :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: (token, token_json) """ saml_authenticated = self._send_service_provider_request(session) if not saml_authenticated: self._send_idp_saml2_authn_request(session) self._send_service_provider_saml2_authn_response(session) return (self.authenticated_response.headers['X-Subject-Token'], self.authenticated_response.json()['token']) def get_auth_ref(self, session, **kwargs): """Authenticate via SAML2 protocol and retrieve unscoped token. This is a multi-step process where a client does federated authn receives an unscoped token. Federated authentication utilizing SAML2 Enhanced Client or Proxy extension. See ``Saml2UnscopedToken_get_unscoped_token()`` for more information on that step. Upon successful authentication and assertion mapping an unscoped token is returned and stored within the plugin object for further use. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :return: an object with scoped token's id and unscoped token json included. :rtype: :py:class:`keystoneclient.access.AccessInfoV3` """ token, token_json = self._get_unscoped_token(session) return access.AccessInfoV3(token, **token_json) class ADFSUnscopedToken(_BaseSAMLPlugin): """Authentication plugin for Microsoft ADFS2.0 IdPs. :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 username: User's login :type username: string :param password: User's password :type password: string """ _auth_method_class = Saml2UnscopedTokenAuthMethod 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, **kwargs): super(ADFSUnscopedToken, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.identity_provider_url = identity_provider_url self.service_provider_endpoint = service_provider_endpoint self._username, self._password = username, password @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value @classmethod def get_options(cls): options = super(ADFSUnscopedToken, cls).get_options() options.extend([ cfg.StrOpt('service-provider-endpoint', help="Service Provider's Endpoint") ]) return options def _cookies(self, session): """Check if cookie jar is not empty. keystoneclient.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: # nosec(cjschaef): fetch cookies from # underylying requests.Session object, or fail trying 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 ``keystoneclient`` doesn't have mechanisms for reusing such tokens (every time ADFS authn method is called, keystoneclient 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_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: keystoneclient.session.Session :raises keystoneclient.exceptions.AuthorizationFailure: when HTTP response from the ADFS server is not a valid XML ADFS security token. :raises keystoneclient.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: keystoneclient.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: keystoneclient.session.Session :raises keystoneclient.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.token_url, authenticated=False) def _get_unscoped_token(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) . Step handled by ``ADFSUnscopedToken._prepare_adfs_request()`` method. * Send ADFS Security token to the ADFS server. Step handled by ``ADFSUnscopedToken._get_adfs_security_token()`` method. * 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 ``ADFSUnscopedToken._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 ``ADFSUnscopedToken._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 ``ADFSUnscopedToken._access_service_provider()`` method. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: (Unscoped federated token, token JSON body) """ 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) try: return (self.authenticated_response.headers['X-Subject-Token'], self.authenticated_response.json()['token']) except (KeyError, ValueError): raise exceptions.InvalidResponse( response=self.authenticated_response) def get_auth_ref(self, session, **kwargs): token, token_json = self._get_unscoped_token(session) return access.AccessInfoV3(token, **token_json) class Saml2ScopedTokenMethod(v3.TokenMethod): _method_name = 'saml2' def get_auth_data(self, session, auth, headers, **kwargs): """Build and return request body for token scoping step.""" t = super(Saml2ScopedTokenMethod, self).get_auth_data( session, auth, headers, **kwargs) _token_method, token = t return self._method_name, token class Saml2ScopedToken(v3.Token): """Class for scoping unscoped saml2 token.""" _auth_method_class = Saml2ScopedTokenMethod def __init__(self, auth_url, token, **kwargs): super(Saml2ScopedToken, self).__init__(auth_url, token, **kwargs) if not (self.project_id or self.domain_id): raise exceptions.ValidationError( _('Neither project nor domain specified')) python-keystoneclient-4.0.0/keystoneclient/contrib/auth/v3/__init__.py0000664000175000017500000000000013644407055026214 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/contrib/auth/v3/oidc.py0000664000175000017500000002106313644407055025407 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import access from keystoneclient.auth.identity.v3 import federated class OidcPassword(federated.FederatedBaseAuth): """Implement authentication plugin for OpenID Connect protocol. OIDC or OpenID Connect is a protocol for federated authentication. The OpenID Connect specification can be found at:: ``http://openid.net/specs/openid-connect-core-1_0.html`` """ @classmethod def get_options(cls): options = super(OidcPassword, cls).get_options() options.extend([ cfg.StrOpt('username', help='Username'), cfg.StrOpt('password', secret=True, help='Password'), cfg.StrOpt('client-id', help='OAuth 2.0 Client ID'), cfg.StrOpt('client-secret', secret=True, help='OAuth 2.0 Client Secret'), cfg.StrOpt('access-token-endpoint', help='OpenID Connect Provider Token Endpoint'), cfg.StrOpt('scope', default="profile", help='OpenID Connect scope that is requested from OP') ]) return options def __init__(self, auth_url, identity_provider, protocol, username, password, client_id, client_secret, access_token_endpoint, scope='profile', grant_type='password'): """The OpenID Connect plugin. 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 username: Username used to authenticate :type username: string :param password: Password used to authenticate :type password: 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_endpoint: OpenID Connect Provider Token Endpoint, for example: https://localhost:8020/oidc/OP/token :type access_token_endpoint: string :param scope: OpenID Connect scope that is requested from OP, defaults to "profile", for example: "profile email" :type scope: string :param grant_type: OpenID Connect grant type, it represents the flow that is used to talk to the OP. Valid values are: "authorization_code", "refresh_token", or "password". :type grant_type: string """ super(OidcPassword, self).__init__(auth_url, identity_provider, protocol) self._username = username self._password = password self.client_id = client_id self.client_secret = client_secret self.access_token_endpoint = access_token_endpoint self.scope = scope self.grant_type = grant_type @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value def get_unscoped_auth_ref(self, session): """Authenticate with OpenID Connect and get back claims. This is a multi-step process. First an access token must be retrieved, to do this, the username and password, the OpenID Connect client ID and secret, and the access token endpoint must be known. Secondly, 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: keystoneclient.session.Session :returns: a token data representation :rtype: :py:class:`keystoneclient.access.AccessInfo` """ # get an access token client_auth = (self.client_id, self.client_secret) payload = {'grant_type': self.grant_type, 'username': self.username, 'password': self.password, 'scope': self.scope} response = self._get_access_token(session, client_auth, payload, self.access_token_endpoint) access_token = response.json()['access_token'] # use access token against protected URL headers = {'Authorization': 'Bearer ' + access_token} response = self._get_keystone_token(session, headers, self.federated_token_url) # grab the unscoped token token = response.headers['X-Subject-Token'] token_json = response.json()['token'] return access.AccessInfoV3(token, **token_json) def _get_access_token(self, session, client_auth, payload, access_token_endpoint): """Exchange a variety of user supplied values for an access token. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session :param client_auth: a tuple representing client id and secret :type client_auth: tuple :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 :param access_token_endpoint: URL to use to get an access token, for example: https://localhost/oidc/token :type access_token_endpoint: string """ op_response = session.post(self.access_token_endpoint, requests_auth=client_auth, data=payload, authenticated=False) return op_response def _get_keystone_token(self, session, headers, federated_token_url): r"""Exchange an acess 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: keystoneclient.session.Session :param headers: an Authorization header containing the access token. :type headers_: dict :param federated_auth_url: Protected URL for federated authentication, for example: https://localhost:5000/v3/\ OS-FEDERATION/identity_providers/bluepages/\ protocols/oidc/auth :type federated_auth_url: string """ auth_response = session.post(self.federated_token_url, headers=headers, authenticated=False) return auth_response python-keystoneclient-4.0.0/keystoneclient/contrib/auth/__init__.py0000664000175000017500000000000013644407055025664 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/0000775000175000017500000000000013644407143022324 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/__init__.py0000664000175000017500000000000013644407055024425 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/functional/0000775000175000017500000000000013644407143024466 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/0000775000175000017500000000000013644407143025016 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_endpoint_groups.py0000664000175000017500000001160113644407055031647 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EndpointGroupsTestMixin(object): def check_endpoint_group(self, endpoint_group, endpoint_group_ref=None): self.assertIsNotNone(endpoint_group.id) self.assertIn('self', endpoint_group.links) self.assertIn('/endpoint_groups/' + endpoint_group.id, endpoint_group.links['self']) if endpoint_group_ref: self.assertEqual(endpoint_group_ref['name'], endpoint_group.name) self.assertEqual(endpoint_group_ref['filters'], endpoint_group.filters) # There is no guarantee description is present in endpoint groups if hasattr(endpoint_group_ref, 'description'): self.assertEqual(endpoint_group_ref['description'], endpoint_group.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(endpoint_group.name) self.assertIsNotNone(endpoint_group.filters) class EndpointGroupsTestCase(base.V3ClientTestCase, EndpointGroupsTestMixin): def test_create_endpoint_group(self): endpoint_group_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'filters': {'interface': 'internal'}, 'description': uuid.uuid4().hex} endpoint_group = self.client.endpoint_groups.create( **endpoint_group_ref) self.addCleanup(self.client.endpoint_groups.delete, endpoint_group) self.check_endpoint_group(endpoint_group, endpoint_group_ref) def test_get_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) endpoint_ret = self.client.endpoint_groups.get(endpoint_group.id) self.check_endpoint_group(endpoint_ret, endpoint_group.ref) self.assertRaises(http.NotFound, self.client.endpoint_groups.get, uuid.uuid4().hex) def test_check_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) self.client.endpoint_groups.check(endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.check, uuid.uuid4().hex) def test_list_endpoint_groups(self): endpoint_group_one = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_one) endpoint_group_two = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_two) endpoint_groups = self.client.endpoint_groups.list() # All endpoints are valid for endpoint_group in endpoint_groups: self.check_endpoint_group(endpoint_group) self.assertIn(endpoint_group_one.entity, endpoint_groups) self.assertIn(endpoint_group_two.entity, endpoint_groups) def test_update_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_filters = {'interface': 'public'} new_description = uuid.uuid4().hex endpoint_group_ret = self.client.endpoint_groups.update( endpoint_group, name=new_name, filters=new_filters, description=new_description) endpoint_group.ref.update({'name': new_name, 'filters': new_filters, 'description': new_description}) self.check_endpoint_group(endpoint_group_ret, endpoint_group.ref) def test_delete_endpoint_group(self): endpoint_group = self.client.endpoint_groups.create( name=fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, filters={'interface': 'admin'}, description=uuid.uuid4().hex) self.client.endpoint_groups.delete(endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.check, endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.get, endpoint_group.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_endpoint_filters.py0000664000175000017500000000656313644407055032013 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures from keystoneclient.tests.functional.v3 import test_endpoint_groups from keystoneclient.tests.functional.v3 import test_projects class EndpointFiltersTestCase(base.V3ClientTestCase, test_endpoint_groups.EndpointGroupsTestMixin, test_projects.ProjectsTestMixin): def setUp(self): super(EndpointFiltersTestCase, self).setUp() self.project = fixtures.Project(self.client) self.endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(self.project) self.useFixture(self.endpoint_group) self.client.endpoint_filter.add_endpoint_group_to_project( self.endpoint_group, self.project) def test_add_endpoint_group_to_project(self): project = fixtures.Project(self.client) endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(project) self.useFixture(endpoint_group) self.client.endpoint_filter.add_endpoint_group_to_project( endpoint_group, project) self.client.endpoint_filter.check_endpoint_group_in_project( endpoint_group, project) def test_delete_endpoint_group_from_project(self): self.client.endpoint_filter.delete_endpoint_group_from_project( self.endpoint_group, self.project) self.assertRaises( http.NotFound, self.client.endpoint_filter.check_endpoint_group_in_project, self.endpoint_group, self.project) def test_list_endpoint_groups_for_project(self): endpoint_group_two = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_two) self.client.endpoint_filter.add_endpoint_group_to_project( endpoint_group_two, self.project) endpoint_groups = ( self.client.endpoint_filter.list_endpoint_groups_for_project( self.project ) ) for endpoint_group in endpoint_groups: self.check_endpoint_group(endpoint_group) self.assertIn(self.endpoint_group.entity, endpoint_groups) self.assertIn(endpoint_group_two.entity, endpoint_groups) def test_list_projects_for_endpoint_group(self): project_two = fixtures.Project(self.client) self.useFixture(project_two) self.client.endpoint_filter.add_endpoint_group_to_project( self.endpoint_group, project_two) f = self.client.endpoint_filter.list_projects_for_endpoint_group projects = f(self.endpoint_group) for project in projects: self.check_project(project) self.assertIn(self.project.entity, projects) self.assertIn(project_two.entity, projects) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/client_fixtures.py0000664000175000017500000001736713644407055030617 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import uuid RESOURCE_NAME_PREFIX = 'keystoneclient-functional-' class Base(fixtures.Fixture): def __init__(self, client, domain_id=None): super(Base, self).__init__() self.client = client self.domain_id = domain_id self.ref = None self.entity = None def __getattr__(self, name): """Return the attribute from the represented entity.""" return getattr(self.entity, name) class User(Base): def setUp(self): super(User, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id} self.entity = self.client.users.create(**self.ref) self.addCleanup(self.client.users.delete, self.entity) class Group(Base): def setUp(self): super(Group, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id} self.entity = self.client.groups.create(**self.ref) self.addCleanup(self.client.groups.delete, self.entity) class Domain(Base): def setUp(self): super(Domain, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'description': uuid.uuid4().hex, 'enabled': True} self.entity = self.client.domains.create(**self.ref) # Only disabled domains can be deleted self.addCleanup(self.client.domains.delete, self.entity) self.addCleanup(self.client.domains.update, self.entity, enabled=False) class Project(Base): def __init__(self, client, domain_id=None, parent=None, tags=None): super(Project, self).__init__(client, domain_id) self.parent = parent self.tags = tags if tags else [] def setUp(self): super(Project, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id, 'enabled': True, 'parent': self.parent, 'tags': self.tags} self.entity = self.client.projects.create(**self.ref) self.addCleanup(self.client.projects.delete, self.entity) class Role(Base): def __init__(self, client, name=None, domain=None): super(Role, self).__init__(client) self.name = name or RESOURCE_NAME_PREFIX + uuid.uuid4().hex self.domain = domain def setUp(self): super(Role, self).setUp() self.ref = {'name': self.name, 'domain': self.domain} self.entity = self.client.roles.create(**self.ref) self.addCleanup(self.client.roles.delete, self.entity) class InferenceRule(Base): def __init__(self, client, prior_role, implied_role): super(InferenceRule, self).__init__(client) self.prior_role = prior_role self.implied_role = implied_role def setUp(self): super(InferenceRule, self).setUp() self.ref = {'prior_role': self.prior_role, 'implied_role': self.implied_role} self.entity = self.client.inference_rules.create(**self.ref) self.addCleanup(self.client.inference_rules.delete, self.prior_role, self.implied_role) class Service(Base): def setUp(self): super(Service, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'type': uuid.uuid4().hex, 'enabled': True, 'description': uuid.uuid4().hex} self.entity = self.client.services.create(**self.ref) self.addCleanup(self.client.services.delete, self.entity) class Policy(Base): def setUp(self): super(Policy, self).setUp() self.ref = {'blob': uuid.uuid4().hex, 'type': uuid.uuid4().hex} self.entity = self.client.policies.create(**self.ref) self.addCleanup(self.client.policies.delete, self.entity) class Region(Base): def __init__(self, client, parent_region=None): super(Region, self).__init__(client) self.parent_region = parent_region def setUp(self): super(Region, self).setUp() self.ref = {'description': uuid.uuid4().hex, 'parent_region': self.parent_region} self.entity = self.client.regions.create(**self.ref) self.addCleanup(self.client.regions.delete, self.entity) class Endpoint(Base): def __init__(self, client, service, interface, region=None): super(Endpoint, self).__init__(client) self.service = service self.interface = interface self.region = region def setUp(self): super(Endpoint, self).setUp() self.ref = {'service': self.service, 'url': 'http://' + uuid.uuid4().hex, 'enabled': True, 'interface': self.interface, 'region': self.region} self.entity = self.client.endpoints.create(**self.ref) self.addCleanup(self.client.endpoints.delete, self.entity) class EndpointGroup(Base): def setUp(self): super(EndpointGroup, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'filters': {'interface': 'public'}, 'description': uuid.uuid4().hex} self.entity = self.client.endpoint_groups.create(**self.ref) self.addCleanup(self.client.endpoint_groups.delete, self.entity) class Credential(Base): def __init__(self, client, user, type, project=None): super(Credential, self).__init__(client) self.user = user self.type = type self.project = project if type == 'ec2': self.blob = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") else: self.blob = uuid.uuid4().hex def setUp(self): super(Credential, self).setUp() self.ref = {'user': self.user, 'type': self.type, 'blob': self.blob, 'project': self.project} self.entity = self.client.credentials.create(**self.ref) self.addCleanup(self.client.credentials.delete, self.entity) class EC2(Base): def __init__(self, client, user_id, project_id): super(EC2, self).__init__(client) self.user_id = user_id self.project_id = project_id def setUp(self): super(EC2, self).setUp() self.ref = {'user_id': self.user_id, 'project_id': self.project_id} self.entity = self.client.ec2.create(**self.ref) self.addCleanup(self.client.ec2.delete, self.user_id, self.entity.access) class DomainConfig(Base): def __init__(self, client, domain_id): super(DomainConfig, self).__init__(client, domain_id=domain_id) self.domain_id = domain_id def setUp(self): super(DomainConfig, self).setUp() self.ref = {'identity': {'driver': uuid.uuid4().hex}, 'ldap': {'url': uuid.uuid4().hex}} self.entity = self.client.domain_configs.create( self.domain_id, self.ref) self.addCleanup(self.client.domain_configs.delete, self.domain_id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/__init__.py0000664000175000017500000000000013644407055027117 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_credentials.py0000664000175000017500000002012713644407055030730 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class CredentialsTestCase(base.V3ClientTestCase): def setUp(self): super(CredentialsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) def check_credential(self, credential, credential_ref=None): self.assertIsNotNone(credential.id) self.assertIn('self', credential.links) self.assertIn('/credentials/' + credential.id, credential.links['self']) if credential_ref: self.assertEqual(credential_ref['user'], credential.user_id) self.assertEqual(credential_ref['type'], credential.type) self.assertEqual(credential_ref['blob'], credential.blob) # There is no guarantee below attributes are present in credential if credential_ref['type'] == 'ec2' or hasattr(credential_ref, 'project'): self.assertEqual(credential_ref['project'], credential.project_id) else: # Only check remaining mandatory attributes self.assertIsNotNone(credential.user_id) self.assertIsNotNone(credential.type) self.assertIsNotNone(credential.blob) if credential.type == 'ec2': self.assertIsNotNone(credential.project_id) def test_create_credential_of_cert_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, 'type': 'cert', 'blob': uuid.uuid4().hex} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_create_credential_of_ec2_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) # project is mandatory attribute if the credential type is ec2 credential_ref = {'user': user.id, 'type': 'ec2', 'blob': ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}")} self.assertRaises(http.BadRequest, self.client.credentials.create, **credential_ref) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) credential_ref = {'user': user.id, 'type': 'ec2', 'blob': ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}"), 'project': project.id} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_create_credential_of_totp_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, 'type': 'totp', 'blob': uuid.uuid4().hex} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_get_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: credential = fixtures.Credential(self.client, user=user.id, type=credential_type, project=project.id) self.useFixture(credential) credential_ret = self.client.credentials.get(credential.id) self.check_credential(credential_ret, credential.ref) def test_list_credentials(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) cert_credential = fixtures.Credential(self.client, user=user.id, type='cert') self.useFixture(cert_credential) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) ec2_credential = fixtures.Credential(self.client, user=user.id, type='ec2', project=project.id) self.useFixture(ec2_credential) totp_credential = fixtures.Credential(self.client, user=user.id, type='totp') self.useFixture(totp_credential) credentials = self.client.credentials.list() # All credentials are valid for credential in credentials: self.check_credential(credential) self.assertIn(cert_credential.entity, credentials) self.assertIn(ec2_credential.entity, credentials) self.assertIn(totp_credential.entity, credentials) def test_update_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) new_user = fixtures.User(self.client, self.test_domain.id) self.useFixture(new_user) new_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(new_project) credential = fixtures.Credential(self.client, user=user.id, type='cert') self.useFixture(credential) new_type = 'ec2' new_blob = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") credential_ret = self.client.credentials.update(credential.id, user=new_user.id, type=new_type, blob=new_blob, project=new_project.id) credential.ref.update({'user': new_user.id, 'type': new_type, 'blob': new_blob, 'project': new_project.id}) self.check_credential(credential_ret, credential.ref) def test_delete_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: if credential_type == 'ec2': blob_value = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") else: blob_value = uuid.uuid4().hex credential = self.client.credentials.create(user=user.id, type=credential_type, blob=blob_value, project=project.id) self.client.credentials.delete(credential.id) self.assertRaises(http.NotFound, self.client.credentials.get, credential.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_regions.py0000664000175000017500000000575313644407055030111 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class RegionsTestCase(base.V3ClientTestCase): def check_region(self, region, region_ref=None): self.assertIsNotNone(region.id) self.assertIn('self', region.links) self.assertIn('/regions/' + region.id, region.links['self']) # There is no guarantee the below attributes are present in region if hasattr(region_ref, 'description'): self.assertEqual(region_ref['description'], region.description) if hasattr(region_ref, 'parent_region'): self.assertEqual( region_ref['parent_region'], region.parent_region) def test_create_region(self): region_ref = {'description': uuid.uuid4().hex} region = self.client.regions.create(**region_ref) self.addCleanup(self.client.regions.delete, region) self.check_region(region, region_ref) def test_get_region(self): region = fixtures.Region(self.client) self.useFixture(region) region_ret = self.client.regions.get(region.id) self.check_region(region_ret, region.ref) def test_list_regions(self): region_one = fixtures.Region(self.client) self.useFixture(region_one) region_two = fixtures.Region(self.client, parent_region=region_one.id) self.useFixture(region_two) regions = self.client.regions.list() # All regions are valid for region in regions: self.check_region(region) self.assertIn(region_one.entity, regions) self.assertIn(region_two.entity, regions) def test_update_region(self): parent = fixtures.Region(self.client) self.useFixture(parent) region = fixtures.Region(self.client) self.useFixture(region) new_description = uuid.uuid4().hex region_ret = self.client.regions.update(region.id, description=new_description, parent_region=parent.id) self.check_region(region_ret, region.ref) def test_delete_region(self): region = self.client.regions.create() self.client.regions.delete(region.id) self.assertRaises(http.NotFound, self.client.regions.get, region.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_policies.py0000664000175000017500000000605013644407055030241 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class PoliciesTestCase(base.V3ClientTestCase): def check_policy(self, policy, policy_ref=None): self.assertIsNotNone(policy.id) self.assertIn('self', policy.links) self.assertIn('/policies/' + policy.id, policy.links['self']) if policy_ref: self.assertEqual(policy_ref['blob'], policy.blob) self.assertEqual(policy_ref['type'], policy.type) else: # Only check remaining mandatory attributes self.assertIsNotNone(policy.blob) self.assertIsNotNone(policy.type) def test_create_policy(self): policy_ref = {'blob': uuid.uuid4().hex, 'type': uuid.uuid4().hex} policy = self.client.policies.create(**policy_ref) self.addCleanup(self.client.policies.delete, policy) self.check_policy(policy, policy_ref) def test_get_policy(self): policy = fixtures.Policy(self.client) self.useFixture(policy) policy_ret = self.client.policies.get(policy.id) self.check_policy(policy_ret, policy.ref) def test_list_policies(self): policy_one = fixtures.Policy(self.client) self.useFixture(policy_one) policy_two = fixtures.Policy(self.client) self.useFixture(policy_two) policies = self.client.policies.list() # All policies are valid for policy in policies: self.check_policy(policy) self.assertIn(policy_one.entity, policies) self.assertIn(policy_two.entity, policies) def test_update_policy(self): policy = fixtures.Policy(self.client) self.useFixture(policy) new_blob = uuid.uuid4().hex new_type = uuid.uuid4().hex policy_ret = self.client.policies.update(policy.id, blob=new_blob, type=new_type) policy.ref.update({'blob': new_blob, 'type': new_type}) self.check_policy(policy_ret, policy.ref) def test_delete_policy(self): policy = self.client.policies.create(blob=uuid.uuid4().hex, type=uuid.uuid4().hex) self.client.policies.delete(policy.id) self.assertRaises(http.NotFound, self.client.policies.get, policy.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_users.py0000664000175000017500000001235613644407055027601 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class UsersTestCase(base.V3ClientTestCase): def check_user(self, user, user_ref=None): self.assertIsNotNone(user.id) self.assertIsNotNone(user.enabled) self.assertIn('self', user.links) self.assertIn('/users/' + user.id, user.links['self']) if user_ref: self.assertEqual(user_ref['name'], user.name) self.assertEqual(user_ref['domain'], user.domain_id) # There is no guarantee the attributes below are present in user if hasattr(user_ref, 'description'): self.assertEqual(user_ref['description'], user.description) if hasattr(user_ref, 'email'): self.assertEqual(user_ref['email'], user.email) if hasattr(user_ref, 'default_project'): self.assertEqual(user_ref['default_project'], user.default_project_id) else: # Only check remaining mandatory attributes self.assertIsNotNone(user.name) self.assertIsNotNone(user.domain_id) def test_create_user(self): user_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user = self.client.users.create(**user_ref) self.addCleanup(self.client.users.delete, user) self.check_user(user, user_ref) def test_get_user(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) user_ret = self.client.users.get(user.id) self.check_user(user_ret, user.ref) def test_list_users(self): user_one = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_one) user_two = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_two) users = self.client.users.list() # All users are valid for user in users: self.check_user(user) self.assertIn(user_one.entity, users) self.assertIn(user_two.entity, users) def test_list_users_with_filters(self): suffix = uuid.uuid4().hex user1_ref = { 'name': 'test_user' + suffix, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user2_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user1 = self.client.users.create(**user1_ref) self.client.users.create(**user2_ref) users = self.client.users.list(name__contains=['test_user', suffix]) self.assertEqual(1, len(users)) self.assertIn(user1, users) def test_update_user(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) new_description = uuid.uuid4().hex user_ret = self.client.users.update(user.id, description=new_description) user.ref.update({'description': new_description}) self.check_user(user_ret, user.ref) def test_user_grouping(self): # keystoneclient.v3.users owns user grouping operations, this is why # this test case belongs to this class user = fixtures.User(self.client, self.project_domain_id) group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(user) self.useFixture(group) self.assertRaises(http.NotFound, self.client.users.check_in_group, user.id, group.id) self.client.users.add_to_group(user.id, group.id) self.client.users.check_in_group(user.id, group.id) self.client.users.remove_from_group(user.id, group.id) self.assertRaises(http.NotFound, self.client.users.check_in_group, user.id, group.id) def test_delete_user(self): user = self.client.users.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.users.delete(user.id) self.assertRaises(http.NotFound, self.client.users.get, user.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_services.py0000664000175000017500000000764413644407055030267 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class ServicesTestCase(base.V3ClientTestCase): def check_service(self, service, service_ref=None): self.assertIsNotNone(service.id) self.assertIn('self', service.links) self.assertIn('/services/' + service.id, service.links['self']) if service_ref: self.assertEqual(service_ref['name'], service.name) self.assertEqual(service_ref['enabled'], service.enabled) self.assertEqual(service_ref['type'], service.type) # There is no guarantee description is present in service if hasattr(service_ref, 'description'): self.assertEqual(service_ref['description'], service.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(service.name) self.assertIsNotNone(service.enabled) self.assertIsNotNone(service.type) def test_create_service(self): service_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'type': uuid.uuid4().hex, 'enabled': True, 'description': uuid.uuid4().hex} service = self.client.services.create(**service_ref) self.addCleanup(self.client.services.delete, service) self.check_service(service, service_ref) def test_get_service(self): service = fixtures.Service(self.client) self.useFixture(service) service_ret = self.client.services.get(service.id) self.check_service(service_ret, service.ref) def test_list_services(self): service_one = fixtures.Service(self.client) self.useFixture(service_one) service_two = fixtures.Service(self.client) self.useFixture(service_two) services = self.client.services.list() # All services are valid for service in services: self.check_service(service) self.assertIn(service_one.entity, services) self.assertIn(service_two.entity, services) def test_update_service(self): service = fixtures.Service(self.client) self.useFixture(service) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_type = uuid.uuid4().hex new_enabled = False new_description = uuid.uuid4().hex service_ret = self.client.services.update(service.id, name=new_name, type=new_type, enabled=new_enabled, description=new_description) service.ref.update({'name': new_name, 'type': new_type, 'enabled': new_enabled, 'description': new_description}) self.check_service(service_ret, service.ref) def test_delete_service(self): service = self.client.services.create(name=uuid.uuid4().hex, type=uuid.uuid4().hex) self.client.services.delete(service.id) self.assertRaises(http.NotFound, self.client.services.get, service.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_domains.py0000664000175000017500000000712313644407055030066 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class DomainsTestCase(base.V3ClientTestCase): def check_domain(self, domain, domain_ref=None): self.assertIsNotNone(domain.id) self.assertIn('self', domain.links) self.assertIn('/domains/' + domain.id, domain.links['self']) if domain_ref: self.assertEqual(domain_ref['name'], domain.name) self.assertEqual(domain_ref['enabled'], domain.enabled) # There is no guarantee description is present in domain if hasattr(domain_ref, 'description'): self.assertEqual(domain_ref['description'], domain.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(domain.name) self.assertIsNotNone(domain.enabled) def test_create_domain(self): domain_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'description': uuid.uuid4().hex, 'enabled': True} domain = self.client.domains.create(**domain_ref) self.check_domain(domain, domain_ref) # Only disabled domains can be deleted self.addCleanup(self.client.domains.delete, domain) self.addCleanup(self.client.domains.update, domain, enabled=False) def test_get_domain(self): domain_id = self.project_domain_id domain_ret = self.client.domains.get(domain_id) self.check_domain(domain_ret) def test_list_domains(self): domain_one = fixtures.Domain(self.client) self.useFixture(domain_one) domain_two = fixtures.Domain(self.client) self.useFixture(domain_two) domains = self.client.domains.list() # All domains are valid for domain in domains: self.check_domain(domain) self.assertIn(domain_one.entity, domains) self.assertIn(domain_two.entity, domains) def test_update_domain(self): domain = fixtures.Domain(self.client) self.useFixture(domain) new_description = uuid.uuid4().hex domain_ret = self.client.domains.update(domain.id, description=new_description) domain.ref.update({'description': new_description}) self.check_domain(domain_ret, domain.ref) def test_delete_domain(self): domain = self.client.domains.create(name=uuid.uuid4().hex, description=uuid.uuid4().hex, enabled=True) # Only disabled domains can be deleted self.assertRaises(http.Forbidden, self.client.domains.delete, domain.id) self.client.domains.update(domain, enabled=False) self.client.domains.delete(domain.id) self.assertRaises(http.NotFound, self.client.domains.get, domain.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_groups.py0000664000175000017500000000663213644407055027757 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class GroupsTestCase(base.V3ClientTestCase): def check_group(self, group, group_ref=None): self.assertIsNotNone(group.id) self.assertIn('self', group.links) self.assertIn('/groups/' + group.id, group.links['self']) if group_ref: self.assertEqual(group_ref['name'], group.name) self.assertEqual(group_ref['domain'], group.domain_id) # There is no guarantee description is present in group if hasattr(group_ref, 'description'): self.assertEqual(group_ref['description'], group.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(group.name) self.assertIsNotNone(group.domain_id) def test_create_group(self): group_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'description': uuid.uuid4().hex} group = self.client.groups.create(**group_ref) self.addCleanup(self.client.groups.delete, group) self.check_group(group, group_ref) def test_get_group(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) group_ret = self.client.groups.get(group.id) self.check_group(group_ret, group.ref) def test_list_groups(self): group_one = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group_one) group_two = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group_two) groups = self.client.groups.list() # All groups are valid for group in groups: self.check_group(group) self.assertIn(group_one.entity, groups) self.assertIn(group_two.entity, groups) def test_update_group(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_description = uuid.uuid4().hex group_ret = self.client.groups.update(group.id, name=new_name, description=new_description) group.ref.update({'name': new_name, 'description': new_description}) self.check_group(group_ret, group.ref) def test_delete_group(self): group = self.client.groups.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.groups.delete(group.id) self.assertRaises(http.NotFound, self.client.groups.get, group.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_projects.py0000664000175000017500000004262713644407055030275 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient import exceptions from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class ProjectsTestMixin(object): def check_project(self, project, project_ref=None): self.assertIsNotNone(project.id) self.assertIn('self', project.links) self.assertIn('/projects/' + project.id, project.links['self']) if project_ref: self.assertEqual(project_ref['name'], project.name) self.assertEqual(project_ref['domain'], project.domain_id) self.assertEqual(project_ref['enabled'], project.enabled) # There is no guarantee the attributes below are present in project if hasattr(project_ref, 'description'): self.assertEqual(project_ref['description'], project.description) if hasattr(project_ref, 'parent'): self.assertEqual(project_ref['parent'], project.parent) else: # Only check remaining mandatory attributes self.assertIsNotNone(project.name) self.assertIsNotNone(project.domain_id) self.assertIsNotNone(project.enabled) class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin): def setUp(self): super(ProjectsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) self.test_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(self.test_project) self.special_tag = '~`!@#$%^&*()-_+=<>.? \'"' def test_create_subproject(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'parent': self.test_project.id} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) self.check_project(project, project_ref) def test_create_project(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) self.check_project(project, project_ref) def test_get_project(self): project_ret = self.client.projects.get(self.test_project.id) self.check_project(project_ret, self.test_project.ref) def test_get_project_invalid_params(self): self.assertRaises(exceptions.ValidationError, self.client.projects.get, self.test_project.id, subtree_as_list=True, subtree_as_ids=True) self.assertRaises(exceptions.ValidationError, self.client.projects.get, self.test_project.id, parents_as_list=True, parents_as_ids=True) def test_get_hierarchy_as_list(self): project = fixtures.Project(self.client, self.test_domain.id, parent=self.test_project.id) self.useFixture(project) child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) # Only parents and subprojects that the current user has role # assingments on are returned when asked for subtree_as_list and # parents_as_list. role = fixtures.Role(self.client) self.useFixture(role) self.client.roles.grant(role.id, user=self.user_id, project=self.test_project.id) self.client.roles.grant(role.id, user=self.user_id, project=project.id) self.client.roles.grant(role.id, user=self.user_id, project=child_project.id) project_ret = self.client.projects.get(project.id, subtree_as_list=True, parents_as_list=True) self.check_project(project_ret, project.ref) self.assertItemsEqual( [{'project': self.test_project.entity.to_dict()}], project_ret.parents) self.assertItemsEqual( [{'project': child_project.entity.to_dict()}], project_ret.subtree) def test_get_hierarchy_as_ids(self): project = fixtures.Project(self.client, self.test_domain.id, parent=self.test_project.id) self.useFixture(project) child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) project_ret = self.client.projects.get(project.id, subtree_as_ids=True, parents_as_ids=True) self.assertItemsEqual([self.test_project.id], project_ret.parents) self.assertItemsEqual([child_project.id], project_ret.subtree) def test_list_projects(self): project_one = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_one) project_two = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_two) projects = self.client.projects.list() # All projects are valid for project in projects: self.check_project(project) self.assertIn(project_one.entity, projects) self.assertIn(project_two.entity, projects) def test_list_subprojects(self): parent_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(parent_project) child_project_one = fixtures.Project(self.client, self.test_domain.id, parent=parent_project.id) self.useFixture(child_project_one) child_project_two = fixtures.Project(self.client, self.test_domain.id, parent=parent_project.id) self.useFixture(child_project_two) projects = self.client.projects.list(parent=parent_project.id) # All projects are valid for project in projects: self.check_project(project) self.assertIn(child_project_one.entity, projects) self.assertIn(child_project_two.entity, projects) # Parent project should not be included in the result self.assertNotIn(parent_project.entity, projects) def test_update_project(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_description = uuid.uuid4().hex project_ret = self.client.projects.update(project.id, name=new_name, enabled=False, description=new_description) project.ref.update({'name': new_name, 'enabled': False, 'description': new_description}) self.check_project(project_ret, project.ref) def test_update_project_domain_not_allowed(self): domain = fixtures.Domain(self.client) self.useFixture(domain) # Cannot update domain after project is created. self.assertRaises(http.BadRequest, self.client.projects.update, self.test_project.id, domain=domain.id) def test_delete_project(self): project = self.client.projects.create(name=uuid.uuid4().hex, domain=self.test_domain.id, enabled=True) self.client.projects.delete(project.id) self.assertRaises(http.NotFound, self.client.projects.get, project.id) def test_list_projects_with_tag_filters(self): project_one = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) project_two = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', 'tag2']) project_three = fixtures.Project( self.client, self.test_domain.id, tags=['tag2', 'tag3']) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) projects = self.client.projects.list(tags='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertIn(project_one.id, project_ids) projects = self.client.projects.list(tags_any='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertIn(project_one.id, project_ids) self.assertIn(project_two.id, project_ids) projects = self.client.projects.list(not_tags='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) projects = self.client.projects.list(not_tags_any='tag1,tag2') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) self.assertNotIn(project_two.id, project_ids) self.assertNotIn(project_three.id, project_ids) projects = self.client.projects.list(tags='tag1,tag2') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) self.assertIn(project_two.id, project_ids) self.assertNotIn(project_three.id, project_ids) def test_add_tag(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) project.add_tag('tag1') tags = self.client.projects.get(project.id).tags self.assertEqual(['tag1'], tags) # verify there is an error when you try to add the same tag self.assertRaises(http.BadRequest, project.add_tag, 'tag1') def test_update_tags(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) project.update_tags(['tag1', 'tag2', self.special_tag]) tags = self.client.projects.get(project.id).tags self.assertIn('tag1', tags) self.assertIn('tag2', tags) self.assertIn(self.special_tag, tags) self.assertEqual(3, len(tags)) project.update_tags([]) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) # cannot have duplicate tags in update self.assertRaises(http.BadRequest, project.update_tags, ['tag1', 'tag1']) def test_delete_tag(self): project = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', self.special_tag]) self.useFixture(project) project.delete_tag('tag1') tags = self.client.projects.get(project.id).tags self.assertEqual([self.special_tag], tags) project.delete_tag(self.special_tag) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) def test_delete_all_tags(self): project_one = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) project_two = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', 'tag2', self.special_tag]) project_three = fixtures.Project( self.client, self.test_domain.id, tags=[]) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) result_one = project_one.delete_all_tags() tags_one = self.client.projects.get(project_one.id).tags tags_two = self.client.projects.get(project_two.id).tags self.assertEqual([], result_one) self.assertEqual([], tags_one) self.assertIn('tag1', tags_two) result_two = project_two.delete_all_tags() tags_two = self.client.projects.get(project_two.id).tags self.assertEqual([], result_two) self.assertEqual([], tags_two) result_three = project_three.delete_all_tags() tags_three = self.client.projects.get(project_three.id).tags self.assertEqual([], result_three) self.assertEqual([], tags_three) def test_list_tags(self): tags_one = ['tag1'] project_one = fixtures.Project( self.client, self.test_domain.id, tags=tags_one) tags_two = ['tag1', 'tag2'] project_two = fixtures.Project( self.client, self.test_domain.id, tags=tags_two) tags_three = [] project_three = fixtures.Project( self.client, self.test_domain.id, tags=tags_three) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) result_one = project_one.list_tags() result_two = project_two.list_tags() result_three = project_three.list_tags() for tag in tags_one: self.assertIn(tag, result_one) self.assertEqual(1, len(result_one)) for tag in tags_two: self.assertIn(tag, result_two) self.assertEqual(2, len(result_two)) for tag in tags_three: self.assertIn(tag, result_three) self.assertEqual(0, len(result_three)) def test_check_tag(self): project = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual(['tag1'], tags) self.assertTrue(project.check_tag('tag1')) self.assertFalse(project.check_tag('tag2')) self.assertFalse(project.check_tag(self.special_tag)) def test_add_invalid_tags(self): project_one = fixtures.Project( self.client, self.test_domain.id) self.useFixture(project_one) self.assertRaises(exceptions.BadRequest, project_one.add_tag, ',') self.assertRaises(exceptions.BadRequest, project_one.add_tag, '/') self.assertRaises(exceptions.BadRequest, project_one.add_tag, '') def test_update_invalid_tags(self): tags_comma = ['tag1', ','] tags_slash = ['tag1', '/'] tags_blank = ['tag1', ''] project_one = fixtures.Project( self.client, self.test_domain.id) self.useFixture(project_one) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_comma) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_slash) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_blank) def test_create_project_invalid_tags(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': ','} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': '/'} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': ''} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_federation.py0000664000175000017500000001003413644407055030547 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base class TestIdentityProviders(base.V3ClientTestCase): def test_idp_create(self): idp_id = uuid.uuid4().hex # Create an identity provider just passing an ID idp = self.client.federation.identity_providers.create(id=idp_id) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertEqual([], idp.remote_ids) self.assertFalse(idp.enabled) def test_idp_create_enabled_true(self): # Create an enabled identity provider idp_id = uuid.uuid4().hex idp = self.client.federation.identity_providers.create( id=idp_id, enabled=True) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertEqual([], idp.remote_ids) self.assertTrue(idp.enabled) def test_idp_create_with_remote_ids(self): # Create an enabled identity provider with remote IDs idp_id = uuid.uuid4().hex remote_ids = [uuid.uuid4().hex, uuid.uuid4().hex] idp = self.client.federation.identity_providers.create( id=idp_id, enabled=True, remote_ids=remote_ids) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertIn(remote_ids[0], idp.remote_ids) self.assertIn(remote_ids[1], idp.remote_ids) self.assertTrue(idp.enabled) def test_idp_list(self): idp_ids = [] for _ in range(3): idp_id = uuid.uuid4().hex self.client.federation.identity_providers.create(id=idp_id) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) idp_ids.append(idp_id) idp_list = self.client.federation.identity_providers.list() fetched_ids = [fetched_idp.id for fetched_idp in idp_list] for idp_id in idp_ids: self.assertIn(idp_id, fetched_ids) def test_idp_get(self): idp_id = uuid.uuid4().hex remote_ids = [uuid.uuid4().hex, uuid.uuid4().hex] idp_create = self.client.federation.identity_providers.create( id=idp_id, enabled=True, remote_ids=remote_ids) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) idp_get = self.client.federation.identity_providers.get(idp_id) self.assertEqual(idp_create.id, idp_get.id) self.assertEqual(idp_create.enabled, idp_get.enabled) self.assertIn(idp_create.remote_ids[0], idp_get.remote_ids) self.assertIn(idp_create.remote_ids[1], idp_get.remote_ids) def test_idp_delete(self): idp_id = uuid.uuid4().hex self.client.federation.identity_providers.create(id=idp_id) # Get should not raise an error self.client.federation.identity_providers.get(idp_id) # Delete it self.client.federation.identity_providers.delete(idp_id) # Now get should raise an error self.assertRaises( http.NotFound, self.client.federation.identity_providers.get, idp_id) # Should not be present in the identity provider list idp_list = self.client.federation.identity_providers.list() fetched_ids = [fetched_idp.id for fetched_idp in idp_list] self.assertNotIn(idp_id, fetched_ids) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_implied_roles.py0000664000175000017500000000551613644407055031267 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures role_defs = ["test_admin", "test_id_manager", "test_resource_manager", "test_role_manager", "test_assignment_manager", "test_domain_manager", "test_project_manager", "test_catalog_manager", "test_policy_manager", "test_observer", "test_domain_tech_lead", "test_project_observer", "test_member"] inference_rules = {"test_admin": "test_id_manager", "test_admin": "test_resource_manager", "test_admin": "test_role_manager", "test_admin": "test_catalog_manager", "test_admin": "test_policy_manager", "test_id_manager": "test_project_observer", "test_resource_manager": "test_project_observer", "test_role_manager": "test_project_observer", "test_catalog_manager": "test_project_observer", "test_policy_manager": "test_project_observer", "test_project_observer": "test_observer", "test_member": "test_observer"} class TestImpliedRoles(base.V3ClientTestCase): def setUp(self): super(TestImpliedRoles, self).setUp() def test_implied_roles(self): initial_rule_count = ( len(self.client.inference_rules.list_inference_roles())) self.create_roles() self.create_rules() rule_count = len(self.client.inference_rules.list_inference_roles()) self.assertEqual(initial_rule_count + len(inference_rules), rule_count) def role_dict(self): roles = {role.name: role.id for role in self.client.roles.list()} return roles def create_roles(self): for role_def in role_defs: role = fixtures.Role(self.client, name=role_def) self.useFixture(role) def create_rules(self): roles = self.role_dict() for prior, implied in inference_rules.items(): rule = fixtures.InferenceRule(self.client, roles[prior], roles[implied]) self.useFixture(rule) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_endpoints.py0000664000175000017500000001310113644407055030430 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EndpointsTestCase(base.V3ClientTestCase): def check_endpoint(self, endpoint, endpoint_ref=None): self.assertIsNotNone(endpoint.id) self.assertIn('self', endpoint.links) self.assertIn('/endpoints/' + endpoint.id, endpoint.links['self']) if endpoint_ref: self.assertEqual(endpoint_ref['service'], endpoint.service_id) self.assertEqual(endpoint_ref['url'], endpoint.url) self.assertEqual(endpoint_ref['interface'], endpoint.interface) self.assertEqual(endpoint_ref['enabled'], endpoint.enabled) # There is no guarantee below attributes are present in endpoint if hasattr(endpoint_ref, 'region'): self.assertEqual(endpoint_ref['region'], endpoint.region) else: # Only check remaining mandatory attributes self.assertIsNotNone(endpoint.service_id) self.assertIsNotNone(endpoint.url) self.assertIsNotNone(endpoint.interface) self.assertIsNotNone(endpoint.enabled) def test_create_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) endpoint_ref = {'service': service.id, 'url': 'http://' + uuid.uuid4().hex, 'enabled': True, 'interface': 'public'} endpoint = self.client.endpoints.create(**endpoint_ref) self.addCleanup(self.client.endpoints.delete, endpoint) self.check_endpoint(endpoint, endpoint_ref) def test_get_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) interfaces = ['public', 'admin', 'internal'] for interface in interfaces: endpoint = fixtures.Endpoint(self.client, service.id, interface) self.useFixture(endpoint) endpoint_ret = self.client.endpoints.get(endpoint.id) # All endpoints are valid self.check_endpoint(endpoint_ret, endpoint.ref) def test_list_endpoints(self): service = fixtures.Service(self.client) self.useFixture(service) region = fixtures.Region(self.client) self.useFixture(region) endpoint_one = fixtures.Endpoint(self.client, service.id, 'public', region=region.id) self.useFixture(endpoint_one) endpoint_two = fixtures.Endpoint(self.client, service.id, 'admin', region=region.id) self.useFixture(endpoint_two) endpoint_three = fixtures.Endpoint(self.client, service.id, 'internal', region=region.id) self.useFixture(endpoint_three) endpoints = self.client.endpoints.list() # All endpoints are valid for endpoint in endpoints: self.check_endpoint(endpoint) self.assertIn(endpoint_one.entity, endpoints) self.assertIn(endpoint_two.entity, endpoints) self.assertIn(endpoint_three.entity, endpoints) def test_update_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) new_service = fixtures.Service(self.client) self.useFixture(new_service) new_region = fixtures.Region(self.client) self.useFixture(new_region) endpoint = fixtures.Endpoint(self.client, service.id, 'public') self.useFixture(endpoint) new_url = 'http://' + uuid.uuid4().hex new_interface = 'internal' new_enabled = False endpoint_ret = self.client.endpoints.update(endpoint.id, service=new_service.id, url=new_url, interface=new_interface, enabled=new_enabled, region=new_region.id) endpoint.ref.update({'service': new_service.id, 'url': new_url, 'interface': new_interface, 'enabled': new_enabled, 'region': new_region.entity}) self.check_endpoint(endpoint_ret, endpoint.ref) def test_delete_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) endpoint = self.client.endpoints.create(service=service.id, url='http://' + uuid.uuid4().hex, enabled=True, interface='public') self.client.endpoints.delete(endpoint.id) self.assertRaises(http.NotFound, self.client.endpoints.get, endpoint.id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_ec2.py0000664000175000017500000000655113644407055027111 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EC2TestCase(base.V3ClientTestCase): def check_ec2(self, ec2, ec2_ref=None): self.assertIn('self', ec2.links) self.assertIn('/users/%s/credentials/OS-EC2/%s' % (ec2.user_id, ec2.access), ec2.links['self']) if ec2_ref: self.assertEqual(ec2_ref['user_id'], ec2.user_id) self.assertEqual(ec2_ref['project_id'], ec2.tenant_id) else: self.assertIsNotNone(ec2.user_id) self.assertIsNotNone(ec2.tenant_id) def test_create_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2_ref = {'user_id': user.id, 'project_id': project.id} ec2 = self.client.ec2.create(**ec2_ref) self.addCleanup(self.client.ec2.delete, user.id, ec2.access) self.check_ec2(ec2, ec2_ref) def test_get_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2 = fixtures.EC2(self.client, user_id=user.id, project_id=project.id) self.useFixture(ec2) ec2_ret = self.client.ec2.get(user.id, ec2.access) self.check_ec2(ec2_ret, ec2.ref) def test_list_ec2(self): user_one = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_one) ec2_one = fixtures.EC2(self.client, user_id=user_one.id, project_id=self.project_domain_id) self.useFixture(ec2_one) user_two = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_two) ec2_two = fixtures.EC2(self.client, user_id=user_two.id, project_id=self.project_domain_id) self.useFixture(ec2_two) ec2_list = self.client.ec2.list(user_one.id) # All ec2 are valid for ec2 in ec2_list: self.check_ec2(ec2) self.assertIn(ec2_one.entity, ec2_list) self.assertNotIn(ec2_two.entity, ec2_list) def test_delete_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2 = self.client.ec2.create(user.id, project.id) self.client.ec2.delete(user.id, ec2.access) self.assertRaises(http.NotFound, self.client.ec2.get, user.id, ec2.access) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_domain_configs.py0000664000175000017500000001027313644407055031413 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class DomainConfigsTestCase(base.V3ClientTestCase): def setUp(self): super(DomainConfigsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) def check_domain_config(self, config, config_ref): for attr in config_ref: self.assertEqual( getattr(config, attr), config_ref[attr], 'Expected different %s' % attr) def _new_ref(self): return {'identity': {'driver': uuid.uuid4().hex}, 'ldap': {'url': uuid.uuid4().hex}} def test_create_domain_config(self): config_ref = self._new_ref() config = self.client.domain_configs.create( self.test_domain.id, config_ref) self.addCleanup( self.client.domain_configs.delete, self.test_domain.id) self.check_domain_config(config, config_ref) def test_create_invalid_domain_config(self): invalid_groups_ref = { uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}, uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.create, self.test_domain.id, invalid_groups_ref) invalid_options_ref = { 'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.create, self.test_domain.id, invalid_options_ref) def test_get_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) config_ret = self.client.domain_configs.get(self.test_domain.id) self.check_domain_config(config_ret, config.ref) def test_update_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) update_config_ref = self._new_ref() config_ret = self.client.domain_configs.update( self.test_domain.id, update_config_ref) self.check_domain_config(config_ret, update_config_ref) def test_update_invalid_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) invalid_groups_ref = { uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}, uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.update, self.test_domain.id, invalid_groups_ref) invalid_options_ref = { 'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.update, self.test_domain.id, invalid_options_ref) def test_domain_config_delete(self): config_ref = self._new_ref() self.client.domain_configs.create(self.test_domain.id, config_ref) self.client.domain_configs.delete(self.test_domain.id) self.assertRaises(http.NotFound, self.client.domain_configs.get, self.project_domain_id) python-keystoneclient-4.0.0/keystoneclient/tests/functional/v3/test_roles.py0000664000175000017500000002177013644407055027564 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.exceptions import http from keystoneclient import exceptions from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class RolesTestCase(base.V3ClientTestCase): def check_role(self, role, role_ref=None): self.assertIsNotNone(role.id) self.assertIn('self', role.links) self.assertIn('/roles/' + role.id, role.links['self']) if role_ref: self.assertEqual(role_ref['name'], role.name) # There is no guarantee domain is present in role if hasattr(role_ref, 'domain'): self.assertEqual(role_ref['domain'], role.domain_id) else: # Only check remaining mandatory attribute self.assertIsNotNone(role.name) def test_create_role(self): role_ref = {'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex} role = self.client.roles.create(**role_ref) self.addCleanup(self.client.roles.delete, role) self.check_role(role, role_ref) def test_create_domain_role(self): role_ref = {'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id} role = self.client.roles.create(**role_ref) self.addCleanup(self.client.roles.delete, role) self.check_role(role, role_ref) def test_get_role(self): role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) role_ret = self.client.roles.get(role.id) self.check_role(role_ret, role.ref) def test_update_role_name(self): role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex role_ret = self.client.roles.update(role.id, name=new_name) role.ref.update({'name': new_name}) self.check_role(role_ret, role.ref) def test_update_role_domain(self): role = fixtures.Role(self.client) self.useFixture(role) domain = fixtures.Domain(self.client) self.useFixture(domain) new_domain = domain.id role_ret = self.client.roles.update(role.id, domain=new_domain) role.ref.update({'domain': new_domain}) self.check_role(role_ret, role.ref) def test_list_roles_invalid_params(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) # Only filter in role grants for a user on a resource. # Domain or project should be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.list, user=user.id) # Only filter in role grants for a group on a resource. # Domain or project should be specified. group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) self.assertRaises(exceptions.ValidationError, self.client.roles.list, group=group.id) def test_list_roles(self): global_role = fixtures.Role(self.client) self.useFixture(global_role) domain = fixtures.Domain(self.client) self.useFixture(domain) domain_role = fixtures.Role(self.client, domain=domain.id) self.useFixture(domain_role) global_roles = self.client.roles.list() domain_roles = self.client.roles.list(domain_id=domain.id) roles = global_roles + domain_roles # All roles are valid for role in roles: self.check_role(role) self.assertIn(global_role.entity, global_roles) self.assertIn(domain_role.entity, domain_roles) def test_delete_role(self): role = self.client.roles.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.roles.delete(role.id) self.assertRaises(http.NotFound, self.client.roles.get, role.id) def test_grant_role_invalid_params(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) # Only grant role to a group on a resource. # Domain or project must be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.grant, role.id, user=user.id) group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) # Only grant role to a group on a resource. # Domain or project must be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.grant, role.id, group=group.id) def test_user_domain_grant_and_revoke(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) domain = fixtures.Domain(self.client) self.useFixture(domain) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, user=user.id, domain=domain.id) roles_after_grant = self.client.roles.list(user=user.id, domain=domain.id) self.assertItemsEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, user=user.id, domain=domain.id) roles_after_revoke = self.client.roles.list(user=user.id, domain=domain.id) self.assertEqual(roles_after_revoke, []) def test_user_project_grant_and_revoke(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, user=user.id, project=project.id) roles_after_grant = self.client.roles.list(user=user.id, project=project.id) self.assertItemsEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, user=user.id, project=project.id) roles_after_revoke = self.client.roles.list(user=user.id, project=project.id) self.assertEqual(roles_after_revoke, []) def test_group_domain_grant_and_revoke(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) domain = fixtures.Domain(self.client) self.useFixture(domain) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, group=group.id, domain=domain.id) roles_after_grant = self.client.roles.list(group=group.id, domain=domain.id) self.assertItemsEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, group=group.id, domain=domain.id) roles_after_revoke = self.client.roles.list(group=group.id, domain=domain.id) self.assertEqual(roles_after_revoke, []) def test_group_project_grant_and_revoke(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, group=group.id, project=project.id) roles_after_grant = self.client.roles.list(group=group.id, project=project.id) self.assertItemsEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, group=group.id, project=project.id) roles_after_revoke = self.client.roles.list(group=group.id, project=project.id) self.assertEqual(roles_after_revoke, []) python-keystoneclient-4.0.0/keystoneclient/tests/functional/__init__.py0000664000175000017500000000000013644407055026567 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/functional/test_base.py0000664000175000017500000000150313644407055027012 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient from keystoneclient.tests.functional import base class V3ClientVersionTestCase(base.V3ClientTestCase): def test_version(self): self.assertIsInstance(self.client, keystoneclient.v3.client.Client) python-keystoneclient-4.0.0/keystoneclient/tests/functional/base.py0000664000175000017500000000553313644407055025762 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import client import os_client_config IDENTITY_CLIENT = 'identity' OPENSTACK_CLOUDS = ('functional_admin', 'devstack-admin', 'envvars') def get_client(version): """Create a keystoneclient instance to run functional tests. The client is instantiated via os-client-config either based on a clouds.yaml config file or from the environment variables. First, look for a 'functional_admin' cloud, as this is a cloud that the user may have defined for functional testing with admin credentials. If that is not found, check for the 'devstack-admin' cloud. Finally, fall back to looking for environment variables. """ for cloud in OPENSTACK_CLOUDS: try: cloud_config = os_client_config.get_config( cloud=cloud, identity_api_version=version) return cloud_config.get_legacy_client(service_key=IDENTITY_CLIENT, constructor=client.Client) except os_client_config.exceptions.OpenStackConfigException: pass raise Exception("Could not find any cloud definition for clouds named" " functional_admin or devstack-admin. Check your" " clouds.yaml file or your envvars and try again.") class ClientTestCase(testtools.TestCase): def setUp(self): super(ClientTestCase, self).setUp() if not self.auth_ref.project_scoped: raise Exception("Could not run functional tests, which are " "run based on the scope provided for " "authentication. Please provide a project " "scope information.") @property def client(self): if not hasattr(self, '_client'): self._client = get_client(self.version) return self._client @property def auth_ref(self): return self.client.session.auth.get_auth_ref(self.client.session) @property def project_domain_id(self): return self.auth_ref.project_domain_id @property def project_id(self): return self.client.session.get_project_id() @property def user_id(self): return self.client.session.get_user_id() class V3ClientTestCase(ClientTestCase): version = '3' python-keystoneclient-4.0.0/keystoneclient/tests/unit/0000775000175000017500000000000013644407143023303 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/apiclient/0000775000175000017500000000000013644407143025253 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/apiclient/__init__.py0000664000175000017500000000000013644407055027354 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/apiclient/test_exceptions.py0000664000175000017500000000460213644407055031051 0ustar zuulzuul00000000000000# Copyright 2012 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. from keystoneclient import exceptions from keystoneclient.tests.unit import utils class FakeResponse(object): json_data = {} def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def json(self): return self.json_data class ExceptionsArgsTest(utils.TestCase): def assert_exception(self, ex_cls, method, url, status_code, json_data): ex = exceptions.from_response( FakeResponse(status_code=status_code, headers={"Content-Type": "application/json"}, json_data=json_data), method, url) self.assertIsInstance(ex, ex_cls) self.assertIn(json_data["error"]["message"], ex.message) self.assertEqual(ex.details, json_data["error"]["details"]) self.assertEqual(ex.method, method) self.assertEqual(ex.url, url) self.assertEqual(ex.http_status, status_code) def test_from_response_known(self): method = "GET" url = "/fake" status_code = 400 json_data = {"error": {"message": "fake message", "details": "fake details"}} self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data) def test_from_response_unknown(self): method = "POST" url = "/fake-unknown" status_code = 499 json_data = {"error": {"message": "fake unknown message", "details": "fake unknown details"}} self.assert_exception( exceptions.HTTPClientError, method, url, status_code, json_data) status_code = 600 self.assert_exception( exceptions.HTTPError, method, url, status_code, json_data) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_discovery.py0000664000175000017500000007336713644407055026745 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import uuid from keystoneauth1 import fixture from oslo_serialization import jsonutils import six from testtools import matchers from keystoneclient import _discover from keystoneclient.auth import token_endpoint from keystoneclient import client from keystoneclient import discover from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client 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 = jsonutils.dumps({ "access": { "token": { "expires": "2999-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_TOKEN = six.u('3e2813b7ba0b4006840c3825860b86ed'), V3_AUTH_RESPONSE = jsonutils.dumps({ "token": { "methods": [ "token", "password" ], "expires_at": "2999-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" } ] } ] } 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 jsonutils.dumps({'versions': {'values': versions}}) def _create_single_version(version): return jsonutils.dumps({'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 AvailableVersionsTests(utils.TestCase): def setUp(self): super(AvailableVersionsTests, self).setUp() self.deprecations.expect_deprecations() def test_available_versions_basics(self): examples = {'keystone': V3_VERSION_LIST, 'cinder': jsonutils.dumps(CINDER_EXAMPLES), 'glance': jsonutils.dumps(GLANCE_EXAMPLES)} for path, text in examples.items(): url = "%s%s" % (BASE_URL, path) self.requests_mock.get(url, status_code=300, text=text) versions = discover.available_versions(url) for v in versions: for n in ('id', 'status', 'links'): msg = '%s missing from %s version data' % (n, path) self.assertThat(v, matchers.Annotate(msg, matchers.Contains(n))) def test_available_versions_individual(self): self.requests_mock.get(V3_URL, status_code=200, text=V3_VERSION_ENTRY) versions = discover.available_versions(V3_URL) for v in versions: self.assertEqual(v['id'], 'v3.0') self.assertEqual(v['status'], 'stable') self.assertIn('media-types', v) self.assertIn('links', v) def test_available_keystone_data(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) versions = discover.available_versions(BASE_URL) self.assertEqual(2, len(versions)) for v in versions: 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) def test_available_cinder_data(self): text = jsonutils.dumps(CINDER_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=300, text=text) versions = discover.available_versions(BASE_URL) self.assertEqual(2, len(versions)) for v in versions: 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') else: self.fail("Invalid version found") def test_available_glance_data(self): text = jsonutils.dumps(GLANCE_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=200, text=text) versions = discover.available_versions(BASE_URL) self.assertEqual(5, len(versions)) for v in versions: 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") class ClientDiscoveryTests(utils.TestCase): def setUp(self): super(ClientDiscoveryTests, self).setUp() self.deprecations.expect_deprecations() def assertCreatesV3(self, **kwargs): self.requests_mock.post('%s/auth/tokens' % V3_URL, text=V3_AUTH_RESPONSE, headers={'X-Subject-Token': V3_TOKEN}) kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') keystone = client.Client(**kwargs) self.assertIsInstance(keystone, v3_client.Client) return keystone def assertCreatesV2(self, **kwargs): self.requests_mock.post("%s/tokens" % V2_URL, text=V2_AUTH_RESPONSE) kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') keystone = client.Client(**kwargs) self.assertIsInstance(keystone, v2_client.Client) return keystone def assertVersionNotAvailable(self, **kwargs): kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') self.assertRaises(exceptions.VersionNotAvailable, client.Client, **kwargs) def assertDiscoveryFailure(self, **kwargs): kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') self.assertRaises(exceptions.DiscoveryFailure, client.Client, **kwargs) def test_discover_v3(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertCreatesV3(auth_url=BASE_URL) def test_discover_v2(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) self.requests_mock.post("%s/tokens" % V2_URL, text=V2_AUTH_RESPONSE) self.assertCreatesV2(auth_url=BASE_URL) def test_discover_endpoint_v2(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) self.assertCreatesV2(endpoint=BASE_URL, token='fake-token') def test_discover_endpoint_v3(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertCreatesV3(endpoint=BASE_URL, token='fake-token') def test_discover_invalid_major_version(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertVersionNotAvailable(auth_url=BASE_URL, version=5) def test_discover_200_response_fails(self): self.requests_mock.get(BASE_URL, text='ok') self.assertDiscoveryFailure(auth_url=BASE_URL) def test_discover_minor_greater_than_available_fails(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertVersionNotAvailable(endpoint=BASE_URL, version=3.4) def test_discover_individual_version_v2(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.assertCreatesV2(auth_url=V2_URL) def test_discover_individual_version_v3(self): self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertCreatesV3(auth_url=V3_URL) def test_discover_individual_endpoint_v2(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.assertCreatesV2(endpoint=V2_URL, token='fake-token') def test_discover_individual_endpoint_v3(self): self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertCreatesV3(endpoint=V3_URL, token='fake-token') def test_discover_fail_to_create_bad_individual_version(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertVersionNotAvailable(auth_url=V2_URL, version=3) self.assertVersionNotAvailable(auth_url=V3_URL, version=2) def test_discover_unstable_versions(self): version_list = fixture.DiscoveryList(BASE_URL, v3_status='beta') self.requests_mock.get(BASE_URL, status_code=300, json=version_list) self.assertCreatesV2(auth_url=BASE_URL) self.assertVersionNotAvailable(auth_url=BASE_URL, version=3) self.assertCreatesV3(auth_url=BASE_URL, unstable=True) def test_discover_forwards_original_ip(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) ip = '192.168.1.1' self.assertCreatesV3(auth_url=BASE_URL, original_ip=ip) self.assertThat(self.requests_mock.last_request.headers['forwarded'], matchers.Contains(ip)) def test_discover_bad_args(self): self.assertRaises(exceptions.DiscoveryFailure, client.Client) def test_discover_bad_response(self): self.requests_mock.get(BASE_URL, status_code=300, json={'FOO': 'BAR'}) self.assertDiscoveryFailure(auth_url=BASE_URL) def test_discovery_ignore_invalid(self): resp = [{'id': 'v3.0', 'links': [1, 2, 3, 4], # invalid links 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED}] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list(resp)) self.assertDiscoveryFailure(auth_url=BASE_URL) def test_ignore_entry_without_links(self): v3 = V3_VERSION.copy() v3['links'] = [] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list([v3, V2_VERSION])) self.assertCreatesV2(auth_url=BASE_URL) def test_ignore_entry_without_status(self): v3 = V3_VERSION.copy() del v3['status'] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list([v3, V2_VERSION])) self.assertCreatesV2(auth_url=BASE_URL) def test_greater_version_than_required(self): versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.6') self.requests_mock.get(BASE_URL, json=versions) self.assertCreatesV3(auth_url=BASE_URL, version=(3, 4)) def test_lesser_version_than_required(self): versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.4') self.requests_mock.get(BASE_URL, json=versions) self.assertVersionNotAvailable(auth_url=BASE_URL, version=(3, 6)) def test_bad_response(self): self.requests_mock.get(BASE_URL, status_code=300, text="Ugly Duckling") self.assertDiscoveryFailure(auth_url=BASE_URL) def test_pass_client_arguments(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) kwargs = {'original_ip': '100', 'use_keyring': False, 'stale_duration': 15} cl = self.assertCreatesV2(auth_url=BASE_URL, **kwargs) with self.deprecations.expect_deprecations_here(): self.assertEqual(cl.original_ip, '100') self.assertEqual(cl.stale_duration, 15) self.assertFalse(cl.use_keyring) def test_overriding_stored_kwargs(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.requests_mock.post("%s/auth/tokens" % V3_URL, text=V3_AUTH_RESPONSE, headers={'X-Subject-Token': V3_TOKEN}) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL, debug=False, username='foo') client = disc.create_client(debug=True, password='bar') self.assertIsInstance(client, v3_client.Client) self.assertFalse(disc._client_kwargs['debug']) self.assertEqual(client.username, 'foo') self.assertEqual(client.password, 'bar') def test_available_versions(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_ENTRY) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) with self.deprecations.expect_deprecations_here(): versions = disc.available_versions() self.assertEqual(1, len(versions)) self.assertEqual(V3_VERSION, versions[0]) def test_unknown_client_version(self): V4_VERSION = {'id': 'v4.0', 'links': [{'href': 'http://url', 'rel': 'self'}], 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED} versions = fixture.DiscoveryList() versions.add_version(V4_VERSION) self.requests_mock.get(BASE_URL, status_code=300, json=versions) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=4) def test_discovery_fail_for_missing_v3(self): versions = fixture.DiscoveryList(v2=True, v3=False) self.requests_mock.get(BASE_URL, status_code=300, json=versions) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=(3, 0)) def _do_discovery_call(self, token=None, **kwargs): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) if not token: token = uuid.uuid4().hex url = 'http://testurl' with self.deprecations.expect_deprecations_here(): a = token_endpoint.Token(url, token) s = session.Session(auth=a) # will default to true as there is a plugin on the session discover.Discover(s, auth_url=BASE_URL, **kwargs) self.assertEqual(BASE_URL, self.requests_mock.last_request.url) def test_setting_authenticated_true(self): token = uuid.uuid4().hex self._do_discovery_call(token) self.assertRequestHeaderEqual('X-Auth-Token', token) def test_setting_authenticated_false(self): self._do_discovery_call(authenticated=False) self.assertNotIn('X-Auth-Token', self.requests_mock.last_request.headers) class DiscoverQueryTests(utils.TestCase): def test_available_keystone_data(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((2, 0), versions[0]['version']) self.assertEqual('stable', versions[0]['raw_status']) self.assertEqual(V2_URL, versions[0]['url']) self.assertEqual((3, 0), versions[1]['version']) self.assertEqual('stable', versions[1]['raw_status']) self.assertEqual(V3_URL, versions[1]['url']) version = disc.data_for('v3.0') self.assertEqual((3, 0), version['version']) self.assertEqual('stable', version['raw_status']) self.assertEqual(V3_URL, version['url']) version = disc.data_for(2) 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.assertEqual(V3_URL, disc.url_for('v3')) self.assertEqual(V2_URL, disc.url_for('v2')) def test_available_cinder_data(self): text = jsonutils.dumps(CINDER_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=300, text=text) v1_url = "%sv1/" % BASE_URL v2_url = "%sv2/" % BASE_URL # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((1, 0), versions[0]['version']) self.assertEqual('CURRENT', versions[0]['raw_status']) self.assertEqual(v1_url, versions[0]['url']) self.assertEqual((2, 0), versions[1]['version']) self.assertEqual('CURRENT', versions[1]['raw_status']) self.assertEqual(v2_url, versions[1]['url']) version = disc.data_for('v2.0') self.assertEqual((2, 0), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v2_url, version['url']) version = disc.data_for(1) self.assertEqual((1, 0), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v1_url, version['url']) self.assertIsNone(disc.url_for('v3')) self.assertEqual(v2_url, disc.url_for('v2')) self.assertEqual(v1_url, disc.url_for('v1')) def test_available_glance_data(self): text = jsonutils.dumps(GLANCE_EXAMPLES) self.requests_mock.get(BASE_URL, text=text) v1_url = "%sv1/" % BASE_URL v2_url = "%sv2/" % BASE_URL # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((1, 0), versions[0]['version']) self.assertEqual('SUPPORTED', versions[0]['raw_status']) self.assertEqual(v1_url, versions[0]['url']) self.assertEqual((1, 1), versions[1]['version']) self.assertEqual('CURRENT', versions[1]['raw_status']) self.assertEqual(v1_url, versions[1]['url']) self.assertEqual((2, 0), versions[2]['version']) self.assertEqual('SUPPORTED', versions[2]['raw_status']) self.assertEqual(v2_url, versions[2]['url']) self.assertEqual((2, 1), versions[3]['version']) self.assertEqual('SUPPORTED', versions[3]['raw_status']) self.assertEqual(v2_url, versions[3]['url']) self.assertEqual((2, 2), versions[4]['version']) self.assertEqual('CURRENT', versions[4]['raw_status']) self.assertEqual(v2_url, versions[4]['url']) for ver in (2, 2.1, 2.2): version = disc.data_for(ver) 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): version = disc.data_for(ver) 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.url_for('v2.3')) 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}] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=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}] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=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) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=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_lnks(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'}], }] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=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 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((6, 1), (6, 1)) assertVersion([1, 4], (1, 4)) versionRaises('hello') versionRaises('1.a') versionRaises('vacuum') python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/0000775000175000017500000000000013644407143023633 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_endpoint_filter.py0000664000175000017500000003022113644407055030431 0ustar zuulzuul00000000000000# Copyright 2014 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 uuid from keystoneclient.tests.unit.v3 import utils class EndpointTestUtils(object): """Mixin class with shared methods between Endpoint Filter & Policy.""" def new_ref(self, **kwargs): # copied from CrudTests as we need to create endpoint and project # refs for our tests. EndpointFilter is not exactly CRUD API. kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def new_endpoint_ref(self, **kwargs): # copied from EndpointTests as we need endpoint refs for our tests kwargs = self.new_ref(**kwargs) kwargs.setdefault('interface', 'public') kwargs.setdefault('region', uuid.uuid4().hex) kwargs.setdefault('service_id', uuid.uuid4().hex) kwargs.setdefault('url', uuid.uuid4().hex) return kwargs def new_endpoint_group_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('filters') return kwargs class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils): """Test project-endpoint associations (a.k.a. EndpointFilter Extension). Endpoint filter provides associations between service endpoints and projects. These assciations are then used to create ad-hoc catalogs for each project-scoped token request. """ def setUp(self): super(EndpointFilterTests, self).setUp() self.manager = self.client.endpoint_filter def new_project_ref(self, **kwargs): # copied from ProjectTests as we need project refs for our tests kwargs = self.new_ref(**kwargs) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_add_endpoint_to_project_via_id(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.add_endpoint_to_project(project=project_id, endpoint=endpoint_id) def test_add_endpoint_to_project_via_obj(self): project_ref = self.new_project_ref() endpoint_ref = self.new_endpoint_ref() project = self.client.projects.resource_class(self.client.projects, project_ref, loaded=True) endpoint = self.client.endpoints.resource_class(self.client.endpoints, endpoint_ref, loaded=True) self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'projects', project_ref['id'], 'endpoints', endpoint_ref['id']], status_code=201) self.manager.add_endpoint_to_project(project=project, endpoint=endpoint) def test_delete_endpoint_from_project(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('DELETE', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.delete_endpoint_from_project(project=project_id, endpoint=endpoint_id) def test_check_endpoint_in_project(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('HEAD', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.check_endpoint_in_project(project=project_id, endpoint=endpoint_id) def test_list_endpoints_for_project(self): project_id = uuid.uuid4().hex endpoints = {'endpoints': [self.new_endpoint_ref(), self.new_endpoint_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints'], json=endpoints, status_code=200) endpoints_resp = self.manager.list_endpoints_for_project( project=project_id) expected_endpoint_ids = [ endpoint['id'] for endpoint in endpoints['endpoints']] actual_endpoint_ids = [endpoint.id for endpoint in endpoints_resp] self.assertEqual(expected_endpoint_ids, actual_endpoint_ids) def test_list_projects_for_endpoint(self): endpoint_id = uuid.uuid4().hex projects = {'projects': [self.new_project_ref(), self.new_project_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'endpoints', endpoint_id, 'projects'], json=projects, status_code=200) projects_resp = self.manager.list_projects_for_endpoint( endpoint=endpoint_id) expected_project_ids = [ project['id'] for project in projects['projects']] actual_project_ids = [project.id for project in projects_resp] self.assertEqual(expected_project_ids, actual_project_ids) def test_list_projects_for_endpoint_group(self): endpoint_group_id = uuid.uuid4().hex projects = {'projects': [self.new_project_ref(), self.new_project_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects'], json=projects, status_code=200) projects_resp = self.manager.list_projects_for_endpoint_group( endpoint_group=endpoint_group_id) expected_project_ids = [ project['id'] for project in projects['projects']] actual_project_ids = [project.id for project in projects_resp] self.assertEqual(expected_project_ids, actual_project_ids) def test_list_projects_for_endpoint_group_value_error(self): self.assertRaises(ValueError, self.manager.list_projects_for_endpoint_group, endpoint_group='') self.assertRaises(ValueError, self.manager.list_projects_for_endpoint_group, endpoint_group=None) def test_list_endpoint_groups_for_project(self): project_id = uuid.uuid4().hex endpoint_groups = { 'endpoint_groups': [self.new_endpoint_group_ref(), self.new_endpoint_group_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoint_groups'], json=endpoint_groups, status_code=200) endpoint_groups_resp = self.manager.list_endpoint_groups_for_project( project=project_id) expected_endpoint_group_ids = [ endpoint_group['id'] for endpoint_group in endpoint_groups['endpoint_groups'] ] actual_endpoint_group_ids = [ endpoint_group.id for endpoint_group in endpoint_groups_resp ] self.assertEqual(expected_endpoint_group_ids, actual_endpoint_group_ids) def test_list_endpoint_groups_for_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.list_endpoint_groups_for_project, project=value) def test_add_endpoint_group_to_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.add_endpoint_group_to_project( project=project_id, endpoint_group=endpoint_group_id) def test_add_endpoint_group_to_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=value, endpoint_group=uuid.uuid4().hex) def test_check_endpoint_group_in_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('HEAD', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.check_endpoint_group_in_project( project=project_id, endpoint_group=endpoint_group_id) def test_check_endpoint_group_in_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=value, endpoint_group=uuid.uuid4().hex) def test_delete_endpoint_group_from_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('DELETE', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.delete_endpoint_group_from_project( project=project_id, endpoint_group=endpoint_group_id) def test_delete_endpoint_group_from_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=value, endpoint_group=uuid.uuid4().hex) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_endpoint_groups.py0000664000175000017500000000245513644407055030473 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import endpoint_groups class EndpointGroupTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(EndpointGroupTests, self).setUp() self.key = 'endpoint_group' self.collection_key = 'endpoint_groups' self.model = endpoint_groups.EndpointGroup self.manager = self.client.endpoint_groups self.path_prefix = 'OS-EP-FILTER' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('filters', '{"interface": "public"}') kwargs.setdefault('description', uuid.uuid4().hex) return kwargs python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_limits.py0000664000175000017500000000561313644407055026554 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import limits class LimitTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(LimitTests, self).setUp() self.key = 'limit' self.collection_key = 'limits' self.model = limits.Limit self.manager = self.client.limits def new_ref(self, **kwargs): ref = { 'id': uuid.uuid4().hex, 'project_id': uuid.uuid4().hex, 'service_id': uuid.uuid4().hex, 'resource_name': uuid.uuid4().hex, 'resource_limit': 15, 'description': uuid.uuid4().hex } ref.update(kwargs) return ref def test_create(self): # This test overrides the generic test case provided by the CrudTests # class because the limits API supports creating multiple limits in a # single POST request. As a result, it returns the limits as a list of # all the created limits from the request. This is different from what # the base test_create() method assumes about keystone's API. The # changes here override the base test to closely model how the actual # limit API behaves. ref = self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') req_ref = [manager_ref.copy()] self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**utils.parameterize(manager_ref)) self.assertIsInstance(returned, self.model) expected_limit = req_ref.pop() for attr in expected_limit: self.assertEqual( getattr(returned, attr), expected_limit[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs([expected_limit]) def test_list_filter_by_service(self): service_id = uuid.uuid4().hex expected_query = {'service_id': service_id} self.test_list(expected_query=expected_query, service=service_id) def test_list_filtered_by_resource_name(self): resource_name = uuid.uuid4().hex self.test_list(resource_name=resource_name) def test_list_filtered_by_region(self): region_id = uuid.uuid4().hex expected_query = {'region_id': region_id} self.test_list(expected_query=expected_query, region=region_id) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/client_fixtures.py0000664000175000017500000001306713644407055027425 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 __future__ import unicode_literals import uuid from keystoneauth1 import fixture def unscoped_token(**kwargs): return fixture.V3Token(**kwargs) def domain_scoped_token(**kwargs): kwargs.setdefault('audit_chain_id', uuid.uuid4().hex) f = fixture.V3Token(**kwargs) if not f.domain_id: f.set_domain_scope() f.add_role(name='admin') f.add_role(name='member') region = 'RegionOne' s = f.add_service('volume') s.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) 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/v1.1/None', internal='http://internal:8774/v1.1/None', admin='http://admin:8774/v1.1/None', 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) return f def project_scoped_token(**kwargs): kwargs.setdefault('audit_chain_id', uuid.uuid4().hex) f = fixture.V3Token(**kwargs) 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) return f AUTH_SUBJECT_TOKEN = uuid.uuid4().hex AUTH_RESPONSE_HEADERS = { 'X-Subject-Token': AUTH_SUBJECT_TOKEN, } def auth_response_body(): f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) f.set_project_scope() f.add_role(name='admin') f.add_role(name='member') s = f.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 = f.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 = f.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') return f def trust_token(): f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) f.set_trust_scope() return f python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_access.py0000664000175000017500000002130313644407055026506 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 fixture from oslo_utils import timeutils from keystoneclient import access from keystoneclient.tests.unit import utils as test_utils from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils TOKEN_RESPONSE = test_utils.test_response( headers=client_fixtures.AUTH_RESPONSE_HEADERS ) UNSCOPED_TOKEN = client_fixtures.unscoped_token() DOMAIN_SCOPED_TOKEN = client_fixtures.domain_scoped_token() PROJECT_SCOPED_TOKEN = client_fixtures.project_scoped_token() class AccessInfoTest(utils.TestCase): def test_building_unscoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=UNSCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertNotIn('catalog', auth_ref) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(UNSCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(UNSCOPED_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) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.auth_url) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.management_url) self.assertFalse(auth_ref.domain_scoped) self.assertFalse(auth_ref.project_scoped) self.assertEqual(UNSCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(UNSCOPED_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( UNSCOPED_TOKEN['token']['expires_at'])) self.assertEqual(auth_ref.issued, timeutils.parse_isotime( UNSCOPED_TOKEN['token']['issued_at'])) self.assertEqual(auth_ref.expires, UNSCOPED_TOKEN.expires) self.assertEqual(auth_ref.issued, UNSCOPED_TOKEN.issued) self.assertEqual(auth_ref.audit_id, UNSCOPED_TOKEN.audit_id) self.assertIsNone(auth_ref.audit_chain_id) self.assertIsNone(UNSCOPED_TOKEN.audit_chain_id) def test_will_expire_soon(self): expires = timeutils.utcnow() + datetime.timedelta(minutes=5) UNSCOPED_TOKEN['token']['expires_at'] = expires.isoformat() auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=UNSCOPED_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): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=DOMAIN_SCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertIn('catalog', auth_ref) self.assertTrue(auth_ref['catalog']) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_id, auth_ref.user_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.role_ids, auth_ref.role_ids) self.assertEqual(DOMAIN_SCOPED_TOKEN.role_names, auth_ref.role_names) self.assertEqual(DOMAIN_SCOPED_TOKEN.domain_name, auth_ref.domain_name) self.assertEqual(DOMAIN_SCOPED_TOKEN.domain_id, auth_ref.domain_id) self.assertIsNone(auth_ref.project_name) self.assertIsNone(auth_ref.project_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(DOMAIN_SCOPED_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.assertEqual(DOMAIN_SCOPED_TOKEN.audit_id, auth_ref.audit_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.audit_chain_id, auth_ref.audit_chain_id) def test_building_project_scoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=PROJECT_SCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertIn('catalog', auth_ref) self.assertTrue(auth_ref['catalog']) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(PROJECT_SCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(PROJECT_SCOPED_TOKEN.user_id, auth_ref.user_id) self.assertEqual(PROJECT_SCOPED_TOKEN.role_ids, auth_ref.role_ids) self.assertEqual(PROJECT_SCOPED_TOKEN.role_names, auth_ref.role_names) self.assertIsNone(auth_ref.domain_name) self.assertIsNone(auth_ref.domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.project_name, auth_ref.project_name) self.assertEqual(PROJECT_SCOPED_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) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.auth_url, ('http://public.com:5000/v3',)) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.management_url, ('http://admin:35357/v3',)) self.assertEqual(PROJECT_SCOPED_TOKEN.project_domain_id, auth_ref.project_domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.project_domain_name, auth_ref.project_domain_name) self.assertEqual(PROJECT_SCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.user_domain_name, auth_ref.user_domain_name) self.assertFalse(auth_ref.domain_scoped) self.assertTrue(auth_ref.project_scoped) self.assertEqual(PROJECT_SCOPED_TOKEN.audit_id, auth_ref.audit_id) self.assertEqual(PROJECT_SCOPED_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.AccessInfo.factory(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['OS-OAUTH1']['consumer_id']) self.assertEqual(access_token_id, auth_ref['OS-OAUTH1']['access_token_id']) def test_override_auth_token(self): token = fixture.V3Token() token.set_project_scope() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token, auth_token=new_auth_token) self.assertEqual(new_auth_token, auth_ref.auth_token) 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.AccessInfo.factory(body=token) self.assertFalse(auth_ref.is_federated) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_client.py0000664000175000017500000002765013644407055026536 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from oslo_serialization import jsonutils from keystoneauth1 import session as auth_session from keystoneclient.auth import token_endpoint from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client class KeystoneClientTest(utils.TestCase): def test_unscoped_init(self): token = client_fixtures.unscoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_domain_name=token.user_domain_name, username=token.user_name, password='password', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertFalse(c.has_service_catalog()) self.assertEqual(token.user_id, c.get_user_id(session=None)) self.assertIsNone(c.get_project_id(session=None)) def test_domain_scoped_init(self): token = client_fixtures.domain_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', domain_name=token.domain_name, auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertTrue(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertEqual(token.domain_id, c.auth_domain_id) def test_project_scoped_init(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token), # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', user_domain_name=token.user_domain_name, project_name=token.project_name, auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertTrue(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertEqual(token.project_id, c.auth_tenant_id) self.assertEqual(token.user_id, c.get_user_id(session=None)) self.assertEqual(token.project_id, c.get_project_id(session=None)) def test_auth_ref_load(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', project_id=token.project_id, auth_url=self.TEST_URL) cache = jsonutils.dumps(c.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache)) self.assertIsNotNone(new_client.auth_ref) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertEqual(token.user_name, new_client.username) self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v3') def test_auth_ref_load_with_overridden_arguments(self): new_auth_url = 'https://newkeystone.com/v3' user_id = uuid.uuid4().hex user_name = uuid.uuid4().hex project_id = uuid.uuid4().hex first = client_fixtures.project_scoped_token(user_id=user_id, user_name=user_name, project_id=project_id) second = client_fixtures.project_scoped_token(user_id=user_id, user_name=user_name, project_id=project_id) self.stub_auth(json=first) self.stub_auth(json=second, base_url=new_auth_url) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=user_id, password='password', project_id=project_id, auth_url=self.TEST_URL) cache = jsonutils.dumps(c.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache), auth_url=new_auth_url) self.assertIsNotNone(new_client.auth_ref) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertEqual(new_auth_url, new_client.auth_url) self.assertEqual(user_name, new_client.username) self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v3') def test_trust_init(self): token = client_fixtures.trust_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_domain_name=token.user_domain_name, username=token.user_name, password='password', auth_url=self.TEST_URL, trust_id=token.trust_id) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.trust_id, c.auth_ref.trust_id) self.assertEqual(token.trustee_user_id, c.auth_ref.trustee_user_id) self.assertEqual(token.trustor_user_id, c.auth_ref.trustor_user_id) self.assertTrue(c.auth_ref.trust_scoped) self.assertEqual(token.user_id, c.auth_user_id) def test_init_err_no_auth_url(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, username='exampleuser', password='password') def _management_url_is_updated(self, fixture, **kwargs): second = copy.deepcopy(fixture) first_url = 'http://admin:35357/v3' second_url = "http://secondurl:%d/v3'" for entry in second['token']['catalog']: if entry['type'] == 'identity': entry['endpoints'] = [{ 'url': second_url % 5000, 'region': 'RegionOne', 'interface': 'public' }, { 'url': second_url % 5000, 'region': 'RegionOne', 'interface': 'internal' }, { 'url': second_url % 35357, 'region': 'RegionOne', 'interface': 'admin' }] self.stub_auth(response_list=[{'json': fixture}, {'json': second}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', auth_url=self.TEST_URL, **kwargs) self.assertEqual(cl.management_url, first_url) with self.deprecations.expect_deprecations_here(): cl.authenticate() self.assertEqual(cl.management_url, second_url % 35357) def test_management_url_is_updated_with_project(self): self._management_url_is_updated(client_fixtures.project_scoped_token(), project_name='exampleproject') def test_management_url_is_updated_with_domain(self): self._management_url_is_updated(client_fixtures.domain_scoped_token(), domain_name='exampledomain') def test_client_with_region_name_passes_to_service_catalog(self): # NOTE(jamielennox): this is deprecated behaviour that should be # removed ASAP, however must remain compatible. self.deprecations.expect_deprecations() self.stub_auth(json=client_fixtures.auth_response_body()) cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='North') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'http://glance.north.host/glanceapi/public') cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='South') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'http://glance.south.host/glanceapi/public') def test_client_without_auth_params(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, project_name='exampleproject', auth_url=self.TEST_URL) def test_client_params(self): with self.deprecations.expect_deprecations_here(): sess = session.Session() auth = token_endpoint.Token('a', 'b') opts = {'auth': auth, 'connect_retries': 50, 'endpoint_override': uuid.uuid4().hex, 'interface': uuid.uuid4().hex, 'region_name': uuid.uuid4().hex, 'service_name': uuid.uuid4().hex, 'user_agent': uuid.uuid4().hex, } cl = client.Client(session=sess, **opts) for k, v in opts.items(): self.assertEqual(v, getattr(cl._adapter, k)) self.assertEqual('identity', cl._adapter.service_type) self.assertEqual((3, 0), cl._adapter.version) def test_empty_service_catalog_param(self): # Client().service_catalog should return None if the client is not # authenticated sess = auth_session.Session() cl = client.Client(session=sess) self.assertIsNone(cl.service_catalog) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_registered_limits.py0000664000175000017500000000571213644407055030771 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import registered_limits class RegisteredLimitTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RegisteredLimitTests, self).setUp() self.key = 'registered_limit' self.collection_key = 'registered_limits' self.model = registered_limits.RegisteredLimit self.manager = self.client.registered_limits def new_ref(self, **kwargs): ref = { 'id': uuid.uuid4().hex, 'service_id': uuid.uuid4().hex, 'resource_name': uuid.uuid4().hex, 'default_limit': 10, 'description': uuid.uuid4().hex } ref.update(kwargs) return ref def test_create(self): # This test overrides the generic test case provided by the CrudTests # class because the registered limits API supports creating multiple # limits in a single POST request. As a result, it returns the # registered limits as a list of all the created limits from the # request. This is different from what the base test_create() method # assumes about keystone's API. The changes here override the base test # to closely model how the actual registered limit API behaves. ref = self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') req_ref = [manager_ref.copy()] self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**utils.parameterize(manager_ref)) self.assertIsInstance(returned, self.model) expected_limit = req_ref.pop() for attr in expected_limit: self.assertEqual( getattr(returned, attr), expected_limit[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs([expected_limit]) def test_list_filter_by_service(self): service_id = uuid.uuid4().hex expected_query = {'service_id': service_id} self.test_list(expected_query=expected_query, service=service_id) def test_list_filter_resource_name(self): resource_name = uuid.uuid4().hex self.test_list(resource_name=resource_name) def test_list_filter_region(self): region_id = uuid.uuid4().hex expected_query = {'region_id': region_id} self.test_list(expected_query=expected_query, region=region_id) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_oauth1.py0000664000175000017500000003256213644407055026457 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 import uuid import mock import six from six.moves.urllib import parse as urlparse from testtools import matchers from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient import utils as client_utils from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import auth from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens try: from oauthlib import oauth1 except ImportError: oauth1 = None class ConsumerTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(ConsumerTests, self).setUp() self.key = 'consumer' self.collection_key = 'consumers' self.model = consumers.Consumer self.manager = self.client.oauth1.consumers self.path_prefix = 'OS-OAUTH1' def new_ref(self, **kwargs): kwargs = super(ConsumerTests, self).new_ref(**kwargs) kwargs.setdefault('description', uuid.uuid4().hex) return kwargs def test_description_is_optional(self): consumer_id = uuid.uuid4().hex resp_ref = {'consumer': {'description': None, 'id': consumer_id}} self.stub_url('POST', [self.path_prefix, self.collection_key], status_code=201, json=resp_ref) consumer = self.manager.create() self.assertEqual(consumer_id, consumer.id) self.assertIsNone(consumer.description) def test_description_not_included(self): consumer_id = uuid.uuid4().hex resp_ref = {'consumer': {'id': consumer_id}} self.stub_url('POST', [self.path_prefix, self.collection_key], status_code=201, json=resp_ref) consumer = self.manager.create() self.assertEqual(consumer_id, consumer.id) class TokenTests(object): def _new_oauth_token(self): key = uuid.uuid4().hex secret = uuid.uuid4().hex params = {'oauth_token': key, 'oauth_token_secret': secret} token = urlparse.urlencode(params) return (key, secret, token) def _new_oauth_token_with_expires_at(self): key, secret, token = self._new_oauth_token() expires_at = client_utils.strtime() params = {'oauth_token': key, 'oauth_token_secret': secret, 'oauth_expires_at': expires_at} token = urlparse.urlencode(params) return (key, secret, expires_at, token) 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 class RequestTokenTests(utils.ClientTestCase, TokenTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(RequestTokenTests, self).setUp() self.model = request_tokens.RequestToken self.manager = self.client.oauth1.request_tokens self.path_prefix = 'OS-OAUTH1' def test_authorize_request_token(self): request_key = uuid.uuid4().hex info = {'id': request_key, 'key': request_key, 'secret': uuid.uuid4().hex} request_token = request_tokens.RequestToken(self.manager, info) verifier = uuid.uuid4().hex resp_ref = {'token': {'oauth_verifier': verifier}} self.stub_url('PUT', [self.path_prefix, 'authorize', request_key], status_code=200, json=resp_ref) # Assert the manager is returning the expected data role_id = uuid.uuid4().hex token = request_token.authorize([role_id]) self.assertEqual(verifier, token.oauth_verifier) # Assert that the request was sent in the expected structure exp_body = {'roles': [{'id': role_id}]} self.assertRequestBodyIs(json=exp_body) def test_create_request_token(self): project_id = uuid.uuid4().hex consumer_key = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex request_key, request_secret, resp_ref = self._new_oauth_token() headers = {'Content-Type': 'application/x-www-form-urlencoded'} self.stub_url('POST', [self.path_prefix, 'request_token'], status_code=201, text=resp_ref, headers=headers) # Assert the manager is returning request token object request_token = self.manager.create(consumer_key, consumer_secret, project_id) self.assertIsInstance(request_token, self.model) self.assertEqual(request_key, request_token.key) self.assertEqual(request_secret, request_token.secret) # Assert that the project id is in the header self.assertRequestHeaderEqual('requested-project-id', project_id) req_headers = self.requests_mock.last_request.headers oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, signature_method=oauth1.SIGNATURE_HMAC, callback_uri="oob") self._validate_oauth_headers(req_headers['Authorization'], oauth_client) class AccessTokenTests(utils.ClientTestCase, TokenTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(AccessTokenTests, self).setUp() self.manager = self.client.oauth1.access_tokens self.model = access_tokens.AccessToken self.path_prefix = 'OS-OAUTH1' def test_create_access_token_expires_at(self): verifier = uuid.uuid4().hex consumer_key = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex request_key = uuid.uuid4().hex request_secret = uuid.uuid4().hex t = self._new_oauth_token_with_expires_at() access_key, access_secret, expires_at, resp_ref = t headers = {'Content-Type': 'application/x-www-form-urlencoded'} self.stub_url('POST', [self.path_prefix, 'access_token'], status_code=201, text=resp_ref, headers=headers) # Assert that the manager creates an access token object access_token = self.manager.create(consumer_key, consumer_secret, request_key, request_secret, verifier) self.assertIsInstance(access_token, self.model) self.assertEqual(access_key, access_token.key) self.assertEqual(access_secret, access_token.secret) self.assertEqual(expires_at, access_token.expires) req_headers = self.requests_mock.last_request.headers oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, resource_owner_key=request_key, resource_owner_secret=request_secret, signature_method=oauth1.SIGNATURE_HMAC, verifier=verifier) self._validate_oauth_headers(req_headers['Authorization'], oauth_client) class AuthenticateWithOAuthTests(utils.TestCase, TokenTests): def setUp(self): super(AuthenticateWithOAuthTests, self).setUp() if oauth1 is None: self.skipTest('optional package oauthlib is not installed') 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 # Just use an existing project scoped token and change # the methods to oauth1, and add an OS-OAUTH1 section. oauth_token = client_fixtures.project_scoped_token() oauth_token['methods'] = ["oauth1"] oauth_token['OS-OAUTH1'] = {"consumer_id": consumer_key, "access_token_id": access_key} self.stub_auth(json=oauth_token) with self.deprecations.expect_deprecations_here(): a = auth.OAuth(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) class OauthRequestIdTests(utils.TestRequestId, TokenTests): def setUp(self): super(OauthRequestIdTests, self).setUp() self.mgr = consumers.ConsumerManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_consumers(self): body = {"consumer": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin') def test_create_consumers(self): body = {"consumer": {"name": "admin"}} post_mock = self._mock_request_method(method='post', body=body) response = self.mgr.create(name="admin", description="fake") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) post_mock.assert_called_once_with('/OS-OAUTH1/consumers', body={ 'consumer': {'name': 'admin', 'description': 'fake'}}) def test_update_consumers(self): body = {"consumer": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin", "demo") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin', body={ 'consumer': {'description': 'demo'}}) def test_delete_consumers(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin') class TestOAuthLibModule(utils.TestCase): def test_no_oauthlib_installed(self): with mock.patch.object(auth, 'oauth1', None): self.assertRaises(NotImplementedError, auth.OAuth, self.TEST_URL, consumer_key=uuid.uuid4().hex, consumer_secret=uuid.uuid4().hex, access_key=uuid.uuid4().hex, access_secret=uuid.uuid4().hex) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/__init__.py0000664000175000017500000000000013644407055025734 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_trusts.py0000664000175000017500000001164413644407055026620 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 oslo_utils import timeutils from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib import trusts class TrustTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(TrustTests, self).setUp() self.key = 'trust' self.collection_key = 'trusts' self.model = trusts.Trust self.manager = self.client.trusts self.path_prefix = 'OS-TRUST' def new_ref(self, **kwargs): kwargs = super(TrustTests, self).new_ref(**kwargs) kwargs.setdefault('project_id', uuid.uuid4().hex) return kwargs def test_create(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False super(TrustTests, self).test_create(ref=ref) def test_create_limited_uses(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False ref['remaining_uses'] = 5 super(TrustTests, self).test_create(ref=ref) def test_create_roles(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a list of role_names, and converts # internally to the slightly odd list-of-dict API format, so we # have to pass the expected request data to allow correct stubbing ref['role_names'] = ['atestrole'] req_ref['roles'] = [{'name': 'atestrole'}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_role_id_and_names(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a list of role_names, and converts # internally to the slightly odd list-of-dict API format, so we # have to pass the expected request data to allow correct stubbing ref['role_names'] = ['atestrole'] ref['role_ids'] = [uuid.uuid4().hex] req_ref['roles'] = [{'name': 'atestrole'}, {'id': ref['role_ids'][0]}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_expires(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False ref['expires_at'] = timeutils.parse_isotime( '2013-03-04T12:00:01.000000Z') req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a datetime.datetime object for # expires_at, and converts it internally into an iso format datestamp req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z' super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_imp(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = True super(TrustTests, self).test_create(ref=ref) def test_create_roles_imp(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = True req_ref = ref.copy() req_ref.pop('id') ref['role_names'] = ['atestrole'] req_ref['roles'] = [{'name': 'atestrole'}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_list_filter_trustor(self): expected_query = {'trustor_user_id': '12345'} super(TrustTests, self).test_list(expected_query=expected_query, trustor_user='12345') def test_list_filter_trustee(self): expected_query = {'trustee_user_id': '12345'} super(TrustTests, self).test_list(expected_query=expected_query, trustee_user='12345') def test_update(self): # Update not supported for the OS-TRUST API self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/saml2_fixtures.py0000664000175000017500000002470013644407055027161 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 SP_SOAP_RESPONSE = six.b(""" https://openstack4.local/shibboleth ss:mem:6f1f20fafbb38433467e9d477df67615 https://openstack4.local/shibboleth """) SAML2_ASSERTION = six.b(""" x= https://idp.testshib.org/idp/shibboleth VALUE== VALUE= """) 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/auth/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/auth/domains", "previous": 'null', "next": 'null' } } 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]) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_service_catalog.py0000664000175000017500000003071213644407055030403 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from keystoneclient import access from keystoneclient import exceptions from keystoneclient.tests.unit import utils as test_utils from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils class ServiceCatalogTest(utils.TestCase): def setUp(self): super(ServiceCatalogTest, self).setUp() self.AUTH_RESPONSE_BODY = client_fixtures.auth_response_body() self.RESPONSE = test_utils.test_response( headers=client_fixtures.AUTH_RESPONSE_HEADERS ) 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.AccessInfo.factory(self.RESPONSE, 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', endpoint_type='internal'), "https://compute.north.host/novapi/internal") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='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" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='public') self.assertEqual(url, "http://glance.north.host/glanceapi/public") self.AUTH_RESPONSE_BODY['token']['region_name'] = "South" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='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.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) self.assertRaises(exceptions.EmptyCatalog, auth_ref.service_catalog.url_for, service_type='image', endpoint_type='internalURL') def test_service_catalog_get_endpoints_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.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): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.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_servcie_catalog_get_url_region_names(self): auth_ref = access.AccessInfo.factory(None, 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], 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.assertIsNone(urls) def test_service_catalog_param_overrides_body_region(self): self.AUTH_RESPONSE_BODY['token']['region_name'] = "North" # Passing region_name to service catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image') self.assertEqual(url, self.north_endpoints['public']) url = sc.url_for(service_type='image', region_name='South') self.assertEqual(url, self.south_endpoints['public']) endpoints = sc.get_endpoints(service_type='image') self.assertEqual(len(endpoints['image']), 3) 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['image']), 3) for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.south_endpoints[endpoint['interface']]) def test_service_catalog_service_name(self): auth_ref = access.AccessInfo.factory(resp=None, body=self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_name='glance', endpoint_type='public', service_type='image', region_name='North') self.assertEqual('http://glance.north.host/glanceapi/public', url) url = sc.url_for(service_name='glance', endpoint_type='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', endpoint_type='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', endpoint_type='public') self.assertIsNone(urls) def test_service_catalog_without_name(self): pr_auth_ref = access.AccessInfo.factory( resp=None, body=client_fixtures.project_scoped_token()) 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', endpoint_type='public') self.assertEqual(url_ref, url) ab_auth_ref = access.AccessInfo.factory(resp=None, 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', endpoint_type='public') class ServiceCatalogV3Test(ServiceCatalogTest): def test_building_a_service_catalog(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, 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', endpoint_type='internal'), 'https://compute.north.host/novapi/internal') self.assertRaises(exceptions.EndpointNotFound, sc.url_for, 'region_id', 'South', service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='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.AccessInfo.factory(resp=None, body=token) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='public') self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='public', region_name='region-1') self.assertEqual(('public-1', ), urls) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_endpoint_policy.py0000664000175000017500000002453513644407055030456 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import test_endpoint_filter from keystoneclient.tests.unit.v3 import utils class EndpointPolicyTests(utils.ClientTestCase, test_endpoint_filter.EndpointTestUtils): """Test policy-endpoint associations (a.k.a. EndpointPolicy Extension).""" def setUp(self): super(EndpointPolicyTests, self).setUp() self.manager = self.client.endpoint_policy def new_policy_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('blob', uuid.uuid4().hex) return kwargs def new_region_ref(self, **kwargs): kwargs = self.new_ref(**kwargs) return kwargs def new_service_ref(self, **kwargs): kwargs = self.new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) return kwargs def _crud_policy_association_for_endpoint_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex endpoint_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'endpoints', endpoint_id], status_code=204) manager_action(policy=policy_id, endpoint=endpoint_id) def _crud_policy_association_for_endpoint_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() endpoint_ref = self.new_endpoint_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) endpoint = self.client.endpoints.resource_class( self.client.endpoints, endpoint_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'endpoints', endpoint_ref['id']], status_code=204) manager_action(policy=policy, endpoint=endpoint) def test_create_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'PUT', self.manager.create_policy_association_for_endpoint) def test_create_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'PUT', self.manager.create_policy_association_for_endpoint) def test_check_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'HEAD', self.manager.check_policy_association_for_endpoint) def test_check_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'HEAD', self.manager.check_policy_association_for_endpoint) def test_delete_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'DELETE', self.manager.delete_policy_association_for_endpoint) def test_delete_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'DELETE', self.manager.delete_policy_association_for_endpoint) def _crud_policy_association_for_service_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex service_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'services', service_id], status_code=204) manager_action(policy=policy_id, service=service_id) def _crud_policy_association_for_service_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() service_ref = self.new_service_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) service = self.client.services.resource_class( self.client.services, service_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'services', service_ref['id']], status_code=204) manager_action(policy=policy, service=service) def test_create_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'PUT', self.manager.create_policy_association_for_service) def test_create_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'PUT', self.manager.create_policy_association_for_service) def test_check_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'HEAD', self.manager.check_policy_association_for_service) def test_check_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'HEAD', self.manager.check_policy_association_for_service) def test_delete_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'DELETE', self.manager.delete_policy_association_for_service) def test_delete_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'DELETE', self.manager.delete_policy_association_for_service) def _crud_policy_association_for_region_and_service_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex region_id = uuid.uuid4().hex service_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'services', service_id, 'regions', region_id], status_code=204) manager_action(policy=policy_id, region=region_id, service=service_id) def _crud_policy_association_for_region_and_service_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() region_ref = self.new_region_ref() service_ref = self.new_service_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) region = self.client.regions.resource_class( self.client.regions, region_ref, loaded=True) service = self.client.services.resource_class( self.client.services, service_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'services', service_ref['id'], 'regions', region_ref['id']], status_code=204) manager_action(policy=policy, region=region, service=service) def test_create_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'PUT', self.manager.create_policy_association_for_region_and_service) def test_create_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'PUT', self.manager.create_policy_association_for_region_and_service) def test_check_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'HEAD', self.manager.check_policy_association_for_region_and_service) def test_check_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'HEAD', self.manager.check_policy_association_for_region_and_service) def test_delete_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'DELETE', self.manager.delete_policy_association_for_region_and_service) def test_delete_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'DELETE', self.manager.delete_policy_association_for_region_and_service) def test_get_policy_for_endpoint(self): endpoint_id = uuid.uuid4().hex expected_policy = self.new_policy_ref() self.stub_url('GET', ['endpoints', endpoint_id, self.manager.OS_EP_POLICY_EXT, 'policy'], json={'policy': expected_policy}, status_code=200) policy_resp = self.manager.get_policy_for_endpoint( endpoint=endpoint_id) self.assertEqual(expected_policy['id'], policy_resp.id) self.assertEqual(expected_policy['blob'], policy_resp.blob) self.assertEqual(expected_policy['type'], policy_resp.type) def test_list_endpoints_for_policy(self): policy_id = uuid.uuid4().hex endpoints = {'endpoints': [self.new_endpoint_ref(), self.new_endpoint_ref()]} self.stub_url('GET', ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'endpoints'], json=endpoints, status_code=200) endpoints_resp = self.manager.list_endpoints_for_policy( policy=policy_id) expected_endpoint_ids = [ endpoint['id'] for endpoint in endpoints['endpoints']] actual_endpoint_ids = [endpoint.id for endpoint in endpoints_resp] self.assertEqual(expected_endpoint_ids, actual_endpoint_ids) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_credentials.py0000664000175000017500000000243513644407055027547 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import credentials class CredentialTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(CredentialTests, self).setUp() self.key = 'credential' self.collection_key = 'credentials' self.model = credentials.Credential self.manager = self.client.credentials def new_ref(self, **kwargs): kwargs = super(CredentialTests, self).new_ref(**kwargs) kwargs.setdefault('blob', uuid.uuid4().hex) kwargs.setdefault('project_id', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('user_id', uuid.uuid4().hex) return kwargs python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_auth_oidc.py0000664000175000017500000001665313644407055027220 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_config import fixture as config from six.moves import urllib import testtools from keystoneclient.auth import conf from keystoneclient.contrib.auth.v3 import oidc from keystoneclient import session from keystoneclient.tests.unit.v3 import utils ACCESS_TOKEN_ENDPOINT_RESP = {"access_token": "z5H1ITZLlJVDHQXqJun", "token_type": "bearer", "expires_in": 3599, "scope": "profile", "refresh_token": "DCERsh83IAhu9bhavrp"} KEYSTONE_TOKEN_VALUE = uuid.uuid4().hex 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" } } } class AuthenticateOIDCTests(utils.TestCase): GROUP = 'auth' def setUp(self): super(AuthenticateOIDCTests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session() self.IDENTITY_PROVIDER = 'bluepages' self.PROTOCOL = 'oidc' self.USER_NAME = 'oidc_user@example.com' self.PASSWORD = uuid.uuid4().hex self.CLIENT_ID = uuid.uuid4().hex self.CLIENT_SECRET = uuid.uuid4().hex self.ACCESS_TOKEN_ENDPOINT = 'https://localhost:8020/oidc/token' self.FEDERATION_AUTH_URL = '%s/%s' % ( self.TEST_URL, 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') self.oidcplugin = oidc.OidcPassword( self.TEST_URL, self.IDENTITY_PROVIDER, self.PROTOCOL, username=self.USER_NAME, password=self.PASSWORD, client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT) @testtools.skip("TypeError: __init__() got an unexpected keyword" " argument 'project_name'") def test_conf_params(self): """Ensure OpenID Connect config options work.""" section = uuid.uuid4().hex identity_provider = uuid.uuid4().hex protocol = uuid.uuid4().hex username = uuid.uuid4().hex password = uuid.uuid4().hex client_id = uuid.uuid4().hex client_secret = uuid.uuid4().hex access_token_endpoint = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(oidc.OidcPassword.get_options(), group=section) self.conf_fixture.config(auth_plugin='v3oidcpassword', identity_provider=identity_provider, protocol=protocol, username=username, password=password, client_id=client_id, client_secret=client_secret, access_token_endpoint=access_token_endpoint, group=section) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(identity_provider, a.identity_provider) self.assertEqual(protocol, a.protocol) self.assertEqual(username, a.username) self.assertEqual(password, a.password) self.assertEqual(client_id, a.client_id) self.assertEqual(client_secret, a.client_secret) self.assertEqual(access_token_endpoint, a.access_token_endpoint) 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=ACCESS_TOKEN_ENDPOINT_RESP) # Prep all the values and send the request grant_type = 'password' scope = 'profile email' client_auth = (self.CLIENT_ID, self.CLIENT_SECRET) payload = {'grant_type': grant_type, 'username': self.USER_NAME, 'password': self.PASSWORD, 'scope': scope} res = self.oidcplugin._get_access_token(self.session, client_auth, payload, self.ACCESS_TOKEN_ENDPOINT) # Verify the request matches the expected structure self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, res.request.url) self.assertEqual('POST', res.request.method) encoded_payload = urllib.parse.urlencode(payload) self.assertEqual(encoded_payload, res.request.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=UNSCOPED_TOKEN, headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) # Prep all the values and send the request access_token = uuid.uuid4().hex headers = {'Authorization': 'Bearer ' + access_token} res = self.oidcplugin._get_keystone_token(self.session, headers, self.FEDERATION_AUTH_URL) # Verify the request matches the expected structure self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) self.assertEqual('POST', res.request.method) 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=ACCESS_TOKEN_ENDPOINT_RESP) # Mock the output that creates the keystone token self.requests_mock.post( self.FEDERATION_AUTH_URL, json=UNSCOPED_TOKEN, headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) response = self.oidcplugin.get_unscoped_auth_ref(self.session) self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_auth.py0000664000175000017500000003675213644407055026224 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_serialization import jsonutils from testtools import testcase from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): def setUp(self): super(AuthenticateAgainstKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "token": { "methods": [ "token", "password" ], "expires_at": "2999-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 }, } self.TEST_REQUEST_BODY = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "domain": { "name": self.TEST_DOMAIN_NAME }, "name": self.TEST_USER, "password": self.TEST_TOKEN } } }, "scope": { "project": { "id": self.TEST_TENANT_ID }, } } } self.TEST_REQUEST_HEADERS = { 'Content-Type': 'application/json', 'User-Agent': 'python-keystoneclient' } self.TEST_RESPONSE_HEADERS = { 'X-Subject-Token': self.TEST_TOKEN } def test_authenticate_success(self): TEST_TOKEN = "abcdef" ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER self.stub_auth(json=self.TEST_RESPONSE_DICT, subject_token=TEST_TOKEN) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, TEST_TOKEN) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_failure(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] ident['password']['user']['password'] = 'bad_key' error = {"unauthorized": {"message": "Unauthorized", "code": "401"}} self.stub_auth(status_code=401, json=error) with testcase.ExpectedException(exceptions.Unauthorized): with self.deprecations.expect_deprecations_here(): client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password="bad_key", project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_redirect(self): headers = {'Location': self.TEST_ADMIN_URL + '/auth/tokens'} self.stub_auth(status_code=305, text='Use proxy', headers=headers) self.stub_auth(json=self.TEST_RESPONSE_DICT, base_url=self.TEST_ADMIN_URL) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) def test_authenticate_success_domain_username_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) def test_authenticate_success_userid_password_domain_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER scope = self.TEST_REQUEST_BODY['auth']['scope'] del scope['project'] scope['domain'] = {} scope['domain']['id'] = self.TEST_DOMAIN_ID token = self.TEST_RESPONSE_DICT['token'] del token['project'] token['domain'] = {} token['domain']['id'] = self.TEST_DOMAIN_ID token['domain']['name'] = self.TEST_DOMAIN_NAME self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, domain_id=self.TEST_DOMAIN_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_domain_id, self.TEST_DOMAIN_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_userid_password_project_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_tenant_id, self.TEST_TENANT_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_unscoped(self): del self.TEST_RESPONSE_DICT['token']['catalog'] del self.TEST_REQUEST_BODY['auth']['scope'] self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): fake_token = 'fake_token' fake_url = '/fake-url' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(auth_url=self.TEST_URL, token=fake_token) body = jsonutils.loads(self.requests_mock.last_request.body) self.assertEqual(body['auth']['identity']['token']['id'], fake_token) resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) def test_authenticate_success_token_domain_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN scope = self.TEST_REQUEST_BODY['auth']['scope'] del scope['project'] scope['domain'] = {} scope['domain']['id'] = self.TEST_DOMAIN_ID token = self.TEST_RESPONSE_DICT['token'] del token['project'] token['domain'] = {} token['domain']['id'] = self.TEST_DOMAIN_ID token['domain']['name'] = self.TEST_DOMAIN_NAME self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, domain_id=self.TEST_DOMAIN_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_domain_id, self.TEST_DOMAIN_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_project_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_tenant_id, self.TEST_TENANT_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_unscoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN del self.TEST_REQUEST_BODY['auth']['scope'] del self.TEST_RESPONSE_DICT['token']['catalog'] self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): fake_url = '/fake-url' fake_token = 'fake_token' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.auth_token, self.TEST_TOKEN) # the token returned from the authentication will be used resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) # then override that token and the new token shall be used cl.auth_token = fake_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(fake_token, token) # if we clear that overridden token then we fall back to the original del cl.auth_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_access_rules.py0000664000175000017500000000300713644407055027721 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import access_rules class AccessRuleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(AccessRuleTests, self).setUp() self.key = 'access_rule' self.collection_key = 'access_rules' self.model = access_rules.AccessRule self.manager = self.client.access_rules self.path_prefix = 'users/%s' % self.TEST_USER_ID def new_ref(self, **kwargs): kwargs = super(AccessRuleTests, self).new_ref(**kwargs) kwargs.setdefault('path', uuid.uuid4().hex) kwargs.setdefault('method', uuid.uuid4().hex) kwargs.setdefault('service', uuid.uuid4().hex) return kwargs def test_update(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_create(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_regions.py0000664000175000017500000000237113644407055026717 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import regions class RegionTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RegionTests, self).setUp() self.key = 'region' self.collection_key = 'regions' self.model = regions.Region self.manager = self.client.regions def new_ref(self, **kwargs): kwargs = super(RegionTests, self).new_ref(**kwargs) kwargs.setdefault('enabled', True) kwargs.setdefault('id', uuid.uuid4().hex) return kwargs def test_update_enabled_defaults_to_none(self): super(RegionTests, self).test_update( req_ref={'description': uuid.uuid4().hex}) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_auth_saml2.py0000664000175000017500000007250213644407055027313 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 uuid from lxml import etree from oslo_config import fixture as config import requests from six.moves import urllib from keystoneclient.auth import conf from keystoneclient.contrib.auth.v3 import saml2 from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import saml2_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib.federation import saml as saml_manager 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 make_oneline(f.read()) class AuthenticateviaSAML2Tests(utils.TestCase): GROUP = 'auth' class _AuthenticatedResponse(object): headers = { 'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER } def json(self): return saml2_fixtures.UNSCOPED_TOKEN class _AuthenticatedResponseInvalidJson(_AuthenticatedResponse): def json(self): raise ValueError() class _AuthentiatedResponseMissingTokenID(_AuthenticatedResponse): headers = {} def setUp(self): super(AuthenticateviaSAML2Tests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session() self.ECP_SP_EMPTY_REQUEST_HEADERS = { 'Accept': 'text/html; application/vnd.paos+xml', 'PAOS': ('ver="urn:liberty:paos:2003-08";' '"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"') } self.ECP_SP_SAML2_REQUEST_HEADERS = { 'Content-Type': 'application/vnd.paos+xml' } self.ECP_SAML2_NAMESPACES = { 'ecp': 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp', 'S': 'http://schemas.xmlsoap.org/soap/envelope/', 'paos': 'urn:liberty:paos:2003-08' } self.ECP_RELAY_STATE = '//ecp:RelayState' self.ECP_SERVICE_PROVIDER_CONSUMER_URL = ('/S:Envelope/S:Header/paos:' 'Request/' '@responseConsumerURL') self.ECP_IDP_CONSUMER_URL = ('/S:Envelope/S:Header/ecp:Response/' '@AssertionConsumerServiceURL') self.IDENTITY_PROVIDER = 'testidp' self.IDENTITY_PROVIDER_URL = 'http://local.url' self.PROTOCOL = 'saml2' self.FEDERATION_AUTH_URL = '%s/%s' % ( self.TEST_URL, 'OS-FEDERATION/identity_providers/testidp/protocols/saml2/auth') self.SHIB_CONSUMER_URL = ('https://openstack4.local/' 'Shibboleth.sso/SAML2/ECP') self.saml2plugin = saml2.Saml2UnscopedToken( self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL, self.TEST_USER, self.TEST_TOKEN) def test_conf_params(self): section = uuid.uuid4().hex identity_provider = uuid.uuid4().hex identity_provider_url = uuid.uuid4().hex username = uuid.uuid4().hex password = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(saml2.Saml2UnscopedToken.get_options(), group=section) self.conf_fixture.config(auth_plugin='v3unscopedsaml', identity_provider=identity_provider, identity_provider_url=identity_provider_url, username=username, password=password, group=section) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(identity_provider, a.identity_provider) self.assertEqual(identity_provider_url, a.identity_provider_url) self.assertEqual(username, a.username) self.assertEqual(password, a.password) def test_initial_sp_call(self): """Test initial call, expect SOAP message.""" self.requests_mock.get( self.FEDERATION_AUTH_URL, content=make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)) a = self.saml2plugin._send_service_provider_request(self.session) self.assertFalse(a) fixture_soap_response = make_oneline( saml2_fixtures.SP_SOAP_RESPONSE) sp_soap_response = make_oneline( etree.tostring(self.saml2plugin.saml2_authn_request)) error_msg = "Expected %s instead of %s" % (fixture_soap_response, sp_soap_response) self.assertEqual(fixture_soap_response, sp_soap_response, error_msg) self.assertEqual( self.saml2plugin.sp_response_consumer_url, self.SHIB_CONSUMER_URL, "Expected consumer_url set to %s instead of %s" % ( self.SHIB_CONSUMER_URL, str(self.saml2plugin.sp_response_consumer_url))) def test_initial_sp_call_when_saml_authenticated(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) a = self.saml2plugin._send_service_provider_request(self.session) self.assertTrue(a) self.assertEqual( saml2_fixtures.UNSCOPED_TOKEN['token'], self.saml2plugin.authenticated_response.json()['token']) self.assertEqual( saml2_fixtures.UNSCOPED_TOKEN_HEADER, self.saml2plugin.authenticated_response.headers['X-Subject-Token']) def test_get_unscoped_token_when_authenticated(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER, 'Content-Type': 'application/json'}) token, token_body = self.saml2plugin._get_unscoped_token(self.session) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_body) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, token) def test_initial_sp_call_invalid_response(self): """Send initial SP HTTP request and receive wrong server response.""" self.requests_mock.get(self.FEDERATION_AUTH_URL, text='NON XML RESPONSE') self.assertRaises( exceptions.AuthorizationFailure, self.saml2plugin._send_service_provider_request, self.session) def test_send_authn_req_to_idp(self): self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=saml2_fixtures.SAML2_ASSERTION) self.saml2plugin.sp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin.saml2_authn_request = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE) self.saml2plugin._send_idp_saml2_authn_request(self.session) idp_response = make_oneline(etree.tostring( self.saml2plugin.saml2_idp_authn_response)) saml2_assertion_oneline = make_oneline( saml2_fixtures.SAML2_ASSERTION) error = "Expected %s instead of %s" % (saml2_fixtures.SAML2_ASSERTION, idp_response) self.assertEqual(idp_response, saml2_assertion_oneline, error) def test_fail_basicauth_idp_authentication(self): self.requests_mock.post(self.IDENTITY_PROVIDER_URL, status_code=401) self.saml2plugin.sp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin.saml2_authn_request = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE) self.assertRaises( exceptions.Unauthorized, self.saml2plugin._send_idp_saml2_authn_request, self.session) def test_mising_username_password_in_plugin(self): self.assertRaises(TypeError, saml2.Saml2UnscopedToken, self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL) def test_send_authn_response_to_sp(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.saml2plugin.relay_state = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE).xpath( self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES)[0] self.saml2plugin.saml2_idp_authn_response = etree.XML( saml2_fixtures.SAML2_ASSERTION) self.saml2plugin.idp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin._send_service_provider_saml2_authn_response( self.session) token_json = self.saml2plugin.authenticated_response.json()['token'] token = self.saml2plugin.authenticated_response.headers[ 'X-Subject-Token'] self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_json) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, token) def test_consumer_url_mismatch_success(self): self.saml2plugin._check_consumer_urls( self.session, self.SHIB_CONSUMER_URL, self.SHIB_CONSUMER_URL) def test_consumer_url_mismatch(self): self.requests_mock.post(self.SHIB_CONSUMER_URL) invalid_consumer_url = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.saml2plugin._check_consumer_urls, self.session, self.SHIB_CONSUMER_URL, invalid_consumer_url) def test_custom_302_redirection(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, text='BODY', headers={'location': self.FEDERATION_AUTH_URL}, status_code=302) self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.session.redirect = False response = self.session.post( self.SHIB_CONSUMER_URL, data='CLIENT BODY') self.assertEqual(302, response.status_code) self.assertEqual(self.FEDERATION_AUTH_URL, response.headers['location']) response = self.saml2plugin._handle_http_ecp_redirect( self.session, response, 'GET') self.assertEqual(self.FEDERATION_AUTH_URL, response.request.url) self.assertEqual('GET', response.request.method) def test_custom_303_redirection(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, text='BODY', headers={'location': self.FEDERATION_AUTH_URL}, status_code=303) self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.session.redirect = False response = self.session.post( self.SHIB_CONSUMER_URL, data='CLIENT BODY') self.assertEqual(303, response.status_code) self.assertEqual(self.FEDERATION_AUTH_URL, response.headers['location']) response = self.saml2plugin._handle_http_ecp_redirect( self.session, response, 'GET') self.assertEqual(self.FEDERATION_AUTH_URL, response.request.url) self.assertEqual('GET', response.request.method) def test_end_to_end_workflow(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, content=make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)) self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=saml2_fixtures.SAML2_ASSERTION) self.requests_mock.post( self.SHIB_CONSUMER_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER, 'Content-Type': 'application/json'}) self.session.redirect = False response = self.saml2plugin.get_auth_ref(self.session) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, response.auth_token) class ScopeFederationTokenTests(AuthenticateviaSAML2Tests): TEST_TOKEN = client_fixtures.AUTH_SUBJECT_TOKEN def setUp(self): super(ScopeFederationTokenTests, self).setUp() self.PROJECT_SCOPED_TOKEN_JSON = client_fixtures.project_scoped_token() self.PROJECT_SCOPED_TOKEN_JSON['methods'] = ['saml2'] # for better readability self.TEST_TENANT_ID = self.PROJECT_SCOPED_TOKEN_JSON.project_id self.TEST_TENANT_NAME = self.PROJECT_SCOPED_TOKEN_JSON.project_name self.DOMAIN_SCOPED_TOKEN_JSON = client_fixtures.domain_scoped_token() self.DOMAIN_SCOPED_TOKEN_JSON['methods'] = ['saml2'] # for better readability self.TEST_DOMAIN_ID = self.DOMAIN_SCOPED_TOKEN_JSON.domain_id self.TEST_DOMAIN_NAME = self.DOMAIN_SCOPED_TOKEN_JSON.domain_name self.saml2_scope_plugin = saml2.Saml2ScopedToken( self.TEST_URL, saml2_fixtures.UNSCOPED_TOKEN_HEADER, project_id=self.TEST_TENANT_ID) def test_scope_saml2_token_to_project(self): self.stub_auth(json=self.PROJECT_SCOPED_TOKEN_JSON) token = self.saml2_scope_plugin.get_auth_ref(self.session) self.assertTrue(token.project_scoped, "Received token is not scoped") self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) self.assertEqual(self.TEST_TENANT_ID, token.project_id) self.assertEqual(self.TEST_TENANT_NAME, token.project_name) def test_scope_saml2_token_to_invalid_project(self): self.stub_auth(status_code=401) self.saml2_scope_plugin.project_id = uuid.uuid4().hex self.saml2_scope_plugin.project_name = None self.assertRaises(exceptions.Unauthorized, self.saml2_scope_plugin.get_auth_ref, self.session) def test_scope_saml2_token_to_invalid_domain(self): self.stub_auth(status_code=401) self.saml2_scope_plugin.project_id = None self.saml2_scope_plugin.project_name = None self.saml2_scope_plugin.domain_id = uuid.uuid4().hex self.saml2_scope_plugin.domain_name = None self.assertRaises(exceptions.Unauthorized, self.saml2_scope_plugin.get_auth_ref, self.session) def test_scope_saml2_token_to_domain(self): self.stub_auth(json=self.DOMAIN_SCOPED_TOKEN_JSON) token = self.saml2_scope_plugin.get_auth_ref(self.session) self.assertTrue(token.domain_scoped, "Received token is not scoped") self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) self.assertEqual(self.TEST_DOMAIN_ID, token.domain_id) self.assertEqual(self.TEST_DOMAIN_NAME, token.domain_name) def test_dont_set_project_nor_domain(self): self.saml2_scope_plugin.project_id = None self.saml2_scope_plugin.domain_id = None self.assertRaises(exceptions.ValidationError, saml2.Saml2ScopedToken, self.TEST_URL, client_fixtures.AUTH_SUBJECT_TOKEN) 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') @property def _uuid4(self): return '4b911420-4982-4009-8afc-5c596cd487f5' def setUp(self): super(AuthenticateviaADFSTests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session(session=requests.Session()) 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.adfsplugin = saml2.ADFSUnscopedToken( self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, self.TEST_USER, self.TEST_TOKEN) self.ADFS_SECURITY_TOKEN_RESPONSE = _load_xml( 'ADFS_RequestSecurityTokenResponse.xml') self.ADFS_FAULT = _load_xml('ADFS_fault.xml') def test_conf_params(self): section = uuid.uuid4().hex identity_provider = uuid.uuid4().hex identity_provider_url = uuid.uuid4().hex sp_endpoint = uuid.uuid4().hex username = uuid.uuid4().hex password = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(saml2.ADFSUnscopedToken.get_options(), group=section) self.conf_fixture.config(auth_plugin='v3unscopedadfs', identity_provider=identity_provider, identity_provider_url=identity_provider_url, service_provider_endpoint=sp_endpoint, username=username, password=password, group=section) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(identity_provider, a.identity_provider) self.assertEqual(identity_provider_url, a.identity_provider_url) self.assertEqual(sp_endpoint, a.service_provider_endpoint) self.assertEqual(username, a.username) self.assertEqual(password, a.password) def test_get_adfs_security_token(self): """Test ADFSUnscopedToken._get_adfs_security_token().""" self.requests_mock.post( self.IDENTITY_PROVIDER_URL, content=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.assertEqual(fixture_response, 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_sp_request(self): assertion = etree.XML(self.ADFS_SECURITY_TOKEN_RESPONSE) assertion = assertion.xpath( saml2.ADFSUnscopedToken.ADFS_ASSERTION_XPATH, namespaces=saml2.ADFSUnscopedToken.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. """ self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=make_oneline(self.ADFS_FAULT), 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, keystoneclient should be prepared and correctly raise exceptions.InternalServerError once it cannot parse XML fault message """ self.requests_mock.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_uri() method. 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.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): # There are no cookies in the session initially, and # _access_service_provider requires a cookie in the session. self.assertRaises(exceptions.AuthorizationFailure, self.adfsplugin._access_service_provider, self.session) def test_check_valid_token_when_authenticated(self): self.requests_mock.get(self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers=client_fixtures.AUTH_RESPONSE_HEADERS) # _access_service_provider requires a cookie in the session. cookie = requests.cookies.create_cookie( name=self.getUniqueString(), value=self.getUniqueString()) self.session.session.cookies.set_cookie(cookie) 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.post(self.IDENTITY_PROVIDER_URL, content=self.ADFS_SECURITY_TOKEN_RESPONSE, status_code=200) self.requests_mock.post(self.SP_ENDPOINT, headers={"set-cookie": 'x'}, status_code=302) self.requests_mock.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. cookie = requests.cookies.create_cookie( name=self.getUniqueString(), value=self.getUniqueString()) self.session.session.cookies.set_cookie(cookie) token, token_json = self.adfsplugin._get_unscoped_token(self.session) self.assertEqual(token, client_fixtures.AUTH_SUBJECT_TOKEN) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_json) class SAMLGenerationTests(utils.ClientTestCase): def setUp(self): super(SAMLGenerationTests, self).setUp() self.manager = self.client.federation.saml self.SAML2_FULL_URL = ''.join([self.TEST_URL, saml_manager.SAML2_ENDPOINT]) self.ECP_FULL_URL = ''.join([self.TEST_URL, saml_manager.ECP_ENDPOINT]) def test_saml_create(self): """Test that a token can be exchanged for a SAML assertion.""" token_id = uuid.uuid4().hex service_provider_id = uuid.uuid4().hex # Mock the returned text for '/auth/OS-FEDERATION/saml2 self.requests_mock.post(self.SAML2_FULL_URL, text=saml2_fixtures.TOKEN_BASED_SAML) text = self.manager.create_saml_assertion(service_provider_id, token_id) # Ensure returned text is correct self.assertEqual(saml2_fixtures.TOKEN_BASED_SAML, text) # Ensure request headers and body are correct req_json = self.requests_mock.last_request.json() self.assertEqual(token_id, req_json['auth']['identity']['token']['id']) self.assertEqual(service_provider_id, req_json['auth']['scope']['service_provider']['id']) self.assertRequestHeaderEqual('Content-Type', 'application/json') def test_ecp_create(self): """Test that a token can be exchanged for an ECP wrapped assertion.""" token_id = uuid.uuid4().hex service_provider_id = uuid.uuid4().hex # Mock returned text for '/auth/OS-FEDERATION/saml2/ecp self.requests_mock.post(self.ECP_FULL_URL, text=saml2_fixtures.TOKEN_BASED_ECP) text = self.manager.create_ecp_assertion(service_provider_id, token_id) # Ensure returned text is correct self.assertEqual(saml2_fixtures.TOKEN_BASED_ECP, text) # Ensure request headers and body are correct req_json = self.requests_mock.last_request.json() self.assertEqual(token_id, req_json['auth']['identity']['token']['id']) self.assertEqual(service_provider_id, req_json['auth']['scope']['service_provider']['id']) self.assertRequestHeaderEqual('Content-Type', 'application/json') python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_policies.py0000664000175000017500000000216113644407055027055 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import policies class PolicyTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(PolicyTests, self).setUp() self.key = 'policy' self.collection_key = 'policies' self.model = policies.Policy self.manager = self.client.policies def new_ref(self, **kwargs): kwargs = super(PolicyTests, self).new_ref(**kwargs) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('blob', uuid.uuid4().hex) return kwargs python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_auth_manager.py0000664000175000017500000000376213644407055027711 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import auth class AuthProjectsTest(utils.ClientTestCase): def setUp(self): super(AuthProjectsTest, self).setUp() self.v3token = fixture.V3Token() self.stub_auth(json=self.v3token) self.stub_url('GET', [], json={'version': fixture.V3Discovery(self.TEST_URL)}) def create_resource(self, id=None, name=None, **kwargs): kwargs['id'] = id or uuid.uuid4().hex kwargs['name'] = name or uuid.uuid4().hex return kwargs def test_get_projects(self): body = {'projects': [self.create_resource(), self.create_resource(), self.create_resource()]} self.stub_url('GET', ['auth', 'projects'], json=body) projects = self.client.auth.projects() self.assertEqual(3, len(projects)) for p in projects: self.assertIsInstance(p, auth.Project) def test_get_domains(self): body = {'domains': [self.create_resource(), self.create_resource(), self.create_resource()]} self.stub_url('GET', ['auth', 'domains'], json=body) domains = self.client.auth.domains() self.assertEqual(3, len(domains)) for d in domains: self.assertIsInstance(d, auth.Domain) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_users.py0000664000175000017500000002654013644407055026416 0ustar zuulzuul00000000000000# Copyright 2012 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 mock import uuid from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import users class UserTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(UserTests, self).setUp() self.key = 'user' self.collection_key = 'users' self.model = users.User self.manager = self.client.users def new_ref(self, **kwargs): kwargs = super(UserTests, self).new_ref(**kwargs) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('default_project_id', uuid.uuid4().hex) return kwargs def test_add_user_to_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.add_to_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.remove_from_group, user=ref['id'], group=None) def test_list_users_in_group(self): group_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['groups', group_id, self.collection_key], entity=ref_list) returned_list = self.manager.list(group=group_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_check_user_in_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.check_in_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.check_in_group, user=ref['id'], group=None) def test_remove_user_from_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.remove_from_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.remove_from_group, user=ref['id'], group=None) def test_create_doesnt_log_password(self): password = uuid.uuid4().hex ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() param_ref['password'] = password params = utils.parameterize(param_ref) self.manager.create(**params) self.assertNotIn(password, self.logger.output) def test_create_with_project(self): # Can create a user with the deprecated project option rather than # default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() # Use deprecated project_id rather than new default_project_id. param_ref['project_id'] = param_ref.pop('default_project_id') params = utils.parameterize(param_ref) returned = self.manager.create(**params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_create_with_project_and_default_project(self): # Can create a user with the deprecated project and default_project_id. # The backend call should only pass the default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() # Add the deprecated project_id in the call, the value will be ignored. param_ref['project_id'] = 'project' params = utils.parameterize(param_ref) returned = self.manager.create(**params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_doesnt_log_password(self): password = uuid.uuid4().hex ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) param_ref['password'] = password params = utils.parameterize(param_ref) self.manager.update(ref['id'], **params) self.assertNotIn(password, self.logger.output) def test_update_with_project(self): # Can update a user with the deprecated project option rather than # default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) # Use deprecated project_id rather than new default_project_id. param_ref['project_id'] = param_ref.pop('default_project_id') params = utils.parameterize(param_ref) returned = self.manager.update(ref['id'], **params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_with_project_and_default_project(self, ref=None): self.deprecations.expect_deprecations() ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) # Add the deprecated project_id in the call, the value will be ignored. param_ref['project_id'] = 'project' params = utils.parameterize(param_ref) returned = self.manager.update(ref['id'], **params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_password(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex self.stub_url('POST', [self.collection_key, self.TEST_USER_ID, 'password']) self.client.user_id = self.TEST_USER_ID self.manager.update_password(old_password, new_password) exp_req_body = { 'user': { 'password': new_password, 'original_password': old_password } } self.assertEqual( '%s/users/%s/password' % (self.TEST_URL, self.TEST_USER_ID), self.requests_mock.last_request.url) self.assertRequestBodyIs(json=exp_req_body) self.assertNotIn(old_password, self.logger.output) self.assertNotIn(new_password, self.logger.output) def test_update_password_with_no_hardcoded_endpoint_filter(self): # test to ensure the 'endpoint_filter' parameter is not being # passed from the manager. Endpoint filtering should be done at # the Session, not the individual managers. old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex expected_params = {'user': {'password': new_password, 'original_password': old_password}} user_password_update_path = '/users/%s/password' % self.TEST_USER_ID self.client.user_id = self.TEST_USER_ID # NOTE(gyee): user manager subclass keystoneclient.base.Manager # and utilize the _update() method in the base class to interface # with the client session to perform the update. In the case, we # just need to make sure the 'endpoint_filter' parameter is not # there. with mock.patch('keystoneclient.base.Manager._update') as m: self.manager.update_password(old_password, new_password) m.assert_called_with(user_password_update_path, expected_params, method='POST', log=False) def test_update_password_with_bad_inputs(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex # users can't unset their password self.assertRaises(exceptions.ValidationError, self.manager.update_password, old_password, None) self.assertRaises(exceptions.ValidationError, self.manager.update_password, old_password, '') # users can't start with empty passwords self.assertRaises(exceptions.ValidationError, self.manager.update_password, None, new_password) self.assertRaises(exceptions.ValidationError, self.manager.update_password, '', new_password) # this wouldn't result in any change anyway self.assertRaises(exceptions.ValidationError, self.manager.update_password, None, None) self.assertRaises(exceptions.ValidationError, self.manager.update_password, '', '') password = uuid.uuid4().hex self.assertRaises(exceptions.ValidationError, self.manager.update_password, password, password) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_services.py0000664000175000017500000000325313644407055027074 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import services class ServiceTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ServiceTests, self).setUp() self.key = 'service' self.collection_key = 'services' self.model = services.Service self.manager = self.client.services def new_ref(self, **kwargs): kwargs = super(ServiceTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_list_filter_name(self): filter_name = uuid.uuid4().hex expected_query = {'name': filter_name} super(ServiceTests, self).test_list(expected_query=expected_query, name=filter_name) def test_list_filter_type(self): filter_type = uuid.uuid4().hex expected_query = {'type': filter_type} super(ServiceTests, self).test_list(expected_query=expected_query, type=filter_type) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_domains.py0000664000175000017500000000364413644407055026707 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import domains class DomainTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(DomainTests, self).setUp() self.key = 'domain' self.collection_key = 'domains' self.model = domains.Domain self.manager = self.client.domains def new_ref(self, **kwargs): kwargs = super(DomainTests, self).new_ref(**kwargs) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_filter_for_default_domain_by_id(self): ref = self.new_ref(id='default') super(DomainTests, self).test_list_by_id( ref=ref, id=ref['id']) def test_list_filter_name(self): super(DomainTests, self).test_list(name='adomain123') def test_list_filter_enabled(self): super(DomainTests, self).test_list(enabled=True) def test_list_filter_disabled(self): # False is converted to '0' ref bug #1267530 expected_query = {'enabled': '0'} super(DomainTests, self).test_list(expected_query=expected_query, enabled=False) def test_update_enabled_defaults_to_none(self): super(DomainTests, self).test_update( req_ref={'name': uuid.uuid4().hex}) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_groups.py0000664000175000017500000000407713644407055026575 0ustar zuulzuul00000000000000# Copyright 2012 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 uuid from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import groups class GroupTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(GroupTests, self).setUp() self.key = 'group' self.collection_key = 'groups' self.model = groups.Group self.manager = self.client.groups def new_ref(self, **kwargs): kwargs = super(GroupTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_list_groups_for_user(self): user_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['users', user_id, self.collection_key], status_code=200, entity=ref_list) returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_groups_for_domain(self): ref_list = [self.new_ref(), self.new_ref()] domain_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], status_code=200, entity=ref_list) returned_list = self.manager.list(domain=domain_id) self.assertTrue(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('domain_id=%s' % domain_id) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_projects.py0000664000175000017500000004104113644407055027077 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import uuid from keystoneauth1 import exceptions as ksa_exceptions from keystoneclient import exceptions as ksc_exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import projects class ProjectTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ProjectTests, self).setUp() self.key = 'project' self.collection_key = 'projects' self.model = projects.Project self.manager = self.client.projects def new_ref(self, **kwargs): kwargs = super(ProjectTests, self).new_ref(**kwargs) return self._new_project_ref(ref=kwargs) def _new_project_ref(self, ref=None): ref = ref or {} ref.setdefault('domain_id', uuid.uuid4().hex) ref.setdefault('enabled', True) ref.setdefault('name', uuid.uuid4().hex) return ref def test_list_projects_for_user(self): ref_list = [self.new_ref(), self.new_ref()] user_id = uuid.uuid4().hex self.stub_entity('GET', ['users', user_id, self.collection_key], entity=ref_list) returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_projects_for_domain(self): ref_list = [self.new_ref(), self.new_ref()] domain_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list(domain=domain_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('domain_id=%s' % domain_id) def test_list_projects_for_parent(self): ref_list = [self.new_ref(), self.new_ref()] parent_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list(parent=parent_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('parent_id=%s' % parent_id) def test_create_with_parent(self): parent_ref = self.new_ref() parent_ref['parent_id'] = uuid.uuid4().hex parent = self.test_create(ref=parent_ref) parent.id = parent_ref['id'] # Create another project under 'parent' in the hierarchy ref = self.new_ref() ref['parent_id'] = parent.id child_ref = ref.copy() del child_ref['parent_id'] child_ref['parent'] = parent # test_create() pops the 'id' of the mocked response del ref['id'] # Resource objects may peform lazy-loading. The create() method of # ProjectManager will try to access the 'uuid' attribute of the parent # object, which will trigger a call to fetch the Resource attributes. self.stub_entity('GET', id=parent_ref['id'], entity=parent_ref) self.test_create(ref=child_ref, req_ref=ref) def test_create_with_parent_id(self): ref = self._new_project_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('POST', entity=ref, status_code=201) returned = self.manager.create(name=ref['name'], domain=ref['domain_id'], parent_id=ref['parent_id']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(ref) def test_create_with_parent_and_parent_id(self): ref = self._new_project_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('POST', entity=ref, status_code=201) # Should ignore the 'parent_id' argument since we are also passing # 'parent' returned = self.manager.create(name=ref['name'], domain=ref['domain_id'], parent=ref['parent_id'], parent_id=uuid.uuid4().hex) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(ref) def _create_projects_hierarchy(self, hierarchy_size=3): """Create a project hierarchy with specified size. :param hierarchy_size: the desired hierarchy size, default is 3. :returns: a list of the projects in the created hierarchy. """ ref = self.new_ref() project_id = ref['id'] projects = [ref] for i in range(1, hierarchy_size): new_ref = self.new_ref() new_ref['parent_id'] = project_id projects.append(new_ref) project_id = new_ref['id'] return projects def test_get_with_subtree_as_ids(self): projects = self._create_projects_hierarchy() ref = projects[0] # We will query for projects[0] subtree, it should include projects[1] # and projects[2] structured like the following: # { # projects[1]: { # projects[2]: None # } # } ref['subtree'] = { projects[1]['id']: { projects[2]['id']: None } } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], subtree_as_ids=True) self.assertQueryStringIs('subtree_as_ids') self.assertEqual(ref['subtree'], returned.subtree) def test_get_with_parents_as_ids(self): projects = self._create_projects_hierarchy() ref = projects[2] # We will query for projects[2] parents, it should include projects[1] # and projects[0] structured like the following: # { # projects[1]: { # projects[0]: None # } # } ref['parents'] = { projects[1]['id']: { projects[0]['id']: None } } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_ids=True) self.assertQueryStringIs('parents_as_ids') self.assertEqual(ref['parents'], returned.parents) def test_get_with_parents_as_ids_and_subtree_as_ids(self): ref = self.new_ref() projects = self._create_projects_hierarchy() ref = projects[1] # We will query for projects[1] subtree and parents. The subtree should # include projects[2] and the parents should include projects[2]. ref['parents'] = { projects[0]['id']: None } ref['subtree'] = { projects[2]['id']: None } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_ids=True, subtree_as_ids=True) self.assertQueryStringIs('subtree_as_ids&parents_as_ids') self.assertEqual(ref['parents'], returned.parents) self.assertEqual(ref['subtree'], returned.subtree) def test_get_with_subtree_as_list(self): projects = self._create_projects_hierarchy() ref = projects[0] ref['subtree_as_list'] = [] for i in range(1, len(projects)): ref['subtree_as_list'].append(projects[i]) self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], subtree_as_list=True) self.assertQueryStringIs('subtree_as_list') for i in range(1, len(projects)): for attr in projects[i]: child = getattr(returned, 'subtree_as_list')[i - 1] self.assertEqual( child[attr], projects[i][attr], 'Expected different %s' % attr) def test_get_with_parents_as_list(self): projects = self._create_projects_hierarchy() ref = projects[2] ref['parents_as_list'] = [] for i in range(0, len(projects) - 1): ref['parents_as_list'].append(projects[i]) self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_list=True) self.assertQueryStringIs('parents_as_list') for i in range(0, len(projects) - 1): for attr in projects[i]: parent = getattr(returned, 'parents_as_list')[i] self.assertEqual( parent[attr], projects[i][attr], 'Expected different %s' % attr) def test_get_with_parents_as_list_and_subtree_as_list(self): ref = self.new_ref() projects = self._create_projects_hierarchy() ref = projects[1] ref['parents_as_list'] = [projects[0]] ref['subtree_as_list'] = [projects[2]] self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_list=True, subtree_as_list=True) self.assertQueryStringIs('subtree_as_list&parents_as_list') for attr in projects[0]: parent = getattr(returned, 'parents_as_list')[0] self.assertEqual( parent[attr], projects[0][attr], 'Expected different %s' % attr) for attr in projects[2]: child = getattr(returned, 'subtree_as_list')[0] self.assertEqual( child[attr], projects[2][attr], 'Expected different %s' % attr) def test_get_with_invalid_parameters_combination(self): # subtree_as_list and subtree_as_ids can not be included at the # same time in the call. self.assertRaises(ksc_exceptions.ValidationError, self.manager.get, project=uuid.uuid4().hex, subtree_as_list=True, subtree_as_ids=True) # parents_as_list and parents_as_ids can not be included at the # same time in the call. self.assertRaises(ksc_exceptions.ValidationError, self.manager.get, project=uuid.uuid4().hex, parents_as_list=True, parents_as_ids=True) def test_update_with_parent_project(self): ref = self.new_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('PATCH', id=ref['id'], entity=ref, status_code=403) req_ref = ref.copy() req_ref.pop('id') # NOTE(rodrigods): this is the expected behaviour of the Identity # server, a different implementation might not fail this request. self.assertRaises(ksa_exceptions.Forbidden, self.manager.update, ref['id'], **utils.parameterize(req_ref)) def test_add_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=201) self.manager.add_tag(ref['id'], tag_name) def test_update_tags(self): new_tags = ["blue", "orange"] ref = self.new_ref() self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags"], json={"tags": new_tags}, status_code=200) ret = self.manager.update_tags(ref['id'], new_tags) self.assertEqual(ret, new_tags) def test_delete_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("DELETE", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=204) self.manager.delete_tag(ref['id'], tag_name) def test_delete_all_tags(self): ref = self.new_ref() self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags"], json={"tags": []}, status_code=200) ret = self.manager.update_tags(ref['id'], []) self.assertEqual([], ret) def test_list_tags(self): ref = self.new_ref() tags = ["blue", "orange", "green"] self.stub_url("GET", parts=[self.collection_key, ref['id'], "tags"], json={"tags": tags}, status_code=200) ret_tags = self.manager.list_tags(ref['id']) self.assertEqual(tags, ret_tags) def test_check_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("HEAD", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=204) self.assertTrue(self.manager.check_tag(ref['id'], tag_name)) no_tag = "orange" self.stub_url("HEAD", parts=[self.collection_key, ref['id'], "tags", no_tag], status_code=404) self.assertFalse(self.manager.check_tag(ref['id'], no_tag)) def _build_project_response(self, tags): project_id = uuid.uuid4().hex ret = {"projects": [ {"is_domain": False, "description": "", "tags": tags, "enabled": True, "id": project_id, "parent_id": "default", "domain_id": "default", "name": project_id} ]} return ret class ProjectsRequestIdTests(utils.TestRequestId): url = "/projects" def setUp(self): super(ProjectsRequestIdTests, self).setUp() self.mgr = projects.ProjectManager(self.client) self.mgr.resource_class = projects.Project def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_project(self): body = {"project": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get(project='admin') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '/admin') def test_create_project(self): body = {"project": {"name": "admin", "domain": "admin"}} post_mock = self._mock_request_method(method='post', body=body) response = self.mgr.create('admin', 'admin') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) post_mock.assert_called_once_with(self.url, body={'project': { 'name': 'admin', 'enabled': True, 'domain_id': 'admin'}}) def test_list_project(self): body = {"projects": [{"name": "admin"}, {"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) returned_list = self.mgr.list() self.assertEqual(returned_list.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '?') def test_update_project(self): body = {"project": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.update("admin", domain='demo') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with(self.url + '/admin', body={ 'project': {'domain_id': 'demo'}}) self.assertFalse(put_mock.called) def test_delete_project(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '/admin') python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/utils.py0000664000175000017500000003151613644407055025355 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 requests import uuid from six.moves.urllib import parse as urlparse from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils from keystoneclient.v3 import client def parameterize(ref): """Rewrite attributes to match the kwarg naming convention in client. >>> parameterize({'project_id': 0}) {'project': 0} """ params = ref.copy() for key in ref: if key[-3:] == '_id': params.setdefault(key[:-3], params.pop(key)) return params class UnauthenticatedTestCase(utils.TestCase): """Class used as base for unauthenticated calls.""" 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') class TestCase(UnauthenticatedTestCase): TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v3" 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" }, { "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_IDENTITY_ENDPOINT, "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" }] def stub_auth(self, subject_token=None, **kwargs): if not subject_token: subject_token = self.TEST_TOKEN try: response_list = kwargs['response_list'] except KeyError: headers = kwargs.setdefault('headers', {}) headers['X-Subject-Token'] = subject_token else: for resp in response_list: headers = resp.setdefault('headers', {}) headers['X-Subject-Token'] = subject_token self.stub_url('POST', ['auth', 'tokens'], **kwargs) class ClientTestCase(utils.ClientTestCaseMixin, TestCase): ORIGINAL_CLIENT_TYPE = 'original' KSC_SESSION_CLIENT_TYPE = 'ksc-session' KSA_SESSION_CLIENT_TYPE = 'ksa-session' scenarios = [ ( ORIGINAL_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.OriginalV3, 'client_type': ORIGINAL_CLIENT_TYPE } ), ( KSC_SESSION_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.KscSessionV3, 'client_type': KSC_SESSION_CLIENT_TYPE } ), ( KSA_SESSION_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.KsaSessionV3, 'client_type': KSA_SESSION_CLIENT_TYPE } ) ] @property def is_original_client(self): return self.client_type == self.ORIGINAL_CLIENT_TYPE @property def is_session_client(self): return self.client_type in (self.KSC_SESSION_CLIENT_TYPE, self.KSA_SESSION_CLIENT_TYPE) class CrudTests(object): key = None collection_key = None model = None manager = None path_prefix = None def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault(uuid.uuid4().hex, uuid.uuid4().hex) return kwargs def encode(self, entity): if isinstance(entity, dict): return {self.key: entity} if isinstance(entity, list): return {self.collection_key: entity} raise NotImplementedError('Are you sure you want to encode that?') def stub_entity(self, method, parts=None, entity=None, id=None, **kwargs): if entity: entity = self.encode(entity) kwargs['json'] = entity if not parts: parts = [self.collection_key] if self.path_prefix: parts.insert(0, self.path_prefix) if id: if not parts: parts = [] parts.append(id) self.stub_url(method, parts=parts, **kwargs) def assertEntityRequestBodyIs(self, entity): self.assertRequestBodyIs(json=self.encode(entity)) def test_create(self, ref=None, req_ref=None): deprecations = self.useFixture(client_fixtures.Deprecations()) deprecations.expect_deprecations() ref = ref or self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) if req_ref: req_ref = req_ref.copy() else: req_ref = ref.copy() req_ref.pop('id') self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**parameterize(manager_ref)) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) # The entity created here may be used in other test cases return returned def test_get(self, ref=None): ref = ref or self.new_ref() self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def _get_expected_path(self, expected_path=None): if not expected_path: if self.path_prefix: expected_path = 'v3/%s/%s' % (self.path_prefix, self.collection_key) else: expected_path = 'v3/%s' % self.collection_key return expected_path def test_list_by_id(self, ref=None, **filter_kwargs): """Test ``entities.list(id=x)`` being rewritten as ``GET /v3/entities/x``. This tests an edge case of each manager's list() implementation, to ensure that it "does the right thing" when users call ``.list()`` when they should have used ``.get()``. """ if 'id' not in filter_kwargs: ref = ref or self.new_ref() filter_kwargs['id'] = ref['id'] self.assertRaises(TypeError, self.manager.list, **filter_kwargs) def test_list(self, ref_list=None, expected_path=None, expected_query=None, **filter_kwargs): ref_list = ref_list or [self.new_ref(), self.new_ref()] expected_path = self._get_expected_path(expected_path) self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path), json=self.encode(ref_list)) returned_list = self.manager.list(**filter_kwargs) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] qs_args = self.requests_mock.last_request.qs qs_args_expected = expected_query or filter_kwargs for key, value in qs_args_expected.items(): self.assertIn(key, qs_args) # The querystring value is a list. Note we convert the value to a # string and lower, as the query string is always a string and the # filter_kwargs may contain non-string values, for example a # boolean, causing the comaprison to fail. self.assertIn(str(value).lower(), qs_args[key]) # Also check that no query string args exist which are not expected for key in qs_args: self.assertIn(key, qs_args_expected) def test_list_params(self): ref_list = [self.new_ref()] filter_kwargs = {uuid.uuid4().hex: uuid.uuid4().hex} expected_path = self._get_expected_path() self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path), json=self.encode(ref_list)) self.manager.list(**filter_kwargs) self.assertQueryStringContains(**filter_kwargs) def test_find(self, ref=None): ref = ref or self.new_ref() ref_list = [ref] self.stub_entity('GET', entity=ref_list) returned = self.manager.find(name=getattr(ref, 'name', None)) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) if hasattr(ref, 'name'): self.assertQueryStringIs('name=%s' % ref['name']) else: self.assertQueryStringIs('') def test_update(self, ref=None, req_ref=None): deprecations = self.useFixture(client_fixtures.Deprecations()) deprecations.expect_deprecations() ref = ref or self.new_ref() self.stub_entity('PATCH', id=ref['id'], entity=ref) # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) if req_ref: req_ref = req_ref.copy() else: req_ref = ref.copy() req_ref.pop('id') returned = self.manager.update(ref['id'], **parameterize(req_ref)) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_delete(self, ref=None): ref = ref or self.new_ref() self.stub_entity('DELETE', id=ref['id'], status_code=204) self.manager.delete(ref['id']) class TestRequestId(TestCase): resp = requests.Response() TEST_REQUEST_ID = uuid.uuid4().hex resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID def setUp(self): super(TestRequestId, self).setUp() auth = v3.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_federation.py0000664000175000017500000007537013644407055027402 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 fixtures import uuid from keystoneauth1 import exceptions from keystoneauth1 import fixture from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneauth1.tests.unit import k2k_fixtures import six from testtools import matchers from keystoneclient import access from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client from keystoneclient.v3.contrib.federation import base from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3.contrib.federation import mappings from keystoneclient.v3.contrib.federation import protocols from keystoneclient.v3.contrib.federation import service_providers from keystoneclient.v3 import domains from keystoneclient.v3 import projects class IdentityProviderTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(IdentityProviderTests, self).setUp() self.key = 'identity_provider' self.collection_key = 'identity_providers' self.model = identity_providers.IdentityProvider self.manager = self.client.federation.identity_providers self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_positional_parameters_expect_fail(self): """Ensure CrudManager raises TypeError exceptions. After passing wrong number of positional arguments an exception should be raised. Operations to be tested: * create() * get() * list() * delete() * update() """ POS_PARAM_1 = uuid.uuid4().hex POS_PARAM_2 = uuid.uuid4().hex POS_PARAM_3 = uuid.uuid4().hex PARAMETERS = { 'create': (POS_PARAM_1, POS_PARAM_2), 'get': (POS_PARAM_1, POS_PARAM_2), 'list': (POS_PARAM_1, POS_PARAM_2), 'update': (POS_PARAM_1, POS_PARAM_2, POS_PARAM_3), 'delete': (POS_PARAM_1, POS_PARAM_2) } for f_name, args in PARAMETERS.items(): self.assertRaises(TypeError, getattr(self.manager, f_name), *args) def test_create(self): ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') self.stub_entity('PUT', entity=ref, id=ref['id'], status_code=201) returned = self.manager.create(**ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) class MappingTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(MappingTests, self).setUp() self.key = 'mapping' self.collection_key = 'mappings' self.model = mappings.Mapping self.manager = self.client.federation.mappings self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('rules', [uuid.uuid4().hex, uuid.uuid4().hex]) return kwargs def test_create(self): ref = self.new_ref() manager_ref = ref.copy() mapping_id = manager_ref.pop('id') req_ref = ref.copy() self.stub_entity('PUT', entity=req_ref, id=mapping_id, status_code=201) returned = self.manager.create(mapping_id=mapping_id, **manager_ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(manager_ref) class ProtocolTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ProtocolTests, self).setUp() self.key = 'protocol' self.collection_key = 'protocols' self.model = protocols.Protocol self.manager = self.client.federation.protocols self.path_prefix = 'OS-FEDERATION/identity_providers' def _transform_to_response(self, ref): """Construct a response body from a dictionary.""" response = copy.deepcopy(ref) del response['identity_provider'] return response def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('mapping_id', uuid.uuid4().hex) kwargs.setdefault('identity_provider', uuid.uuid4().hex) return kwargs def build_parts(self, idp_id, protocol_id=None): """Build array used to construct mocking URL. Construct and return array with URL parts later used by methods like utils.TestCase.stub_entity(). Example of URL: ``OS-FEDERATION/identity_providers/{idp_id}/ protocols/{protocol_id}`` """ parts = ['OS-FEDERATION', 'identity_providers', idp_id, 'protocols'] if protocol_id: parts.append(protocol_id) return parts def test_build_url_provide_base_url(self): base_url = uuid.uuid4().hex parameters = {'base_url': base_url} url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual('/'.join([base_url, self.collection_key]), url) def test_build_url_w_idp_id(self): """Test whether kwargs ``base_url`` discards object's base_url. This test shows, that when ``base_url`` is specified in the dict_args_in_out dictionary, values like ``identity_provider_id`` are not taken into consideration while building the url. """ base_url, identity_provider_id = uuid.uuid4().hex, uuid.uuid4().hex parameters = { 'base_url': base_url, 'identity_provider_id': identity_provider_id } url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual('/'.join([base_url, self.collection_key]), url) def test_build_url_default_base_url(self): identity_provider_id = uuid.uuid4().hex parameters = { 'identity_provider_id': identity_provider_id } url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual( '/'.join([self.manager.base_url, identity_provider_id, self.manager.collection_key]), url) def test_create(self): """Test creating federation protocol tied to an Identity Provider. URL to be tested: PUT /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('PUT', entity=expected, parts=parts, status_code=201) returned = self.manager.create( protocol_id=ref['id'], identity_provider=ref['identity_provider'], mapping=ref['mapping_id']) self.assertEqual(expected, returned.to_dict()) request_body = {'mapping_id': ref['mapping_id']} self.assertEntityRequestBodyIs(request_body) def test_get(self): """Fetch federation protocol object. URL to be tested: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('GET', entity=expected, parts=parts, status_code=201) returned = self.manager.get(ref['identity_provider'], ref['id']) self.assertIsInstance(returned, self.model) self.assertEqual(expected, returned.to_dict()) def test_delete(self): """Delete federation protocol object. URL to be tested: DELETE /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('DELETE', parts=parts, status_code=204) self.manager.delete(ref['identity_provider'], ref['id']) def test_list(self): """Test listing all federation protocols tied to the Identity Provider. URL to be tested: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols """ def _ref_protocols(): return { 'id': uuid.uuid4().hex, 'mapping_id': uuid.uuid4().hex } ref = self.new_ref() expected = [_ref_protocols() for _ in range(3)] parts = self.build_parts(idp_id=ref['identity_provider']) self.stub_entity('GET', parts=parts, entity=expected, status_code=200) returned = self.manager.list(ref['identity_provider']) for obj, ref_obj in zip(returned, expected): self.assertEqual(obj.to_dict(), ref_obj) def test_list_by_id(self): # The test in the parent class needs to be overridden because it # assumes globally unique IDs, which is not the case with protocol IDs # (which are contextualized per identity provider). ref = self.new_ref() super(ProtocolTests, self).test_list_by_id( ref=ref, identity_provider=ref['identity_provider'], id=ref['id']) def test_list_params(self): request_args = self.new_ref() filter_kwargs = {uuid.uuid4().hex: uuid.uuid4().hex} parts = self.build_parts(request_args['identity_provider']) # Return HTTP 401 as we don't accept such requests. self.stub_entity('GET', parts=parts, status_code=401) self.assertRaises(exceptions.Unauthorized, self.manager.list, request_args['identity_provider'], **filter_kwargs) self.assertQueryStringContains(**filter_kwargs) def test_update(self): """Test updating federation protocol. URL to be tested: PATCH /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('PATCH', parts=parts, entity=expected, status_code=200) returned = self.manager.update(ref['identity_provider'], ref['id'], mapping=ref['mapping_id']) self.assertIsInstance(returned, self.model) self.assertEqual(expected, returned.to_dict()) request_body = {'mapping_id': ref['mapping_id']} self.assertEntityRequestBodyIs(request_body) class EntityManagerTests(utils.ClientTestCase): def test_create_object_expect_fail(self): self.assertRaises(TypeError, base.EntityManager, self.client) class FederationProjectTests(utils.ClientTestCase): def setUp(self): super(FederationProjectTests, self).setUp() self.key = 'project' self.collection_key = 'projects' self.model = projects.Project self.manager = self.client.federation.projects self.URL = "%s%s" % (self.TEST_URL, '/auth/projects') def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_list_accessible_projects(self): projects_ref = [self.new_ref(), self.new_ref()] projects_json = { self.collection_key: [self.new_ref(), self.new_ref()] } self.requests_mock.get(self.URL, json=projects_json) returned_list = self.manager.list() self.assertEqual(len(projects_ref), len(returned_list)) for project in returned_list: self.assertIsInstance(project, self.model) class K2KFederatedProjectTests(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_ID = 'sp1' SP_ROOT_URL = 'https://example.com/v3' SP_URL = 'https://example.com/Shibboleth.sso/SAML2/ECP' SP_AUTH_URL = (SP_ROOT_URL + '/OS-FEDERATION/identity_providers' '/testidp/protocols/saml2/auth') def setUp(self): super(K2KFederatedProjectTests, 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.collection_key = 'projects' self.model = projects.Project self.URL = '%s%s' % (self.SP_ROOT_URL, '/auth/projects') self.k2kplugin = self.get_plugin() self._mock_k2k_flow_urls() def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs 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): # We need to check the auth versions available self.requests_mock.get( self.TEST_URL, json={'version': fixture.V3Discovery(self.TEST_URL)}, headers={'Content-Type': 'application/json'}) # The identity provider receives a request for an ECP wrapped # assertion. This assertion contains the user authentication info # and will be presented to the service provider 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 service provider is presented with the ECP wrapped assertion # generated by the identity provider and should return a redirect # (302 or 303) upon successful authentication 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}) 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_list_projects(self): k2k_client = client.Client(session=self.session, auth=self.k2kplugin) self.requests_mock.get(self.URL, json={ self.collection_key: [self.new_ref(), self.new_ref()] }) self.requests_mock.get(self.SP_ROOT_URL, json={ 'version': fixture.discovery.V3Discovery(self.SP_ROOT_URL) }) returned_list = k2k_client.federation.projects.list() self.assertThat(returned_list, matchers.HasLength(2)) for project in returned_list: self.assertIsInstance(project, self.model) class FederationDomainTests(utils.ClientTestCase): def setUp(self): super(FederationDomainTests, self).setUp() self.key = 'domain' self.collection_key = 'domains' self.model = domains.Domain self.manager = self.client.federation.domains self.URL = "%s%s" % (self.TEST_URL, '/auth/domains') def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) return kwargs def test_list_accessible_domains(self): domains_ref = [self.new_ref(), self.new_ref()] domains_json = { self.collection_key: domains_ref } self.requests_mock.get(self.URL, json=domains_json) returned_list = self.manager.list() self.assertEqual(len(domains_ref), len(returned_list)) for domain in returned_list: self.assertIsInstance(domain, self.model) class FederatedTokenTests(utils.ClientTestCase): def setUp(self): super(FederatedTokenTests, self).setUp() token = fixture.V3FederationToken() token.set_project_scope() token.add_role() self.federated_token = access.AccessInfo.factory(body=token) def test_federated_property_federated_token(self): """Check if is_federated property returns expected value.""" self.assertTrue(self.federated_token.is_federated) def test_get_user_domain_name(self): """Ensure a federated user's domain name does not exist.""" self.assertIsNone(self.federated_token.user_domain_name) def test_get_user_domain_id(self): """Ensure a federated user's domain ID does not exist.""" self.assertEqual('Federated', self.federated_token.user_domain_id) class ServiceProviderTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ServiceProviderTests, self).setUp() self.key = 'service_provider' self.collection_key = 'service_providers' self.model = service_providers.ServiceProvider self.manager = self.client.federation.service_providers self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('auth_url', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('sp_url', uuid.uuid4().hex) return kwargs def test_positional_parameters_expect_fail(self): """Ensure CrudManager raises TypeError exceptions. After passing wrong number of positional arguments an exception should be raised. Operations to be tested: * create() * get() * list() * delete() * update() """ POS_PARAM_1 = uuid.uuid4().hex POS_PARAM_2 = uuid.uuid4().hex POS_PARAM_3 = uuid.uuid4().hex PARAMETERS = { 'create': (POS_PARAM_1, POS_PARAM_2), 'get': (POS_PARAM_1, POS_PARAM_2), 'list': (POS_PARAM_1, POS_PARAM_2), 'update': (POS_PARAM_1, POS_PARAM_2, POS_PARAM_3), 'delete': (POS_PARAM_1, POS_PARAM_2) } for f_name, args in PARAMETERS.items(): self.assertRaises(TypeError, getattr(self.manager, f_name), *args) def test_create(self): ref = self.new_ref() # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) req_ref = ref.copy() req_ref.pop('id') self.stub_entity('PUT', entity=ref, id=ref['id'], status_code=201) returned = self.manager.create(**ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) class IdentityProviderRequestIdTests(utils.TestRequestId): def setUp(self): super(IdentityProviderRequestIdTests, self).setUp() self.mgr = identity_providers.IdentityProviderManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_identity_provider(self): body = {"identity_provider": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin') def test_list_identity_provider(self): body = {"identity_providers": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/identity_providers?') def test_create_identity_provider(self): body = {"identity_provider": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(id="admin", description='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin', body={'identity_provider': {'description': 'fake'}}) def test_update_identity_provider(self): body = {"identity_provider": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin', body={ 'identity_provider': {}}) def test_delete_identity_provider(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin') class MappingRequestIdTests(utils.TestRequestId): def setUp(self): super(MappingRequestIdTests, self).setUp() self.mgr = mappings.MappingManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_mapping(self): body = {"mapping": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings/admin') def test_list_mapping(self): body = {"mappings": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings?') def test_create_mapping(self): body = {"mapping": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(mapping_id="admin", description='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/mappings/admin', body={ 'mapping': {'description': 'fake'}}) def test_update_mapping(self): body = {"mapping": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/mappings/admin', body={'mapping': {}}) def test_delete_mapping(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings/admin') class ProtocolRequestIdTests(utils.TestRequestId): def setUp(self): super(ProtocolRequestIdTests, self).setUp() self.mgr = protocols.ProtocolManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_protocol(self): body = {"protocol": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin", "protocol") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin/protocols/protocol') def test_list_protocol(self): body = {"protocols": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list("identity_provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/identity_provider/protocols?') def test_create_protocol(self): body = {"protocol": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create( protocol_id="admin", identity_provider='fake', mapping='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/fake/protocols/admin', body={ 'protocol': {'mapping_id': 'fake'}}) def test_update_protocol(self): body = {"protocol": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update(protocol="admin", identity_provider='fake', mapping='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/fake/protocols/admin', body={ 'protocol': {'mapping_id': 'fake'}}) def test_delete_protocol(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("identity_provider", "protocol") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/' 'identity_provider/protocols/protocol') class ServiceProviderRequestIdTests(utils.TestRequestId): def setUp(self): super(ServiceProviderRequestIdTests, self).setUp() self.mgr = service_providers.ServiceProviderManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_service_provider(self): body = {"service_provider": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider') def test_list_service_provider(self): body = {"service_providers": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/service_providers?') def test_create_service_provider(self): body = {"service_provider": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(id='provider') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider', body={ 'service_provider': {}}) def test_update_service_provider(self): body = {"service_provider": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider', body={ 'service_provider': {}}) def test_delete_service_provider(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("provider") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider') python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_endpoints.py0000664000175000017500000001025713644407055027256 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import endpoints class EndpointTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(EndpointTests, self).setUp() self.key = 'endpoint' self.collection_key = 'endpoints' self.model = endpoints.Endpoint self.manager = self.client.endpoints def new_ref(self, **kwargs): kwargs = super(EndpointTests, self).new_ref(**kwargs) kwargs.setdefault('interface', 'public') kwargs.setdefault('region', uuid.uuid4().hex) kwargs.setdefault('service_id', uuid.uuid4().hex) kwargs.setdefault('url', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_create_public_interface(self): ref = self.new_ref(interface='public') self.test_create(ref) def test_create_admin_interface(self): ref = self.new_ref(interface='admin') self.test_create(ref) def test_create_internal_interface(self): ref = self.new_ref(interface='internal') self.test_create(ref) def test_create_invalid_interface(self): ref = self.new_ref(interface=uuid.uuid4().hex) self.assertRaises(exceptions.ValidationError, self.manager.create, **utils.parameterize(ref)) def test_update_public_interface(self): ref = self.new_ref(interface='public') self.test_update(ref) def test_update_admin_interface(self): ref = self.new_ref(interface='admin') self.test_update(ref) def test_update_internal_interface(self): ref = self.new_ref(interface='internal') self.test_update(ref) def test_update_invalid_interface(self): ref = self.new_ref(interface=uuid.uuid4().hex) ref['endpoint'] = "fake_endpoint" self.assertRaises(exceptions.ValidationError, self.manager.update, **utils.parameterize(ref)) def test_list_public_interface(self): interface = 'public' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_admin_interface(self): interface = 'admin' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_internal_interface(self): interface = 'admin' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_invalid_interface(self): interface = uuid.uuid4().hex expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.assertRaises(exceptions.ValidationError, self.manager.list, expected_path=expected_path, interface=interface) def test_list_filtered_by_region(self): region_id = uuid.uuid4().hex ref_list = [self.new_ref(region=region_id), self.new_ref(region=region_id)] expected_path = 'v3/%s?region_id=%s' % (self.collection_key, region_id) expected_query = {'region_id': region_id} # Validate passing either region or region_id result to the API call. self.test_list(ref_list=ref_list, expected_path=expected_path, expected_query=expected_query, region=region_id) self.test_list(ref_list=ref_list, expected_path=expected_path, expected_query=expected_query, region_id=region_id) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_role_assignments.py0000664000175000017500000003065113644407055030627 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import role_assignments class RoleAssignmentsTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RoleAssignmentsTests, self).setUp() self.key = 'role_assignment' self.collection_key = 'role_assignments' self.model = role_assignments.RoleAssignment self.manager = self.client.role_assignments self.TEST_USER_SYSTEM_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'system': { 'all': True } }, 'user': { 'id': self.TEST_USER_ID } }] self.TEST_GROUP_SYSTEM_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'system': { 'all': True } }, 'group': { 'id': self.TEST_GROUP_ID } }] self.TEST_USER_DOMAIN_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'domain': { 'id': self.TEST_DOMAIN_ID } }, 'user': { 'id': self.TEST_USER_ID } }] self.TEST_GROUP_PROJECT_LIST = [{ 'group': { 'id': self.TEST_GROUP_ID }, 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'project': { 'id': self.TEST_TENANT_ID } } }] self.TEST_USER_PROJECT_LIST = [{ 'user': { 'id': self.TEST_USER_ID }, 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'project': { 'id': self.TEST_TENANT_ID } } }] self.TEST_ALL_RESPONSE_LIST = (self.TEST_USER_PROJECT_LIST + self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_DOMAIN_LIST + self.TEST_USER_SYSTEM_LIST + self.TEST_GROUP_SYSTEM_LIST) def _assert_returned_list(self, ref_list, returned_list): self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_by_id(self): # It doesn't make sense to "list role assignments by ID" at all, given # that they don't have globally unique IDs in the first place. But # calling RoleAssignmentsManager.list(id=...) should still raise a # TypeError when given an unexpected keyword argument 'id', so we don't # actually have to modify the test in the superclass... I just wanted # to make a note here in case the superclass changes. super(RoleAssignmentsTests, self).test_list_by_id() def test_list_params(self): ref_list = self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s&user.id=%s' % (self.TEST_TENANT_ID, self.TEST_USER_ID)], entity=ref_list) returned_list = self.manager.list(user=self.TEST_USER_ID, project=self.TEST_TENANT_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID, 'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_all_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list() self._assert_returned_list(ref_list, returned_list) kwargs = {} self.assertQueryStringContains(**kwargs) def test_project_assignments_list(self): ref_list = self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s' % self.TEST_TENANT_ID], entity=ref_list) returned_list = self.manager.list(project=self.TEST_TENANT_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID} self.assertQueryStringContains(**kwargs) def test_project_assignments_list_include_subtree(self): ref_list = self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s&include_subtree=True' % self.TEST_TENANT_ID], entity=ref_list) returned_list = self.manager.list(project=self.TEST_TENANT_ID, include_subtree=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID, 'include_subtree': 'True'} self.assertQueryStringContains(**kwargs) def test_domain_assignments_list(self): ref_list = self.TEST_USER_DOMAIN_LIST self.stub_entity('GET', [self.collection_key, '?scope.domain.id=%s' % self.TEST_DOMAIN_ID], entity=ref_list) returned_list = self.manager.list(domain=self.TEST_DOMAIN_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.domain.id': self.TEST_DOMAIN_ID} self.assertQueryStringContains(**kwargs) def test_system_assignment_list(self): ref_list = self.TEST_USER_SYSTEM_LIST + self.TEST_GROUP_SYSTEM_LIST self.stub_entity('GET', [self.collection_key, '?scope.system=all'], entity=ref_list) returned_list = self.manager.list(system='all') self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all'} self.assertQueryStringContains(**kwargs) def test_system_assignment_list_for_user(self): ref_list = self.TEST_USER_SYSTEM_LIST self.stub_entity('GET', [self.collection_key, '?user.id=%s&scope.system=all' % self.TEST_USER_ID], entity=ref_list) returned_list = self.manager.list(system='all', user=self.TEST_USER_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all', 'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_system_assignment_list_for_group(self): ref_list = self.TEST_GROUP_SYSTEM_LIST self.stub_entity( 'GET', [self.collection_key, '?group.id=%s&scope.system=all' % self.TEST_GROUP_ID], entity=ref_list) returned_list = self.manager.list( system='all', group=self.TEST_GROUP_ID ) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all', 'group.id': self.TEST_GROUP_ID} self.assertQueryStringContains(**kwargs) def test_group_assignments_list(self): ref_list = self.TEST_GROUP_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?group.id=%s' % self.TEST_GROUP_ID], entity=ref_list) returned_list = self.manager.list(group=self.TEST_GROUP_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'group.id': self.TEST_GROUP_ID} self.assertQueryStringContains(**kwargs) def test_user_assignments_list(self): ref_list = self.TEST_USER_DOMAIN_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?user.id=%s' % self.TEST_USER_ID], entity=ref_list) returned_list = self.manager.list(user=self.TEST_USER_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_effective_assignments_list(self): ref_list = self.TEST_USER_PROJECT_LIST + self.TEST_USER_DOMAIN_LIST self.stub_entity('GET', [self.collection_key, '?effective=True'], entity=ref_list) returned_list = self.manager.list(effective=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'effective': 'True'} self.assertQueryStringContains(**kwargs) def test_include_names_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?include_names=True'], entity=ref_list) returned_list = self.manager.list(include_names=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'include_names': 'True'} self.assertQueryStringContains(**kwargs) def test_role_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?role.id=' + self.TEST_ROLE_ID], entity=ref_list) returned_list = self.manager.list(role=self.TEST_ROLE_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'role.id': self.TEST_ROLE_ID} self.assertQueryStringContains(**kwargs) def test_role_assignments_inherited_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?scope.OS-INHERIT:inherited_to=projects'], entity=ref_list ) returned_list = self.manager.list( os_inherit_extension_inherited_to='projects') self._assert_returned_list(ref_list, returned_list) query_string = 'scope.OS-INHERIT:inherited_to=projects' self.assertQueryStringIs(query_string) def test_domain_and_project_list(self): # Should only accept either domain or project, never both self.assertRaises(exceptions.ValidationError, self.manager.list, domain=self.TEST_DOMAIN_ID, project=self.TEST_TENANT_ID) def test_user_and_group_list(self): # Should only accept either user or group, never both self.assertRaises(exceptions.ValidationError, self.manager.list, user=self.TEST_USER_ID, group=self.TEST_GROUP_ID) def test_create(self): # Create not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) def test_update(self): # Update not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_delete(self): # Delete not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.delete) def test_get(self): # Get not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.get) def test_find(self): # Find not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_discover.py0000664000175000017500000000742213644407055027071 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.generic import client from keystoneclient.tests.unit.v3 import utils class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): def setUp(self): super(DiscoverKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "versions": { "values": [{"id": "v3.0", "status": "beta", "updated": "2013-03-06T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v3.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "identity-dev-guide-3.pdf", }, ]}, {"id": "v2.0", "status": "beta", "updated": "2013-03-06T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", } ]}], }, } self.TEST_REQUEST_HEADERS = { 'User-Agent': 'python-keystoneclient', 'Accept': 'application/json', } def test_get_version_local(self): self.requests_mock.get("http://localhost:35357/", status_code=300, json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover() self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v3.0', versions) self.assertEqual( versions['v3.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][1]['links'][0] ['href']) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/examples/0000775000175000017500000000000013644407143025451 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/examples/xml/0000775000175000017500000000000013644407143026251 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenResponse.xmlpython-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenRespo0000664000175000017500000003347213644407055034176 0ustar zuulzuul00000000000000 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 python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_fault.xml0000664000175000017500000000153313644407055030707 0ustar zuulzuul00000000000000 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. python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_ec2.py0000664000175000017500000000724213644407055025724 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import ec2 class EC2Tests(utils.ClientTestCase): def test_create(self): user_id = 'usr' tenant_id = 'tnt' req_body = { "tenant_id": tenant_id, } resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('POST', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) cred = self.client.ec2.create(user_id, tenant_id) self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') self.assertRequestBodyIs(json=req_body) def test_get(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2', 'access'], json=resp_body) cred = self.client.ec2.get(user_id, 'access') self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_list(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credentials": { "values": [ { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, }, { "access": "another", "secret": "key", "tenant_id": tenant_id, "created": "12/12/31", "enabled": True, } ] } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) creds = self.client.ec2.list(user_id) self.assertEqual(len(creds), 2) cred = creds[0] self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_delete(self): user_id = 'usr' access = 'access' self.stub_url('DELETE', ['users', user_id, 'credentials', 'OS-EC2', access], status_code=204) self.client.ec2.delete(user_id, access) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_domain_configs.py0000664000175000017500000000670613644407055030236 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import domain_configs class DomainConfigsTests(utils.ClientTestCase, utils.CrudTests): """Test domain config database management.""" def setUp(self): super(DomainConfigsTests, self).setUp() self.key = 'config' self.model = domain_configs.DomainConfig self.manager = self.client.domain_configs def new_ref(self, **kwargs): config_groups = {'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} kwargs.setdefault('config', config_groups) return kwargs def _assert_resource_attributes(self, resource, req_ref): for attr in req_ref: self.assertEqual( getattr(resource, attr), req_ref[attr], 'Expected different %s' % attr) def test_create(self): domain_id = uuid.uuid4().hex config = self.new_ref() self.stub_url('PUT', parts=['domains', domain_id, 'config'], json=config, status_code=201) res = self.manager.create(domain_id, config) self._assert_resource_attributes(res, config['config']) self.assertEntityRequestBodyIs(config) def test_update(self): domain_id = uuid.uuid4().hex config = self.new_ref() self.stub_url('PATCH', parts=['domains', domain_id, 'config'], json=config, status_code=200) res = self.manager.update(domain_id, config) self._assert_resource_attributes(res, config['config']) self.assertEntityRequestBodyIs(config) def test_get(self): domain_id = uuid.uuid4().hex config = self.new_ref() config = config['config'] self.stub_entity('GET', parts=['domains', domain_id, 'config'], entity=config) res = self.manager.get(domain_id) self._assert_resource_attributes(res, config) def test_delete(self): domain_id = uuid.uuid4().hex self.stub_url('DELETE', parts=['domains', domain_id, 'config'], status_code=204) self.manager.delete(domain_id) def test_list(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_list_by_id(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_list_params(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_find(self): # Find not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_application_credentials.py0000664000175000017500000001206513644407055032132 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 oslo_utils import timeutils from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import application_credentials class ApplicationCredentialTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ApplicationCredentialTests, self).setUp() self.key = 'application_credential' self.collection_key = 'application_credentials' self.model = application_credentials.ApplicationCredential self.manager = self.client.application_credentials self.path_prefix = 'users/%s' % self.TEST_USER_ID def new_ref(self, **kwargs): kwargs = super(ApplicationCredentialTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('unrestricted', False) return kwargs def test_create_with_roles(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['roles'] = [{'name': 'atestrole'}] req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_with_role_id_and_names(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, uuid.uuid4().hex] req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') req_ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, {'id': ref['roles'][1]}] self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_expires(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['expires_at'] = timeutils.parse_isotime( '2013-03-04T12:00:01.000000Z') req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z' self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_unrestricted(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['unrestricted'] = True req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_with_access_rules(self): ref = self.new_ref(user=uuid.uuid4().hex) access_rules = [ { 'method': 'GET', 'path': '/v3/projects', 'service': 'identity' } ] ref['access_rules'] = access_rules req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_get(self): ref = self.new_ref(user=uuid.uuid4().hex) self.stub_entity( 'GET', ['users', ref['user'], self.collection_key, ref['id']], entity=ref) returned = self.manager.get(ref['id'], ref['user']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def test_update(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_roles.py0000664000175000017500000007222113644407055026376 0ustar zuulzuul00000000000000# Copyright 2012 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 uuid from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import roles from testtools import matchers class RoleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RoleTests, self).setUp() self.key = 'role' self.collection_key = 'roles' self.model = roles.Role self.manager = self.client.roles def new_ref(self, **kwargs): kwargs = super(RoleTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def _new_domain_ref(self, **kwargs): kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_create_with_domain_id(self): ref = self.new_ref() ref['domain_id'] = uuid.uuid4().hex self.test_create(ref=ref) def test_create_with_domain(self): ref = self.new_ref() domain_ref = self._new_domain_ref() domain_ref['id'] = uuid.uuid4().hex ref['domain_id'] = domain_ref['id'] self.stub_entity('POST', entity=ref, status_code=201) returned = self.manager.create(name=ref['name'], domain=domain_ref) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def test_domain_role_grant(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, user=user_id) def test_domain_role_grant_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_grant_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.grant(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_grant(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, group=group_id) def test_domain_group_role_grant_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_grant_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.grant(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_domain_role_list(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['domains', domain_id, 'users', user_id, self.collection_key], entity=ref_list) self.manager.list(domain=domain_id, user=user_id) def test_domain_role_list_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(domain=domain_id, user=user_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_project_user_role_list_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(project=project_id, user=user_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_domain_group_role_list(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['domains', domain_id, 'groups', group_id, self.collection_key], entity=ref_list) self.manager.list(domain=domain_id, group=group_id) def test_domain_group_role_list_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(domain=domain_id, group=group_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_project_group_role_list_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(project=project_id, group=group_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_domain_role_check(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, user=user_id) def test_domain_role_check_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_check_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_check(self): return group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, group=group_id) def test_domain_group_role_check_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_check_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_domain_role_revoke(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id) def test_domain_group_role_revoke(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, group=group_id) def test_domain_role_revoke_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_revoke_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_revoke_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=200) self.manager.revoke(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_revoke_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_project_role_grant(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_grant(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], project=project_id, group=group_id) def test_project_role_list(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['projects', project_id, 'users', user_id, self.collection_key], entity=ref_list) self.manager.list(project=project_id, user=user_id) def test_project_group_role_list(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['projects', project_id, 'groups', group_id, self.collection_key], entity=ref_list) self.manager.list(project=project_id, group=group_id) def test_project_role_check(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=200) self.manager.check(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_check(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=200) self.manager.check(role=ref['id'], project=project_id, group=group_id) def test_project_role_revoke(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_revoke(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, group=group_id) def test_domain_project_role_grant_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.grant, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_list_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.manager.list, domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_check_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.check, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_revoke_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.revoke, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_user_group_role_grant_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.grant, role=ref['id'], project=project_id, group=group_id, user=user_id) def test_user_group_role_list_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.manager.list, project=project_id, group=group_id, user=user_id) def test_user_group_role_check_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.check, role=ref['id'], project=project_id, group=group_id, user=user_id) def test_user_group_role_revoke_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.revoke, role=ref['id'], project=project_id, group=group_id, user=user_id) class DeprecatedImpliedRoleTests(utils.ClientTestCase): def setUp(self): super(DeprecatedImpliedRoleTests, self).setUp() self.key = 'role' self.collection_key = 'roles' self.model = roles.Role self.manager = self.client.roles def test_implied_create(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('PUT', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=201) with self.deprecations.expect_deprecations_here(): manager_result = self.manager.create_implied(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) class ImpliedRoleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ImpliedRoleTests, self).setUp() self.key = 'role_inference' self.collection_key = 'role_inferences' self.model = roles.InferenceRule self.manager = self.client.inference_rules def test_check(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('HEAD', ['roles', prior_role_id, 'implies', implied_role_id], status_code=204) result = self.manager.check(prior_role_id, implied_role_id) self.assertTrue(result) def test_get(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('GET', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=200) manager_result = self.manager.get(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_create(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('PUT', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=201) manager_result = self.manager.create(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_delete(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('DELETE', ['roles', prior_role_id, 'implies', implied_role_id], status_code=204) status, body = self.manager.delete(prior_role_id, implied_role_id) self.assertEqual(204, status.status_code) self.assertIsNone(body) def test_list_role_inferences(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inferences": [{ "implies": [{ "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }], "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } }] } self.stub_url('GET', ['role_inferences'], json=mock_response, status_code=200) manager_result = self.manager.list_inference_roles() self.assertEqual(1, len(manager_result)) self.assertIsInstance(manager_result[0], roles.InferenceRule) self.assertEqual(mock_response['role_inferences'][0]['implies'], manager_result[0].implies) self.assertEqual(mock_response['role_inferences'][0]['prior_role'], manager_result[0].prior_role) def test_list(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": [{ "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }], "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } }, "links": {"self": "http://host/v3/roles/%s/implies" % prior_id} } self.stub_url('GET', ['roles', prior_id, 'implies'], json=mock_response, status_code=200) manager_result = self.manager.list(prior_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(1, len(manager_result.implies)) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_update(self): # Update not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_find(self): # Find not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) def test_put(self): # Put not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.put) def test_list_params(self): # Put not supported for rule inferences self.skipTest("list params not supported by rule inferences") python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_tokens.py0000664000175000017500000001466213644407055026562 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import testresources from keystoneclient import access from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v3 import utils class TokenTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_revoke_token_with_token_id(self): token_id = uuid.uuid4().hex self.stub_url('DELETE', ['/auth/tokens'], status_code=204) self.client.tokens.revoke_token(token_id) self.assertRequestHeaderEqual('X-Subject-Token', token_id) def test_revoke_token_with_access_info_instance(self): token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] token = access.AccessInfoV3(token_id, token_ref['token']) self.stub_url('DELETE', ['/auth/tokens'], status_code=204) self.client.tokens.revoke_token(token) self.assertRequestHeaderEqual('X-Subject-Token', token_id) def test_get_revoked(self): sample_revoked_response = {'signed': '-----BEGIN CMS-----\nMIIB...'} self.stub_url('GET', ['auth', 'tokens', 'OS-PKI', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked() self.assertQueryStringIs() self.assertEqual(sample_revoked_response, resp) def test_get_revoked_audit_id_only(self): # When get_revoked(audit_id_only=True) then ?audit_id_only is set on # the request. sample_revoked_response = { 'revoked': [ { 'audit_id': uuid.uuid4().hex, 'expires': '2016-01-21T15:53:52Z', }, ], } self.stub_url('GET', ['auth', 'tokens', 'OS-PKI', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked(audit_id_only=True) self.assertQueryStringIs('audit_id_only') self.assertEqual(sample_revoked_response, resp) def test_validate_token_with_token_id(self): # Can validate a token passing a string token ID. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertEqual(token_data, token_ref) access_info = self.client.tokens.validate(token_id) self.assertRequestHeaderEqual('X-Subject-Token', token_id) self.assertIsInstance(access_info, access.AccessInfoV3) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_with_access_info(self): # Can validate a token passing an access info. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] token = access.AccessInfoV3(token_id, token_ref['token']) self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) access_info = self.client.tokens.validate(token) self.assertRequestHeaderEqual('X-Subject-Token', token_id) self.assertIsInstance(access_info, access.AccessInfoV3) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_invalid(self): # When the token is invalid the server typically returns a 404. token_id = uuid.uuid4().hex self.stub_url('GET', ['auth', 'tokens'], status_code=404) self.assertRaises(exceptions.NotFound, self.client.tokens.get_token_data, token_id) self.assertRaises(exceptions.NotFound, self.client.tokens.validate, token_id) def test_validate_token_catalog(self): # Can validate a token and a catalog is requested by default. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertQueryStringIs() self.assertIn('catalog', token_data['token']) access_info = self.client.tokens.validate(token_id) self.assertQueryStringIs() self.assertTrue(access_info.has_service_catalog()) def test_validate_token_nocatalog(self): # Can validate a token and request no catalog. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_UNSCOPED] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertQueryStringIs() self.assertNotIn('catalog', token_data['token']) access_info = self.client.tokens.validate(token_id, include_catalog=False) self.assertQueryStringIs('nocatalog') self.assertFalse(access_info.has_service_catalog()) def test_validate_token_allow_expired(self): token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_UNSCOPED] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) self.client.tokens.validate(token_id) self.assertQueryStringIs() self.client.tokens.validate(token_id, allow_expired=True) self.assertQueryStringIs('allow_expired=1') def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v3/test_simple_cert.py0000664000175000017500000000554313644407055027563 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import testresources from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib import simple_cert class SimpleCertTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_get_ca_certificate(self): self.stub_url('GET', ['OS-SIMPLE-CERT', 'ca'], headers={'Content-Type': 'application/x-pem-file'}, text=self.examples.SIGNING_CA) res = self.client.simple_cert.get_ca_certificates() self.assertEqual(self.examples.SIGNING_CA, res) def test_get_certificates(self): self.stub_url('GET', ['OS-SIMPLE-CERT', 'certificates'], headers={'Content-Type': 'application/x-pem-file'}, text=self.examples.SIGNING_CERT) res = self.client.simple_cert.get_certificates() self.assertEqual(self.examples.SIGNING_CERT, res) class SimpleCertRequestIdTests(utils.TestRequestId): def setUp(self): super(SimpleCertRequestIdTests, self).setUp() self.mgr = simple_cert.SimpleCertManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_list_ca_certificates(self): body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get_ca_certificates() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( '/OS-SIMPLE-CERT/ca', authenticated=False) def test_list_certificates(self): body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get_certificates() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( '/OS-SIMPLE-CERT/certificates', authenticated=False) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/client_fixtures.py0000664000175000017500000007515313644407055027101 0ustar zuulzuul00000000000000# 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 contextlib import os import uuid import warnings import fixtures from keystoneauth1 import fixture from keystoneauth1 import identity as ksa_identity from keystoneauth1 import session as ksa_session from oslo_serialization import jsonutils from oslo_utils import timeutils import six import testresources from keystoneclient.auth import identity as ksc_identity from keystoneclient.common import cms from keystoneclient import session as ksc_session from keystoneclient import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client TEST_ROOT_URL = 'http://127.0.0.1:5000/' TESTDIR = os.path.dirname(os.path.abspath(__file__)) ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..', '..')) CERTDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'certs') CMSDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'cms') KEYDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'private') class BaseFixture(fixtures.Fixture): TEST_ROOT_URL = TEST_ROOT_URL def __init__(self, requests, deprecations): super(BaseFixture, self).__init__() self.requests = requests self.deprecations = deprecations self.user_id = uuid.uuid4().hex self.client = self.new_client() class BaseV2(BaseFixture): TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v2.0') class OriginalV2(BaseV2): def new_client(self): # Creating a Client not using session is deprecated. with self.deprecations.expect_deprecations_here(): return v2_client.Client(username=uuid.uuid4().hex, user_id=self.user_id, token=uuid.uuid4().hex, tenant_name=uuid.uuid4().hex, auth_url=self.TEST_URL, endpoint=self.TEST_URL) class KscSessionV2(BaseV2): def new_client(self): t = fixture.V2Token(user_id=self.user_id) t.set_scope() s = t.add_service('identity') s.add_endpoint(self.TEST_URL) d = fixture.V2Discovery(self.TEST_URL) self.requests.register_uri('POST', self.TEST_URL + '/tokens', json=t) # NOTE(jamielennox): Because of the versioned URL hack here even though # the V2 URL will be in the service catalog it will be the root URL # that will be queried for discovery. self.requests.register_uri('GET', self.TEST_ROOT_URL, json={'version': d}) with self.deprecations.expect_deprecations_here(): a = ksc_identity.V2Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksc_session.Session(auth=a) return v2_client.Client(session=s) class KsaSessionV2(BaseV2): def new_client(self): t = fixture.V2Token(user_id=self.user_id) t.set_scope() s = t.add_service('identity') s.add_endpoint(self.TEST_URL) d = fixture.V2Discovery(self.TEST_URL) self.requests.register_uri('POST', self.TEST_URL + '/tokens', json=t) # NOTE(jamielennox): Because of the versioned URL hack here even though # the V2 URL will be in the service catalog it will be the root URL # that will be queried for discovery. self.requests.register_uri('GET', self.TEST_ROOT_URL, json={'version': d}) a = ksa_identity.V2Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksa_session.Session(auth=a) return v2_client.Client(session=s) class BaseV3(BaseFixture): TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') class OriginalV3(BaseV3): def new_client(self): # Creating a Client not using session is deprecated. with self.deprecations.expect_deprecations_here(): return v3_client.Client(username=uuid.uuid4().hex, user_id=self.user_id, token=uuid.uuid4().hex, tenant_name=uuid.uuid4().hex, auth_url=self.TEST_URL, endpoint=self.TEST_URL) class KscSessionV3(BaseV3): def new_client(self): t = fixture.V3Token(user_id=self.user_id) t.set_project_scope() s = t.add_service('identity') s.add_standard_endpoints(public=self.TEST_URL, admin=self.TEST_URL) d = fixture.V3Discovery(self.TEST_URL) headers = {'X-Subject-Token': uuid.uuid4().hex} self.requests.register_uri('POST', self.TEST_URL + '/auth/tokens', headers=headers, json=t) self.requests.register_uri('GET', self.TEST_URL, json={'version': d}) with self.deprecations.expect_deprecations_here(): a = ksc_identity.V3Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, user_domain_id=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksc_session.Session(auth=a) return v3_client.Client(session=s) class KsaSessionV3(BaseV3): def new_client(self): t = fixture.V3Token(user_id=self.user_id) t.set_project_scope() s = t.add_service('identity') s.add_standard_endpoints(public=self.TEST_URL, admin=self.TEST_URL) d = fixture.V3Discovery(self.TEST_URL) headers = {'X-Subject-Token': uuid.uuid4().hex} self.requests.register_uri('POST', self.TEST_URL + '/auth/tokens', headers=headers, json=t) self.requests.register_uri('GET', self.TEST_URL, json={'version': d}) a = ksa_identity.V3Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, user_domain_id=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksa_session.Session(auth=a) return v3_client.Client(session=s) def _hash_signed_token_safe(signed_text, **kwargs): if isinstance(signed_text, six.text_type): signed_text = signed_text.encode('utf-8') return utils.hash_signed_token(signed_text, **kwargs) class Examples(fixtures.Fixture): """Example tokens and certs loaded from the examples directory. To use this class correctly, the module needs to override the test suite class to use testresources.OptimisingTestSuite (otherwise the files will be read on every test). This is done by defining a load_tests function in the module, like this: def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) (see http://docs.python.org/2/library/unittest.html#load-tests-protocol ) """ def setUp(self): super(Examples, self).setUp() # The data for several tests are signed using openssl and are stored in # files in the signing subdirectory. In order to keep the values # consistent between the tests and the signed documents, we read them # in for use in the tests. with open(os.path.join(CMSDIR, 'auth_token_scoped.json')) as f: self.TOKEN_SCOPED_DATA = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pem')) as f: self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_unscoped.pem')) as f: self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pem')) as f: self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_v3_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_revoked.pem')) as f: self.REVOKED_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pem')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pem')) as f: self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f: self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f: self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_revoked.pkiz')) as f: self.REVOKED_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pkiz')) as f: self.REVOKED_v3_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.json')) as f: self.REVOCATION_LIST = jsonutils.loads(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f: self.SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()}) self.SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem') with open(self.SIGNING_CERT_FILE) as f: self.SIGNING_CERT = f.read() self.KERBEROS_BIND = 'USER@REALM' self.SIGNING_KEY_FILE = os.path.join(KEYDIR, 'signing_key.pem') with open(self.SIGNING_KEY_FILE) as f: self.SIGNING_KEY = f.read() self.SIGNING_CA_FILE = os.path.join(CERTDIR, 'cacert.pem') with open(self.SIGNING_CA_FILE) as f: self.SIGNING_CA = f.read() self.UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d" self.UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df' self.UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776' self.UUID_TOKEN_BIND = '3fc54048ad64405c98225ce0897af7c5' self.UUID_TOKEN_UNKNOWN_BIND = '8885fdf4d42e4fb9879e6379fa1eaf48' self.VALID_DIABLO_TOKEN = 'b0cf19b55dbb4f20a6ee18e6c6cf1726' self.v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1' self.v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79' self.v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792' self.v3_UUID_TOKEN_BIND = '2f61f73e1c854cbb9534c487f9bd63c2' self.v3_UUID_TOKEN_UNKNOWN_BIND = '7ed9781b62cd4880b8d8c6788ab1d1e2' revoked_token = self.REVOKED_TOKEN if isinstance(revoked_token, six.text_type): revoked_token = revoked_token.encode('utf-8') self.REVOKED_TOKEN_HASH = utils.hash_signed_token(revoked_token) self.REVOKED_TOKEN_HASH_SHA256 = utils.hash_signed_token(revoked_token, mode='sha256') self.REVOKED_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_TOKEN_LIST_JSON = jsonutils.dumps(self.REVOKED_TOKEN_LIST) revoked_v3_token = self.REVOKED_v3_TOKEN if isinstance(revoked_v3_token, six.text_type): revoked_v3_token = revoked_v3_token.encode('utf-8') self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(revoked_v3_token) hash = utils.hash_signed_token(revoked_v3_token, mode='sha256') self.REVOKED_v3_TOKEN_HASH_SHA256 = hash self.REVOKED_v3_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_v3_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_v3_TOKEN_LIST_JSON = jsonutils.dumps( self.REVOKED_v3_TOKEN_LIST) revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ if isinstance(revoked_token_pkiz, six.text_type): revoked_token_pkiz = revoked_token_pkiz.encode('utf-8') self.REVOKED_TOKEN_PKIZ_HASH = utils.hash_signed_token( revoked_token_pkiz) revoked_v3_token_pkiz = self.REVOKED_v3_TOKEN_PKIZ if isinstance(revoked_v3_token_pkiz, six.text_type): revoked_v3_token_pkiz = revoked_v3_token_pkiz.encode('utf-8') self.REVOKED_v3_PKIZ_TOKEN_HASH = utils.hash_signed_token( revoked_v3_token_pkiz) self.REVOKED_TOKEN_PKIZ_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_PKIZ_HASH, 'expires': timeutils.utcnow()}, {'id': self.REVOKED_v3_PKIZ_TOKEN_HASH, 'expires': timeutils.utcnow()}, ]}) self.REVOKED_TOKEN_PKIZ_LIST_JSON = jsonutils.dumps( self.REVOKED_TOKEN_PKIZ_LIST) self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED) self.SIGNED_v3_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED_PKIZ) self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED_PKIZ) self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED_PKIZ) self.INVALID_SIGNED_TOKEN = ( "MIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") self.INVALID_SIGNED_PKIZ_TOKEN = ( "PKIZ_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") # JSON responses keyed by token ID self.TOKEN_RESPONSES = { self.UUID_TOKEN_DEFAULT: { 'access': { 'token': { 'id': self.UUID_TOKEN_DEFAULT, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.VALID_DIABLO_TOKEN: { 'access': { 'token': { 'id': self.VALID_DIABLO_TOKEN, 'expires': '2999-01-01T00:00:10.000123Z', 'tenantId': 'tenant_id1', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.UUID_TOKEN_UNSCOPED: { 'access': { 'token': { 'id': self.UUID_TOKEN_UNSCOPED, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.UUID_TOKEN_NO_SERVICE_CATALOG: { 'access': { 'token': { 'id': 'valid-token', 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], } }, }, self.UUID_TOKEN_BIND: { 'access': { 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'id': self.UUID_TOKEN_BIND, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.UUID_TOKEN_UNKNOWN_BIND: { 'access': { 'token': { 'bind': {'FOO': 'BAR'}, 'id': self.UUID_TOKEN_UNKNOWN_BIND, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.v3_UUID_TOKEN_DEFAULT: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.v3_UUID_TOKEN_UNSCOPED: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } } } }, self.v3_UUID_TOKEN_DOMAIN_SCOPED: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'domain': { 'id': 'domain_id1', 'name': 'domain_name1', }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.SIGNED_TOKEN_SCOPED_KEY: { 'access': { 'token': { 'id': self.SIGNED_TOKEN_SCOPED_KEY, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'tenantId': 'tenant_id1', 'tenantName': 'tenant_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.SIGNED_TOKEN_UNSCOPED_KEY: { 'access': { 'token': { 'id': self.SIGNED_TOKEN_UNSCOPED_KEY, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.SIGNED_v3_TOKEN_SCOPED_KEY: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1'}, {'name': 'role2'} ], 'catalog': {} } }, self.v3_UUID_TOKEN_BIND: { 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'methods': ['password'], 'expires_at': '2999-01-01T00:00:10.000123Z', 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.v3_UUID_TOKEN_UNKNOWN_BIND: { 'token': { 'bind': {'FOO': 'BAR'}, 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, } self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY]) self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in self.TOKEN_RESPONSES.items()]) EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) class Deprecations(fixtures.Fixture): def setUp(self): super(Deprecations, self).setUp() # If keystoneclient calls any deprecated function this will raise an # exception. warnings.filterwarnings('error', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') self.addCleanup(warnings.resetwarnings) def expect_deprecations(self): """Call this if the test expects to call deprecated function.""" warnings.resetwarnings() warnings.filterwarnings('ignore', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') @contextlib.contextmanager def expect_deprecations_here(self): warnings.resetwarnings() warnings.filterwarnings('ignore', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') yield warnings.resetwarnings() warnings.filterwarnings('error', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_cms.py0000664000175000017500000002040713644407055025503 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 errno import os import subprocess import mock import testresources from testtools import matchers from keystoneclient.common import cms from keystoneclient import exceptions from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils class CMSTest(utils.TestCase, testresources.ResourcedTestCase): """Unit tests for the keystoneclient.common.cms module.""" resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def __init__(self, *args, **kwargs): super(CMSTest, self).__init__(*args, **kwargs) process = subprocess.Popen(['openssl', 'version'], stdout=subprocess.PIPE) out, err = process.communicate() # Example output: 'OpenSSL 0.9.8za 5 Jun 2014' openssl_version = out.split()[1] if err or openssl_version.startswith(b'0'): raise Exception('Your version of OpenSSL is not supported. ' 'You will need to update it to 1.0 or later.') def _raise_OSError(*args): e = OSError() e.errno = errno.EPIPE raise e def test_cms_verify(self): self.assertRaises(exceptions.CertificateConfigError, cms.cms_verify, 'data', 'no_exist_cert_file', 'no_exist_ca_file') def test_token_tocms_to_token(self): with open(os.path.join(client_fixtures.CMSDIR, 'auth_token_scoped.pem')) as f: AUTH_TOKEN_SCOPED_CMS = f.read() self.assertEqual(cms.token_to_cms(self.examples.SIGNED_TOKEN_SCOPED), AUTH_TOKEN_SCOPED_CMS) tok = cms.cms_to_token(cms.token_to_cms( self.examples.SIGNED_TOKEN_SCOPED)) self.assertEqual(tok, self.examples.SIGNED_TOKEN_SCOPED) def test_asn1_token(self): self.assertTrue(cms.is_asn1_token(self.examples.SIGNED_TOKEN_SCOPED)) self.assertFalse(cms.is_asn1_token('FOOBAR')) def test_cms_sign_token_no_files(self): self.assertRaises(subprocess.CalledProcessError, cms.cms_sign_token, self.examples.TOKEN_SCOPED_DATA, '/no/such/file', '/no/such/key') def test_cms_sign_token_no_files_pkiz(self): self.assertRaises(subprocess.CalledProcessError, cms.pkiz_sign, self.examples.TOKEN_SCOPED_DATA, '/no/such/file', '/no/such/key') def test_cms_sign_token_success(self): self.assertTrue( cms.pkiz_sign(self.examples.TOKEN_SCOPED_DATA, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_KEY_FILE)) def test_cms_verify_token_no_files(self): self.assertRaises(exceptions.CertificateConfigError, cms.cms_verify, self.examples.SIGNED_TOKEN_SCOPED, '/no/such/file', '/no/such/key') def test_cms_verify_token_no_oserror(self): with mock.patch('subprocess.Popen.communicate', new=self._raise_OSError): try: cms.cms_verify("x", '/no/such/file', '/no/such/key') except exceptions.CertificateConfigError as e: self.assertIn('/no/such/file', e.output) self.assertIn('Hit OSError ', e.output) else: self.fail('Expected exceptions.CertificateConfigError') def test_cms_verify_token_scoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_TOKEN_SCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_scoped_expired(self): cms_content = cms.token_to_cms( self.examples.SIGNED_TOKEN_SCOPED_EXPIRED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_unscoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_TOKEN_UNSCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_v3_scoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_v3_TOKEN_SCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_hash_token_no_token_id(self): token_id = None self.assertThat(cms.cms_hash_token(token_id), matchers.Is(None)) def test_cms_hash_token_not_pki(self): """If the token_id is not a PKI token then it returns the token_id.""" token = 'something' self.assertFalse(cms.is_asn1_token(token)) self.assertThat(cms.cms_hash_token(token), matchers.Is(token)) def test_cms_hash_token_default_md5(self): """The default hash method is md5.""" token = self.examples.SIGNED_TOKEN_SCOPED token_id_default = cms.cms_hash_token(token) token_id_md5 = cms.cms_hash_token(token, mode='md5') self.assertThat(token_id_default, matchers.Equals(token_id_md5)) # md5 hash is 32 chars. self.assertThat(token_id_default, matchers.HasLength(32)) def test_cms_hash_token_sha256(self): """Can also hash with sha256.""" token = self.examples.SIGNED_TOKEN_SCOPED token_id = cms.cms_hash_token(token, mode='sha256') # sha256 hash is 64 chars. self.assertThat(token_id, matchers.HasLength(64)) @mock.patch('keystoneclient.common.cms._check_files_accessible') def test_process_communicate_handle_oserror_epipe(self, files_acc_mock): process_mock = mock.Mock() process_mock.communicate = self._raise_OSError process_mock.stderr = mock.Mock() process_mock.stderr.read = mock.Mock(return_value='proc stderr') files_acc_mock.return_value = 1, ('file_path', 'fileerror') output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual((output, retcode), ('', 1)) self.assertIn('file_path', err) self.assertIn('fileerror', err) self.assertIn('proc stderr', err) @mock.patch('keystoneclient.common.cms._check_files_accessible') def test_process_communicate_handle_oserror_epipe_files_ok( self, files_acc_mock): process_mock = mock.Mock() process_mock.communicate = self._raise_OSError process_mock.stderr = mock.Mock() process_mock.stderr.read = mock.Mock(return_value='proc stderr') files_acc_mock.return_value = -1, None output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual((output, retcode), ('', -1)) self.assertIn('proc stderr', err) def test_process_communicate_handle_oserror_no_exception(self): process_mock = mock.Mock() process_mock.communicate.return_value = 'out', 'err' process_mock.poll.return_value = 0 output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual(output, 'out') self.assertEqual(err, 'err') self.assertEqual(retcode, 0) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/__init__.py0000664000175000017500000000000013644407055025404 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_fixtures.py0000664000175000017500000002321313644407055026570 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import fixture from keystoneclient.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']) 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']) 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']) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_base.py0000664000175000017500000003723513644407055025642 0ustar zuulzuul00000000000000# -*- 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. import uuid import fixtures from keystoneauth1.identity import v2 from keystoneauth1 import session import requests from keystoneclient import base from keystoneclient import exceptions from keystoneclient.tests.unit import utils from keystoneclient import utils as base_utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import roles from keystoneclient.v3 import users TEST_REQUEST_ID = uuid.uuid4().hex TEST_REQUEST_ID_1 = uuid.uuid4().hex def create_response_with_request_id_header(): resp = requests.Response() resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID return resp class HumanReadable(base.Resource): HUMAN_ID = True class BaseTest(utils.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual(repr(r), "") def test_getid(self): self.assertEqual(base.getid(4), 4) class TmpObject(object): id = 4 self.assertEqual(base.getid(TmpObject), 4) def test_resource_lazy_getattr(self): auth = v2.Token(token=self.TEST_TOKEN, auth_url='http://127.0.0.1:5000') session_ = session.Session(auth=auth) self.client = client.Client(session=session_) self.useFixture(fixtures.MockPatchObject( self.client._adapter, 'get', side_effect=AttributeError, autospec=True)) f = roles.Role(self.client.roles, {'id': 1, 'name': 'Member'}) self.assertEqual(f.name, 'Member') # Missing stuff still fails after a second get self.assertRaises(AttributeError, getattr, f, 'blahblah') def test_eq(self): # Two resources with same ID: never equal if their info is not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertNotEqual(r1, r2) self.assertTrue(r1 != r2) # Two resources with same ID: equal if their info is equal # The truth of r1==r2 does not imply that r1!=r2 is false in PY2. # Test that inequality operator is defined and that comparing equal # items returns False r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertTrue(r1 == r2) self.assertFalse(r1 != r2) # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = roles.Role(None, {'id': 1}) self.assertNotEqual(r1, r2) self.assertTrue(r1 != r2) # Two resources with no ID: equal if their info is equal # The truth of r1==r2 does not imply that r1!=r2 is false in PY2. # Test that inequality operator is defined and that comparing equal # items returns False. r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertTrue(r1 == r2) self.assertFalse(r1 != r2) r1 = base.Resource(None, {'id': 1}) self.assertNotEqual(r1, object()) self.assertTrue(r1 != object()) self.assertNotEqual(r1, {'id': 1}) self.assertTrue(r1 != {'id': 1}) def test_human_id(self): r = base.Resource(None, {"name": "1 of !"}) self.assertIsNone(r.human_id) r = HumanReadable(None, {"name": "1 of !"}) self.assertEqual(r.human_id, "1-of") def test_non_ascii_attr(self): r_dict = {"name": "foobar", u"тест": "1234", u"тест2": u"привет мир"} r = base.Resource(None, r_dict) self.assertEqual(r.name, "foobar") self.assertEqual(r.to_dict(), r_dict) class ManagerTest(utils.TestCase): body = {"hello": {"hi": 1}} url = "/test-url" def setUp(self): super(ManagerTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_)._adapter self.mgr = base.Manager(self.client) self.mgr.resource_class = base.Resource def test_api(self): with self.deprecations.expect_deprecations_here(): self.assertEqual(self.mgr.api, self.client) def test_get(self): get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._get(self.url, "hello") get_mock.assert_called_once_with(self.url) self.assertEqual(rsrc.hi, 1) def test_post(self): post_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'post', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._post(self.url, self.body, "hello") post_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) post_mock.reset_mock() rsrc = self.mgr._post(self.url, self.body, "hello", return_raw=True) post_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc["hi"], 1) def test_put(self): put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._put(self.url, self.body, "hello") put_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) put_mock.reset_mock() rsrc = self.mgr._put(self.url, self.body) put_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hello["hi"], 1) def test_patch(self): patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._patch(self.url, self.body, "hello") patch_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) patch_mock.reset_mock() rsrc = self.mgr._patch(self.url, self.body) patch_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hello["hi"], 1) def test_update(self): patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._update( self.url, body=self.body, response_key="hello", method="PATCH", management=False) patch_mock.assert_called_once_with( self.url, management=False, body=self.body) self.assertEqual(rsrc.hi, 1) rsrc = self.mgr._update( self.url, body=None, response_key="hello", method="PUT", management=True) put_mock.assert_called_once_with(self.url, management=True, body=None) self.assertEqual(rsrc.hi, 1) class ManagerRequestIdTest(utils.TestCase): url = "/test-url" resp = create_response_with_request_id_header() def setUp(self): super(ManagerRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter self.mgr = base.Manager(self.client) self.mgr.resource_class = base.Resource def mock_request_method(self, request_method, body): return self.useFixture(fixtures.MockPatchObject( self.client, request_method, autospec=True, return_value=(self.resp, body)) ).mock def test_get(self): body = {"hello": {"hi": 1}} get_mock = self.mock_request_method('get', body) rsrc = self.mgr._get(self.url, "hello") get_mock.assert_called_once_with(self.url) self.assertEqual(rsrc.data.hi, 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_list(self): body = {"hello": [{"name": "admin"}, {"name": "admin"}]} get_mock = self.mock_request_method('get', body) returned_list = self.mgr._list(self.url, "hello") self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url) def test_list_with_multiple_response_objects(self): body = {"hello": [{"name": "admin"}, {"name": "admin"}]} resp_1 = requests.Response() resp_1.headers['x-openstack-request-id'] = TEST_REQUEST_ID resp_2 = requests.Response() resp_2.headers['x-openstack-request-id'] = TEST_REQUEST_ID_1 resp_result = [resp_1, resp_2] get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(resp_result, body)) ).mock returned_list = self.mgr._list(self.url, "hello") self.assertIn(returned_list.request_ids[0], [ TEST_REQUEST_ID, TEST_REQUEST_ID_1]) self.assertIn(returned_list.request_ids[1], [ TEST_REQUEST_ID, TEST_REQUEST_ID_1]) get_mock.assert_called_once_with(self.url) def test_post(self): body = {"hello": {"hi": 1}} post_mock = self.mock_request_method('post', body) rsrc = self.mgr._post(self.url, body, "hello") post_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) post_mock.reset_mock() rsrc = self.mgr._post(self.url, body, "hello", return_raw=True) post_mock.assert_called_once_with(self.url, body=body) self.assertNotIsInstance(rsrc, base.Response) self.assertEqual(rsrc["hi"], 1) def test_put(self): body = {"hello": {"hi": 1}} put_mock = self.mock_request_method('put', body) rsrc = self.mgr._put(self.url, body, "hello") put_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) put_mock.reset_mock() rsrc = self.mgr._put(self.url, body) put_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hello["hi"], 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_head(self): get_mock = self.mock_request_method('head', None) rsrc = self.mgr._head(self.url) get_mock.assert_called_once_with(self.url) self.assertFalse(rsrc.data) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_delete(self): delete_mock = self.mock_request_method('delete', None) resp, base_resp = self.mgr._delete(self.url, name="hello") delete_mock.assert_called_once_with('/test-url', name='hello') self.assertEqual(base_resp.request_ids[0], TEST_REQUEST_ID) self.assertEqual(base_resp.data, None) self.assertTrue(isinstance(resp, requests.Response)) def test_patch(self): body = {"hello": {"hi": 1}} patch_mock = self.mock_request_method('patch', body) rsrc = self.mgr._patch(self.url, body, "hello") patch_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) patch_mock.reset_mock() rsrc = self.mgr._patch(self.url, body) patch_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hello["hi"], 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_update(self): body = {"hello": {"hi": 1}} patch_mock = self.mock_request_method('patch', body) put_mock = self.mock_request_method('put', body) rsrc = self.mgr._update( self.url, body=body, response_key="hello", method="PATCH", management=False) patch_mock.assert_called_once_with( self.url, management=False, body=body) self.assertEqual(rsrc.data.hi, 1) rsrc = self.mgr._update( self.url, body=None, response_key="hello", method="PUT", management=True) put_mock.assert_called_once_with(self.url, management=True, body=None) self.assertEqual(rsrc.data.hi, 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) class ManagerWithFindRequestIdTest(utils.TestCase): url = "/fakes" resp = create_response_with_request_id_header() def setUp(self): super(ManagerWithFindRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter def test_find_resource(self): body = {"roles": [{"name": 'entity_one'}, {"name": 'entity_one_1'}]} request_resp = requests.Response() request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, side_effect=[exceptions.NotFound, (request_resp, body)]) ).mock mgr = roles.RoleManager(self.client) mgr.resource_class = roles.Role response = base_utils.find_resource(mgr, 'entity_one') get_mock.assert_called_with('/OS-KSADM/roles') self.assertEqual(response.request_ids[0], TEST_REQUEST_ID) class CrudManagerRequestIdTest(utils.TestCase): resp = create_response_with_request_id_header() request_resp = requests.Response() request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID def setUp(self): super(CrudManagerRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter def test_find_resource(self): body = {"users": [{"name": 'entity_one'}]} get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, side_effect=[exceptions.NotFound, (self.request_resp, body)]) ).mock mgr = users.UserManager(self.client) mgr.resource_class = users.User response = base_utils.find_resource(mgr, 'entity_one') get_mock.assert_called_with('/users?name=entity_one') self.assertEqual(response.request_ids[0], TEST_REQUEST_ID) def test_list(self): body = {"users": [{"name": "admin"}, {"name": "admin"}]} get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(self.request_resp, body)) ).mock mgr = users.UserManager(self.client) mgr.resource_class = users.User returned_list = mgr.list() self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID) get_mock.assert_called_once_with('/users?') python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_session.py0000664000175000017500000012210513644407055026402 0ustar zuulzuul00000000000000# -*- 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. import argparse import itertools import logging import uuid import mock from oslo_config import cfg from oslo_config import fixture as config from oslo_serialization import jsonutils import requests import six from testtools import matchers from keystoneclient import adapter from keystoneclient.auth import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session from keystoneclient.tests.unit import utils class SessionTests(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/' def setUp(self): super(SessionTests, self).setUp() self.deprecations.expect_deprecations() 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_user_agent(self): session = client_session.Session(user_agent='test-agent') self.stub_url('GET', text='response') resp = session.get(self.TEST_URL) self.assertTrue(resp.ok) self.assertRequestHeaderEqual('User-Agent', 'test-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') def test_http_session_opts(self): session = client_session.Session(cert='cert.pem', timeout=5, verify='certs') FAKE_RESP = utils.test_response(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_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 = jsonutils.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 = jsonutils.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 = jsonutils.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_unicode_data_in_debug_output(self): """Verify that ascii-encodable data is logged without modification.""" session = client_session.Session(verify=False) body = 'RESP' data = u'αβγδ' self.stub_url('POST', text=body) session.post(self.TEST_URL, data=data) self.assertIn("'%s'" % data, self.logger.output) def test_binary_data_not_in_debug_output(self): """Verify that non-ascii-encodable data causes replacement.""" if six.PY2: data = "my data" + chr(255) else: # Python 3 logging handles binary data well. return session = client_session.Session(verify=False) body = 'RESP' self.stub_url('POST', text=body) # Forced mixed unicode and byte strings in request # elements to make sure that all joins are appropriately # handled (any join of unicode and byte strings should # raise a UnicodeDecodeError) session.post(six.text_type(self.TEST_URL), data=data) self.assertNotIn('my data', 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): def _timeout_error(request, context): raise requests.exceptions.Timeout() self.stub_url('GET', text=_timeout_error) session = client_session.Session() retries = 3 with mock.patch('time.sleep') as m: self.assertRaises(exceptions.RequestTimeout, 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 def _ssl_error(request, context): raise requests.exceptions.SSLError(error) self.stub_url('GET', text=_ssl_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} six.assertRaisesRegex(self, exceptions.SSLError, msg, session.get, self.TEST_URL) def test_mask_password_in_http_log_response(self): session = client_session.Session() def fake_debug(msg): self.assertNotIn('verybadpass', msg) logger = mock.Mock(isEnabledFor=mock.Mock(return_value=True)) logger.debug = mock.Mock(side_effect=fake_debug) body = { "connection_info": { "driver_volume_type": "iscsi", "data": { "auth_password": "verybadpass", "target_discovered": False, "encrypted": False, "qos_specs": None, "target_iqn": ("iqn.2010-10.org.openstack:volume-" "744d2085-8e78-40a5-8659-ef3cffb2480e"), "target_portal": "172.99.69.228:3260", "volume_id": "744d2085-8e78-40a5-8659-ef3cffb2480e", "target_lun": 1, "access_mode": "rw", "auth_username": "verybadusername", "auth_method": "CHAP"}}} body_json = jsonutils.dumps(body) response = mock.Mock(text=body_json, status_code=200, headers={'content-type': 'application/json'}) session._http_log_response(response, logger) self.assertEqual(1, logger.debug.call_count) class TCPKeepAliveAdapter(utils.TestCase): @mock.patch.object(client_session, 'socket') @mock.patch('requests.adapters.HTTPAdapter.init_poolmanager') def test_init_poolmanager_all_options(self, mock_parent_init_poolmanager, mock_socket): # properties expected to be in socket. mock_socket.TCP_KEEPIDLE = mock.sentinel.TCP_KEEPIDLE mock_socket.TCP_KEEPCNT = mock.sentinel.TCP_KEEPCNT mock_socket.TCP_KEEPINTVL = mock.sentinel.TCP_KEEPINTVL desired_opts = [mock_socket.TCP_KEEPIDLE, mock_socket.TCP_KEEPCNT, mock_socket.TCP_KEEPINTVL] adapter = client_session.TCPKeepAliveAdapter() adapter.init_poolmanager() call_args, call_kwargs = mock_parent_init_poolmanager.call_args called_socket_opts = call_kwargs['socket_options'] call_options = [opt for (protocol, opt, value) in called_socket_opts] for opt in desired_opts: self.assertIn(opt, call_options) @mock.patch.object(client_session, 'socket') @mock.patch('requests.adapters.HTTPAdapter.init_poolmanager') def test_init_poolmanager(self, mock_parent_init_poolmanager, mock_socket): spec = ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE'] mock_socket.mock_add_spec(spec) adapter = client_session.TCPKeepAliveAdapter() adapter.init_poolmanager() call_args, call_kwargs = mock_parent_init_poolmanager.call_args called_socket_opts = call_kwargs['socket_options'] call_options = [opt for (protocol, opt, value) in called_socket_opts] self.assertEqual([mock_socket.TCP_NODELAY, mock_socket.SO_KEEPALIVE], call_options) 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(self): super(RedirectTests, self).setUp() self.deprecations.expect_deprecations() def setup_redirects(self, method='GET', status_code=305, redirect_kwargs=None, final_kwargs=None): redirect_kwargs = redirect_kwargs or {} final_kwargs = final_kwargs or {} 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) class ConstructSessionFromArgsTests(utils.TestCase): KEY = 'keyfile' CERT = 'certfile' CACERT = 'cacert-path' def _s(self, k=None, **kwargs): k = k or kwargs with self.deprecations.expect_deprecations_here(): return client_session.Session.construct(k) def test_verify(self): self.assertFalse(self._s(insecure=True).verify) self.assertTrue(self._s(verify=True, insecure=True).verify) self.assertFalse(self._s(verify=False, insecure=True).verify) self.assertEqual(self._s(cacert=self.CACERT).verify, self.CACERT) def test_cert(self): tup = (self.CERT, self.KEY) self.assertEqual(self._s(cert=tup).cert, tup) self.assertEqual(self._s(cert=self.CERT, key=self.KEY).cert, tup) self.assertIsNone(self._s(key=self.KEY).cert) def test_pass_through(self): value = 42 # only a number because timeout needs to be for key in ['timeout', 'session', 'original_ip', 'user_agent']: args = {key: value} self.assertEqual(getattr(self._s(args), key), value) self.assertNotIn(key, args) class AuthPlugin(base.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(base.BaseAuthPlugin): ENDPOINT = 'http://fakeendpoint/' def __init__(self, invalidate=True): self.get_token_called = False self.get_endpoint_called = False self.endpoint_arguments = {} self.invalidate_called = False self._invalidate = invalidate def get_token(self, session): self.get_token_called = True return utils.TestCase.TEST_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 class SessionAuthTests(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/' TEST_JSON = {'hello': 'world'} def setUp(self): super(SessionAuthTests, self).setUp() self.deprecations.expect_deprecations() 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 = object() FAKE_RESP = utils.test_response(text='resp') RESP = mock.Mock(return_value=FAKE_RESP) with mock.patch.object(sess.session, 'request', RESP) as mocked: sess.get(self.TEST_URL, requests_auth=requests_auth) mocked.assert_called_once_with('GET', self.TEST_URL, headers=mock.ANY, allow_redirects=mock.ANY, auth=requests_auth, verify=mock.ANY) 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) 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) 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(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 TEST_URL = CalledAuthPlugin.ENDPOINT def setUp(self): super(AdapterTest, self).setUp() self.deprecations.expect_deprecations() def _create_loaded_adapter(self): auth = CalledAuthPlugin() sess = client_session.Session() return adapter.Adapter(sess, auth=auth, 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) 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.assertTrue(adpt.auth.get_token_called) self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) 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 = jsonutils.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) def _refused_error(request, context): raise requests.exceptions.ConnectionError() self.stub_url('GET', text=_refused_error) with mock.patch('time.sleep') as m: self.assertRaises(exceptions.ConnectionRefused, 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(list(response.keys())[0], self.logger.output) self.assertNotIn(list(response.values())[0], self.logger.output) class ConfLoadingTests(utils.TestCase): GROUP = 'sessiongroup' def setUp(self): super(ConfLoadingTests, self).setUp() self.conf_fixture = self.useFixture(config.Config()) client_session.Session.register_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): with self.deprecations.expect_deprecations_here(): return client_session.Session.load_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 = client_session.Session.get_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() client_session.Session.register_cli_options(self.parser) def get_session(self, val, **kwargs): args = self.parser.parse_args(val.split()) with self.deprecations.expect_deprecations_here(): return client_session.Session.load_from_cli_options(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) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_ec2utils.py0000664000175000017500000003266513644407055026464 0ustar zuulzuul00000000000000# Copyright 2012 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 __future__ import unicode_literals import testtools from keystoneclient.contrib.ec2 import utils from keystoneclient.tests.unit import client_fixtures class Ec2SignerTest(testtools.TestCase): def setUp(self): super(Ec2SignerTest, self).setUp() self.useFixture(client_fixtures.Deprecations()) self.access = '966afbde20b84200ae4e62e09acf46b2' self.secret = '89cdf9e94e2643cab35b8b8ac5a51f83' self.signer = utils.Ec2Signer(self.secret) def test_v4_creds_header(self): auth_str = 'AWS4-HMAC-SHA256 blah' credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {}, 'headers': {'Authorization': auth_str}} self.assertTrue(self.signer._v4_creds(credentials)) def test_v4_creds_param(self): credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'X-Amz-Algorithm': 'AWS4-HMAC-SHA256'}, 'headers': {}} self.assertTrue(self.signer._v4_creds(credentials)) def test_v4_creds_false(self): credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '0', 'AWSAccessKeyId': self.access, 'Timestamp': '2012-11-27T11:47:02Z', 'Action': 'Foo'}} self.assertFalse(self.signer._v4_creds(credentials)) def test_generate_0(self): """Test generate function for v0 signature.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '0', 'AWSAccessKeyId': self.access, 'Timestamp': '2012-11-27T11:47:02Z', 'Action': 'Foo'}} signature = self.signer.generate(credentials) expected = 'SmXQEZAUdQw5glv5mX8mmixBtas=' self.assertEqual(signature, expected) def test_generate_1(self): """Test generate function for v1 signature.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '1', 'AWSAccessKeyId': self.access}} signature = self.signer.generate(credentials) expected = 'VRnoQH/EhVTTLhwRLfuL7jmFW9c=' self.assertEqual(signature, expected) def test_generate_v2_SHA256(self): """Test generate function for v2 signature, SHA256.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '2', 'AWSAccessKeyId': self.access}} signature = self.signer.generate(credentials) expected = 'odsGmT811GffUO0Eu13Pq+xTzKNIjJ6NhgZU74tYX/w=' self.assertEqual(signature, expected) def test_generate_v2_SHA1(self): """Test generate function for v2 signature, SHA1.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '2', 'AWSAccessKeyId': self.access}} self.signer.hmac_256 = None signature = self.signer.generate(credentials) expected = 'ZqCxMI4ZtTXWI175743mJ0hy/Gc=' self.assertEqual(signature, expected) def test_generate_v4(self): """Test v4 generator with data from AWS docs example. see: http://docs.aws.amazon.com/general/latest/gr/ sigv4-create-canonical-request.html and http://docs.aws.amazon.com/general/latest/gr/ sigv4-signed-request-examples.html """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'iam.amazonaws.com', 'Authorization': auth_str} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {'Action': 'CreateUser', 'UserName': 'NewUser', 'Version': '2010-05-08', 'X-Amz-Algorithm': 'AWS4-HMAC-SHA256', 'X-Amz-Credential': 'AKIAEXAMPLE/20140611/' 'us-east-1/iam/aws4_request', 'X-Amz-Date': '20140611T231318Z', 'X-Amz-Expires': '30', 'X-Amz-SignedHeaders': 'host', 'X-Amz-Signature': 'ced6826de92d2bdeed8f846f0bf508e8' '559e98e4b0199114b84c54174deb456c'} credentials = {'host': 'iam.amazonaws.com', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('ced6826de92d2bdeed8f846f0bf508e8' '559e98e4b0199114b84c54174deb456c') self.assertEqual(signature, expected) def test_generate_v4_port(self): """Test v4 generator with host:port format.""" # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(signature, expected) def test_generate_v4_port_strip(self): """Test v4 generator with host:port format for old boto version. Validate for old (<2.9.3) version of boto, where the port should be stripped to match boto behavior. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.9.2 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('9a4b2276a5039ada3b90f72ea8ec1745' '14b92b909fb106b22ad910c5d75a54f4') self.assertEqual(expected, signature) def test_generate_v4_port_nostrip(self): """Test v4 generator with host:port format for new boto version. Validate for new (>=2.9.3) version of boto, where the port should not be stripped. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.9.3 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(expected, signature) def test_generate_v4_port_malformed_version(self): """Test v4 generator with host:port format for malformed boto version. Validate for malformed version of boto, where the port should not be stripped. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.922 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(expected, signature) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_utils.py0000664000175000017500000001103313644407055026054 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 as ksa_exceptions import six import testresources from testtools import matchers from keystoneclient import exceptions as ksc_exceptions from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils as test_utils from keystoneclient import utils class FakeResource(object): pass class FakeManager(object): resource_class = FakeResource resources = { '1234': {'name': 'entity_one'}, '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0': {'name': 'entity_two'}, '\xe3\x82\xbdtest': {'name': u'\u30bdtest'}, '5678': {'name': '9876'} } def get(self, resource_id): try: return self.resources[str(resource_id)] except KeyError: raise ksa_exceptions.NotFound(resource_id) def find(self, name=None): if name == '9999': # NOTE(morganfainberg): special case that raises NoUniqueMatch. raise ksc_exceptions.NoUniqueMatch() for resource_id, resource in self.resources.items(): if resource['name'] == str(name): return resource raise ksa_exceptions.NotFound(name) class FindResourceTestCase(test_utils.TestCase): def setUp(self): super(FindResourceTestCase, self).setUp() self.manager = FakeManager() def test_find_none(self): self.assertRaises(ksc_exceptions.CommandError, utils.find_resource, self.manager, 'asdf') def test_find_by_integer_id(self): output = utils.find_resource(self.manager, 1234) self.assertEqual(output, self.manager.resources['1234']) def test_find_by_str_id(self): output = utils.find_resource(self.manager, '1234') self.assertEqual(output, self.manager.resources['1234']) def test_find_by_uuid(self): uuid = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' output = utils.find_resource(self.manager, uuid) self.assertEqual(output, self.manager.resources[uuid]) def test_find_by_unicode(self): name = '\xe3\x82\xbdtest' output = utils.find_resource(self.manager, name) self.assertEqual(output, self.manager.resources[name]) def test_find_by_str_name(self): output = utils.find_resource(self.manager, 'entity_one') self.assertEqual(output, self.manager.resources['1234']) def test_find_by_int_name(self): output = utils.find_resource(self.manager, 9876) self.assertEqual(output, self.manager.resources['5678']) def test_find_no_unique_match(self): self.assertRaises(ksc_exceptions.CommandError, utils.find_resource, self.manager, 9999) class FakeObject(object): def __init__(self, name): self.name = name class HashSignedTokenTestCase(test_utils.TestCase, testresources.ResourcedTestCase): """Unit tests for utils.hash_signed_token().""" resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_default_md5(self): """The default hash method is md5.""" token = self.examples.SIGNED_TOKEN_SCOPED if six.PY3: token = token.encode('utf-8') token_id_default = utils.hash_signed_token(token) token_id_md5 = utils.hash_signed_token(token, mode='md5') self.assertThat(token_id_default, matchers.Equals(token_id_md5)) # md5 hash is 32 chars. self.assertThat(token_id_default, matchers.HasLength(32)) def test_sha256(self): """Can also hash with sha256.""" token = self.examples.SIGNED_TOKEN_SCOPED if six.PY3: token = token.encode('utf-8') token_id = utils.hash_signed_token(token, mode='sha256') # sha256 hash is 64 chars. self.assertThat(token_id, matchers.HasLength(64)) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/generic/0000775000175000017500000000000013644407143024717 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/generic/test_client.py0000664000175000017500000000542313644407055027614 0ustar zuulzuul00000000000000# Copyright 2014 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. from oslo_serialization import jsonutils from keystoneclient.generic import client from keystoneclient.tests.unit import utils BASE_HOST = 'http://keystone.example.com' BASE_URL = "%s:5000/" % BASE_HOST V2_URL = "%sv2.0" % BASE_URL EXTENSION_NAMESPACE = ("https://docs.openstack.org/identity/api/ext/OS-FAKE/" "v1.0") EXTENSION_DESCRIBED = {"href": "https://github.com/openstack/identity-api", "rel": "describedby", "type": "text/html"} EXTENSION_ALIAS_FOO = "OS-FAKE-FOO" EXTENSION_NAME_FOO = "OpenStack Keystone Fake Extension Foo" EXTENSION_FOO = {"alias": EXTENSION_ALIAS_FOO, "description": "Fake Foo extension to V2.0 API.", "links": [EXTENSION_DESCRIBED], "name": EXTENSION_NAME_FOO, "namespace": EXTENSION_NAMESPACE, "updated": '2014-01-08T00:00:00Z'} EXTENSION_ALIAS_BAR = "OS-FAKE-BAR" EXTENSION_NAME_BAR = "OpenStack Keystone Fake Extension Bar" EXTENSION_BAR = {"alias": EXTENSION_ALIAS_BAR, "description": "Fake Bar extension to V2.0 API.", "links": [EXTENSION_DESCRIBED], "name": EXTENSION_NAME_BAR, "namespace": EXTENSION_NAMESPACE, "updated": '2014-01-08T00:00:00Z'} def _create_extension_list(extensions): return jsonutils.dumps({'extensions': {'values': extensions}}) EXTENSION_LIST = _create_extension_list([EXTENSION_FOO, EXTENSION_BAR]) class ClientDiscoveryTests(utils.TestCase): def test_discover_extensions_v2(self): self.requests_mock.get("%s/extensions" % V2_URL, text=EXTENSION_LIST) # Creating a HTTPClient not using session is deprecated. # creating a generic client at all is deprecated. with self.deprecations.expect_deprecations_here(): extensions = client.Client().discover_extensions(url=V2_URL) self.assertIn(EXTENSION_ALIAS_FOO, extensions) self.assertEqual(extensions[EXTENSION_ALIAS_FOO], EXTENSION_NAME_FOO) self.assertIn(EXTENSION_ALIAS_BAR, extensions) self.assertEqual(extensions[EXTENSION_ALIAS_BAR], EXTENSION_NAME_BAR) python-keystoneclient-4.0.0/keystoneclient/tests/unit/generic/__init__.py0000664000175000017500000000000013644407055027020 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/0000775000175000017500000000000013644407143024051 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_discovery.py0000664000175000017500000000675713644407055027512 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.generic import client from keystoneclient.tests.unit.v2_0 import utils class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): def setUp(self): super(DiscoverKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "versions": { "values": [{ "id": "v2.0", "status": "beta", "updated": "2011-11-19T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", }, {"rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://127.0.0.1:5000/v2.0/identity.wadl", } ], "media-types": [{ "base": "application/xml", "type": "application/vnd.openstack.identity-v2.0+xml", }, { "base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json", }], }], }, } def test_get_versions(self): self.stub_url('GET', base_url=self.TEST_ROOT_URL, json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover(self.TEST_ROOT_URL) self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v2.0', versions) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) def test_get_version_local(self): self.stub_url('GET', base_url="http://localhost:35357/", json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover() self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v2.0', versions) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/client_fixtures.py0000664000175000017500000001242013644407055027633 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 __future__ import unicode_literals import uuid from keystoneauth1 import fixture def unscoped_token(): return fixture.V2Token(token_id='3e2813b7ba0b4006840c3825860b86ed', expires='2012-10-03T16:58:01Z', user_id='c4da488862bd435c9e6c0275a0d0e49a', user_name='exampleuser') def project_scoped_token(): _TENANT_ID = '225da22d3ce34b15877ea70b2a575f58' f = fixture.V2Token(token_id='04c7d5ffaeef485f9dc69c06db285bdb', expires='2012-10-03T16:53:36Z', tenant_id='225da22d3ce34b15877ea70b2a575f58', tenant_name='exampleproject', user_id='c4da488862bd435c9e6c0275a0d0e49a', user_name='exampleuser', audit_chain_id=uuid.uuid4().hex) f.add_role(id='member_id', name='Member') s = f.add_service('volume', 'Volume Service') s.add_endpoint(public='http://public.com:8776/v1/%s' % _TENANT_ID, admin='http://admin:8776/v1/%s' % _TENANT_ID, internal='http://internal:8776/v1/%s' % _TENANT_ID, region='RegionOne') s = f.add_service('image', 'Image Service') s.add_endpoint(public='http://public.com:9292/v1', admin='http://admin:9292/v1', internal='http://internal:9292/v1', region='RegionOne') s = f.add_service('compute', 'Compute Service') s.add_endpoint(public='http://public.com:8774/v2/%s' % _TENANT_ID, admin='http://admin:8774/v2/%s' % _TENANT_ID, internal='http://internal:8774/v2/%s' % _TENANT_ID, region='RegionOne') s = f.add_service('ec2', 'EC2 Service') s.add_endpoint(public='http://public.com:8773/services/Cloud', admin='http://admin:8773/services/Admin', internal='http://internal:8773/services/Cloud', region='RegionOne') s = f.add_service('identity', 'Identity Service') s.add_endpoint(public='http://public.com:5000/v2.0', admin='http://admin:35357/v2.0', internal='http://internal:5000/v2.0', region='RegionOne') return f def auth_response_body(): f = 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) f.add_role(id='234', name='compute:admin') role = f.add_role(id='235', name='object-store:admin') role['tenantId'] = '1' s = f.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 = f.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 = f.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') return f python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_access.py0000664000175000017500000002061313644407055026727 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 fixture from oslo_utils import timeutils import testresources from keystoneclient import access from keystoneclient.tests.unit import client_fixtures as token_data from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class AccessInfoTest(utils.TestCase, testresources.ResourcedTestCase): resources = [('examples', token_data.EXAMPLES_RESOURCE)] def test_building_unscoped_accessinfo(self): token = client_fixtures.unscoped_token() auth_ref = access.AccessInfo.factory(body=token) self.assertTrue(auth_ref) self.assertIn('token', auth_ref) self.assertEqual(auth_ref.auth_token, '3e2813b7ba0b4006840c3825860b86ed') self.assertEqual(auth_ref.username, 'exampleuser') self.assertEqual(auth_ref.user_id, 'c4da488862bd435c9e6c0275a0d0e49a') self.assertEqual(auth_ref.role_ids, []) self.assertEqual(auth_ref.role_names, []) self.assertIsNone(auth_ref.tenant_name) self.assertIsNone(auth_ref.tenant_id) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.auth_url) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.management_url) with self.deprecations.expect_deprecations_here(): self.assertFalse(auth_ref.scoped) 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.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') 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) def test_will_expire_soon(self): token = client_fixtures.unscoped_token() expires = timeutils.utcnow() + datetime.timedelta(minutes=5) token.expires = expires auth_ref = access.AccessInfo.factory(body=token) 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 = client_fixtures.project_scoped_token() auth_ref = access.AccessInfo.factory(body=token) self.assertTrue(auth_ref) self.assertIn('token', auth_ref) self.assertIn('serviceCatalog', auth_ref) self.assertTrue(auth_ref['serviceCatalog']) self.assertEqual(auth_ref.auth_token, '04c7d5ffaeef485f9dc69c06db285bdb') self.assertEqual(auth_ref.username, 'exampleuser') self.assertEqual(auth_ref.user_id, 'c4da488862bd435c9e6c0275a0d0e49a') self.assertEqual(auth_ref.role_ids, ['member_id']) self.assertEqual(auth_ref.role_names, ['Member']) self.assertEqual(auth_ref.tenant_name, 'exampleproject') self.assertEqual(auth_ref.tenant_id, '225da22d3ce34b15877ea70b2a575f58') self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.auth_url, ('http://public.com:5000/v2.0',)) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.management_url, ('http://admin:35357/v2.0',)) self.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') with self.deprecations.expect_deprecations_here(): self.assertTrue(auth_ref.scoped) 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 = self.examples.TOKEN_RESPONSES[ self.examples.VALID_DIABLO_TOKEN] auth_ref = access.AccessInfo.factory(body=diablo_token) 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.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') self.assertEqual(auth_ref.role_names, ['role1', 'role2']) with self.deprecations.expect_deprecations_here(): self.assertFalse(auth_ref.scoped) def test_grizzly_token(self): grizzly_token = self.examples.TOKEN_RESPONSES[ self.examples.SIGNED_TOKEN_SCOPED_KEY] auth_ref = access.AccessInfo.factory(body=grizzly_token) self.assertEqual(auth_ref.project_id, 'tenant_id1') self.assertEqual(auth_ref.project_name, 'tenant_name1') self.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(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.AccessInfo.factory(body=token) self.assertEqual([role_id], auth_ref.role_ids) self.assertEqual([role_id], auth_ref['metadata']['roles']) self.assertEqual([role_name], auth_ref.role_names) self.assertEqual([{'name': role_name}], auth_ref['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.AccessInfo.factory(body=token) 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_override_auth_token(self): token = fixture.V2Token() token.set_scope() token.add_role() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token) self.assertEqual(token.token_id, auth_ref.auth_token) auth_ref.auth_token = new_auth_token self.assertEqual(new_auth_token, auth_ref.auth_token) del auth_ref.auth_token self.assertEqual(token.token_id, auth_ref.auth_token) def test_override_auth_token_in_factory(self): token = fixture.V2Token() token.set_scope() token.add_role() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token, auth_token=new_auth_token) self.assertEqual(new_auth_token, auth_ref.auth_token) del auth_ref.auth_token self.assertEqual(token.token_id, auth_ref.auth_token) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_client.py0000664000175000017500000002343013644407055026744 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_serialization import jsonutils from keystoneauth1 import fixture from keystoneauth1 import session as auth_session from keystoneclient.auth import token_endpoint from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client class KeystoneClientTest(utils.TestCase): def test_unscoped_init(self): token = client_fixtures.unscoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username='exampleuser', password='password', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertFalse(c.auth_ref.scoped) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertIsNone(c.auth_ref.trust_id) self.assertFalse(c.auth_ref.trust_scoped) self.assertIsNone(c.get_project_id(session=None)) self.assertEqual(token.user_id, c.get_user_id(session=None)) def test_scoped_init(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(c.auth_ref.scoped) self.assertTrue(c.auth_ref.project_scoped) self.assertFalse(c.auth_ref.domain_scoped) self.assertIsNone(c.auth_ref.trust_id) self.assertFalse(c.auth_ref.trust_scoped) self.assertEqual(token.tenant_id, c.get_project_id(session=None)) self.assertEqual(token.user_id, c.get_user_id(session=None)) def test_auth_ref_load(self): self.stub_auth(json=client_fixtures.project_scoped_token()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) cache = jsonutils.dumps(cl.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache)) self.assertIsNotNone(new_client.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(new_client.auth_ref.scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertIsNone(new_client.auth_ref.trust_id) self.assertFalse(new_client.auth_ref.trust_scoped) self.assertEqual(new_client.username, 'exampleuser') self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v2.0') def test_auth_ref_load_with_overridden_arguments(self): self.stub_auth(json=client_fixtures.project_scoped_token()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) cache = jsonutils.dumps(cl.auth_ref) new_auth_url = "http://new-public:5000/v2.0" # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache), auth_url=new_auth_url) self.assertIsNotNone(new_client.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(new_client.auth_ref.scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertIsNone(new_client.auth_ref.trust_id) self.assertFalse(new_client.auth_ref.trust_scoped) self.assertEqual(new_client.auth_url, new_auth_url) self.assertEqual(new_client.username, 'exampleuser') self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v2.0') def test_init_err_no_auth_url(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, username='exampleuser', password='password') def test_management_url_is_updated(self): first = fixture.V2Token() first.set_scope() admin_url = 'http://admin:35357/v2.0' second_url = 'http://secondurl:35357/v2.0' s = first.add_service('identity') s.add_endpoint(public='http://public.com:5000/v2.0', admin=admin_url) second = fixture.V2Token() second.set_scope() s = second.add_service('identity') s.add_endpoint(public='http://secondurl:5000/v2.0', admin=second_url) self.stub_auth(response_list=[{'json': first}, {'json': second}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.management_url, admin_url) with self.deprecations.expect_deprecations_here(): cl.authenticate() self.assertEqual(cl.management_url, second_url) def test_client_with_region_name_passes_to_service_catalog(self): # NOTE(jamielennox): this is deprecated behaviour that should be # removed ASAP, however must remain compatible. self.stub_auth(json=client_fixtures.auth_response_body()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='North') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'https://image.north.host/v1/') # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='South') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'https://image.south.host/v1/') def test_client_without_auth_params(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, project_name='exampleproject', auth_url=self.TEST_URL) def test_client_params(self): with self.deprecations.expect_deprecations_here(): sess = session.Session() auth = token_endpoint.Token('a', 'b') opts = {'auth': auth, 'connect_retries': 50, 'endpoint_override': uuid.uuid4().hex, 'interface': uuid.uuid4().hex, 'region_name': uuid.uuid4().hex, 'service_name': uuid.uuid4().hex, 'user_agent': uuid.uuid4().hex, } cl = client.Client(session=sess, **opts) for k, v in opts.items(): self.assertEqual(v, getattr(cl._adapter, k)) self.assertEqual('identity', cl._adapter.service_type) self.assertEqual((2, 0), cl._adapter.version) def test_empty_service_catalog_param(self): # Client().service_catalog should return None if the client is not # authenticated sess = auth_session.Session() cl = client.Client(session=sess) self.assertIsNone(cl.service_catalog) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/__init__.py0000664000175000017500000000000013644407055026152 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_extensions.py0000664000175000017500000000542713644407055027673 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import extensions class ExtensionTests(utils.ClientTestCase): def setUp(self): super(ExtensionTests, self).setUp() self.TEST_EXTENSIONS = { 'extensions': { "values": [ { 'name': 'OpenStack Keystone User CRUD', 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-KSCRUD/v1.0', 'updated': '2013-07-07T12:00:0-00:00', 'alias': 'OS-KSCRUD', 'description': 'OpenStack extensions to Keystone v2.0 API' ' enabling User Operations.', 'links': '[{"href":' '"https://github.com/openstack/identity-api", "type":' ' "text/html", "rel": "describedby"}]', }, { 'name': 'OpenStack EC2 API', 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-EC2/v1.0', 'updated': '2013-09-07T12:00:0-00:00', 'alias': 'OS-EC2', 'description': 'OpenStack EC2 Credentials backend.', 'links': '[{"href":' '"https://github.com/openstack/identity-api", "type":' ' "text/html", "rel": "describedby"}]', } ] } } def test_list(self): self.stub_url('GET', ['extensions'], json=self.TEST_EXTENSIONS) extensions_list = self.client.extensions.list() self.assertEqual(2, len(extensions_list)) for extension in extensions_list: self.assertIsInstance(extension, extensions.Extension) self.assertIsNotNone(extension.alias) self.assertIsNotNone(extension.description) self.assertIsNotNone(extension.links) self.assertIsNotNone(extension.name) self.assertIsNotNone(extension.namespace) self.assertIsNotNone(extension.updated) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_service_catalog.py0000664000175000017500000002211013644407055030612 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from keystoneclient import access from keystoneclient import exceptions from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class ServiceCatalogTest(utils.TestCase): def setUp(self): super(ServiceCatalogTest, self).setUp() self.AUTH_RESPONSE_BODY = client_fixtures.auth_response_body() def test_building_a_service_catalog(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog self.assertEqual(sc.url_for(service_type='compute'), "https://compute.north.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '1', service_type='compute'), "https://compute.north.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '2', service_type='compute'), "https://compute.north.host/v1.1/3456") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='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_regions(self): self.AUTH_RESPONSE_BODY['access']['region_name'] = "North" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='publicURL') self.assertEqual(url, "https://image.north.host/v1/") self.AUTH_RESPONSE_BODY['access']['region_name'] = "South" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='internalURL') self.assertEqual(url, "https://image-internal.south.host/v1/") def test_service_catalog_empty(self): self.AUTH_RESPONSE_BODY['access']['serviceCatalog'] = [] auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) self.assertRaises(exceptions.EmptyCatalog, auth_ref.service_catalog.url_for, service_type='image', endpoint_type='internalURL') def test_service_catalog_get_endpoints_region_names(self): auth_ref = access.AccessInfo.factory(None, 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.AccessInfo.factory(None, 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/') url = sc.url_for(service_type='compute', region_name='North', attr='versionId', filter_value='1.1') self.assertEqual(url, 'https://compute.north.host/v1.1/3456') 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.AccessInfo.factory(None, 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.assertIsNone(urls) def test_service_catalog_param_overrides_body_region(self): self.AUTH_RESPONSE_BODY['access']['region_name'] = "North" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image') 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/') endpoints = sc.get_endpoints(service_type='image') self.assertEqual(len(endpoints['image']), 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['image']), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.south.host/v1/') def test_service_catalog_service_name(self): auth_ref = access.AccessInfo.factory(resp=None, body=self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_name='Image Servers', endpoint_type='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', endpoint_type='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', endpoint_type='public') self.assertIsNone(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.AccessInfo.factory(resp=None, body=token) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='publicURL') self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='publicURL', region_name='region-1') self.assertEqual(('public-1', ), urls) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_certificates.py0000664000175000017500000000313613644407055030134 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 testresources from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class CertificateTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_get_ca_certificate(self): self.stub_url('GET', ['certificates', 'ca'], headers={'Content-Type': 'text/html; charset=UTF-8'}, text=self.examples.SIGNING_CA) res = self.client.certificates.get_ca_certificate() self.assertEqual(self.examples.SIGNING_CA, res) def test_get_signing_certificate(self): self.stub_url('GET', ['certificates', 'signing'], headers={'Content-Type': 'text/html; charset=UTF-8'}, text=self.examples.SIGNING_CERT) res = self.client.certificates.get_signing_certificate() self.assertEqual(self.examples.SIGNING_CERT, res) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_auth.py0000664000175000017500000002645513644407055026441 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import datetime from oslo_serialization import jsonutils from oslo_utils import timeutils from testtools import testcase from keystoneclient import exceptions from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): def setUp(self): super(AuthenticateAgainstKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "access": { "token": { "expires": "2999-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, }, } self.TEST_REQUEST_BODY = { "auth": { "passwordCredentials": { "username": self.TEST_USER, "password": self.TEST_TOKEN, }, "tenantId": self.TEST_TENANT_ID, }, } def test_authenticate_success_expired(self): resp_a = copy.deepcopy(self.TEST_RESPONSE_DICT) resp_b = copy.deepcopy(self.TEST_RESPONSE_DICT) headers = {'Content-Type': 'application/json'} # Build an expired token resp_a['access']['token']['expires'] = ( (timeutils.utcnow() - datetime.timedelta(1)).isoformat()) # Build a new response TEST_TOKEN = "abcdef" resp_b['access']['token']['expires'] = '2999-01-01T00:00:10.000123Z' resp_b['access']['token']['id'] = TEST_TOKEN # return expired first, and then the new response self.stub_auth(response_list=[{'json': resp_a, 'headers': headers}, {'json': resp_b, 'headers': headers}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL, username=self.TEST_USER, password=self.TEST_TOKEN) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) with self.deprecations.expect_deprecations_here(): self.assertEqual(cs.auth_token, TEST_TOKEN) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_failure(self): _auth = 'auth' _cred = 'passwordCredentials' _pass = 'password' self.TEST_REQUEST_BODY[_auth][_cred][_pass] = 'bad_key' error = {"unauthorized": {"message": "Unauthorized", "code": "401"}} self.stub_auth(status_code=401, json=error) with testcase.ExpectedException(exceptions.Unauthorized): with self.deprecations.expect_deprecations_here(): client.Client(username=self.TEST_USER, password="bad_key", project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_redirect(self): self.stub_auth(status_code=305, text='Use Proxy', headers={'Location': self.TEST_ADMIN_URL + "/tokens"}) self.stub_auth(base_url=self.TEST_ADMIN_URL, json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_unscoped(self): del self.TEST_RESPONSE_DICT['access']['serviceCatalog'] del self.TEST_REQUEST_BODY['auth']['tenantId'] self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): fake_token = 'fake_token' fake_url = '/fake-url' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) with self.deprecations.expect_deprecations_here(): cl = client.Client(auth_url=self.TEST_URL, token=fake_token) json_body = jsonutils.loads(self.requests_mock.last_request.body) self.assertEqual(json_body['auth']['token']['id'], fake_token) with self.deprecations.expect_deprecations_here(): resp, body = cl.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) def test_authenticate_success_token_scoped(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_scoped_trust(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.TEST_REQUEST_BODY['auth']['trust_id'] = self.TEST_TRUST_ID response = self.TEST_RESPONSE_DICT.copy() response['access']['trust'] = {"trustee_user_id": self.TEST_USER, "id": self.TEST_TRUST_ID} self.stub_auth(json=response) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, trust_id=self.TEST_TRUST_ID, auth_url=self.TEST_URL) self.assertTrue(cs.auth_ref.trust_scoped) self.assertEqual(cs.auth_ref.trust_id, self.TEST_TRUST_ID) self.assertEqual(cs.auth_ref.trustee_user_id, self.TEST_USER) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_unscoped(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] del self.TEST_REQUEST_BODY['auth']['tenantId'] del self.TEST_RESPONSE_DICT['access']['serviceCatalog'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): fake_url = '/fake-url' fake_token = 'fake_token' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.auth_token, self.TEST_TOKEN) # the token returned from the authentication will be used resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) # then override that token and the new token shall be used cl.auth_token = fake_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(fake_token, token) # if we clear that overridden token then we fall back to the original del cl.auth_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_users.py0000664000175000017500000002353013644407055026630 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import users class UserTests(utils.ClientTestCase): def setUp(self): super(UserTests, self).setUp() self.ADMIN_USER_ID = uuid.uuid4().hex self.DEMO_USER_ID = uuid.uuid4().hex self.TEST_USERS = { "users": { "values": [ { "email": "None", "enabled": True, "id": self.ADMIN_USER_ID, "name": "admin", }, { "email": "None", "enabled": True, "id": self.DEMO_USER_ID, "name": "demo", }, ] } } def test_create(self): tenant_id = uuid.uuid4().hex user_id = uuid.uuid4().hex password = uuid.uuid4().hex req_body = { "user": { "name": "gabriel", "password": password, "tenantId": tenant_id, "email": "test@example.com", "enabled": True, } } resp_body = { "user": { "name": "gabriel", "enabled": True, "tenantId": tenant_id, "id": user_id, "password": password, "email": "test@example.com", } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create(req_body['user']['name'], req_body['user']['password'], req_body['user']['email'], tenant_id=req_body['user']['tenantId'], enabled=req_body['user']['enabled']) self.assertIsInstance(user, users.User) self.assertEqual(user.id, user_id) self.assertEqual(user.name, "gabriel") self.assertEqual(user.email, "test@example.com") self.assertRequestBodyIs(json=req_body) self.assertNotIn(password, self.logger.output) def test_create_user_without_email(self): tenant_id = uuid.uuid4().hex req_body = { "user": { "name": "gabriel", "password": "test", "tenantId": tenant_id, "enabled": True, "email": None, } } user_id = uuid.uuid4().hex resp_body = { "user": { "name": "gabriel", "enabled": True, "tenantId": tenant_id, "id": user_id, "password": "test", } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create( req_body['user']['name'], req_body['user']['password'], tenant_id=req_body['user']['tenantId'], enabled=req_body['user']['enabled']) self.assertIsInstance(user, users.User) self.assertEqual(user.id, user_id) self.assertEqual(user.name, "gabriel") self.assertRequestBodyIs(json=req_body) def test_create_user_without_password(self): user_name = 'test' user_id = uuid.uuid4().hex tenant_id = uuid.uuid4().hex user_enabled = True req_body = { 'user': { 'name': user_name, 'password': None, 'tenantId': tenant_id, 'enabled': user_enabled, 'email': None, } } resp_body = { 'user': { 'name': user_name, 'enabled': user_enabled, 'tenantId': tenant_id, 'id': user_id, } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create(user_name, tenant_id=tenant_id, enabled=user_enabled) self.assertIsInstance(user, users.User) self.assertEqual(user_id, user.id) self.assertEqual(user_name, user.name) self.assertRequestBodyIs(json=req_body) def test_delete(self): self.stub_url('DELETE', ['users', self.ADMIN_USER_ID], status_code=204) self.client.users.delete(self.ADMIN_USER_ID) def test_get(self): self.stub_url('GET', ['users', self.ADMIN_USER_ID], json={'user': self.TEST_USERS['users']['values'][0]}) u = self.client.users.get(self.ADMIN_USER_ID) self.assertIsInstance(u, users.User) self.assertEqual(u.id, self.ADMIN_USER_ID) self.assertEqual(u.name, 'admin') def test_list(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list() [self.assertIsInstance(u, users.User) for u in user_list] def test_list_limit(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(limit=1) self.assertQueryStringIs('limit=1') [self.assertIsInstance(u, users.User) for u in user_list] def test_list_marker(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(marker='foo') self.assertQueryStringIs('marker=foo') [self.assertIsInstance(u, users.User) for u in user_list] def test_list_limit_marker(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(limit=1, marker='foo') self.assertQueryStringIs('marker=foo&limit=1') [self.assertIsInstance(u, users.User) for u in user_list] def test_update(self): req_1 = { "user": { "email": "gabriel@example.com", "name": "gabriel", } } password = uuid.uuid4().hex req_2 = { "user": { "password": password, } } tenant_id = uuid.uuid4().hex req_3 = { "user": { "tenantId": tenant_id, } } req_4 = { "user": { "enabled": False, } } self.stub_url('PUT', ['users', self.DEMO_USER_ID], json=req_1) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'password'], json=req_2) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'tenant'], json=req_3) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'enabled'], json=req_4) self.client.users.update(self.DEMO_USER_ID, name='gabriel', email='gabriel@example.com') self.assertRequestBodyIs(json=req_1) self.client.users.update_password(self.DEMO_USER_ID, password) self.assertRequestBodyIs(json=req_2) self.client.users.update_tenant(self.DEMO_USER_ID, tenant_id) self.assertRequestBodyIs(json=req_3) self.client.users.update_enabled(self.DEMO_USER_ID, False) self.assertRequestBodyIs(json=req_4) self.assertNotIn(password, self.logger.output) def test_update_own_password(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex req_body = { 'user': { 'password': new_password, 'original_password': old_password } } resp_body = { 'access': {} } self.stub_url('PATCH', ['OS-KSCRUD', 'users', self.TEST_USER_ID], json=resp_body) self.client.users.update_own_password(old_password, new_password) self.assertRequestBodyIs(json=req_body) self.assertNotIn(old_password, self.logger.output) self.assertNotIn(new_password, self.logger.output) def test_user_role_listing(self): user_id = uuid.uuid4().hex role_id1 = uuid.uuid4().hex role_id2 = uuid.uuid4().hex tenant_id = uuid.uuid4().hex user_resp = { 'user': { 'id': user_id, 'email': uuid.uuid4().hex, 'name': uuid.uuid4().hex, } } roles_resp = { 'roles': { 'values': [ { 'name': uuid.uuid4().hex, 'id': role_id1, }, { 'name': uuid.uuid4().hex, 'id': role_id2, } ] } } self.stub_url('GET', ['users', user_id], json=user_resp) self.stub_url('GET', ['tenants', tenant_id, 'users', user_id, 'roles'], json=roles_resp) user = self.client.users.get(user_id) role_objs = user.list_roles(tenant_id) for r in role_objs: self.assertIsInstance(r, roles.Role) self.assertEqual(set([role_id1, role_id2]), set([r.id for r in role_objs])) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_services.py0000664000175000017500000001123313644407055027307 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import services class ServiceTests(utils.ClientTestCase): def setUp(self): super(ServiceTests, self).setUp() self.NOVA_SERVICE_ID = uuid.uuid4().hex self.KEYSTONE_SERVICE_ID = uuid.uuid4().hex self.TEST_SERVICES = { "OS-KSADM:services": { "values": [ { "name": "nova", "type": "compute", "description": "Nova-compatible service.", "id": self.NOVA_SERVICE_ID }, { "name": "keystone", "type": "identity", "description": "Keystone-compatible service.", "id": self.KEYSTONE_SERVICE_ID }, ], }, } def test_create_with_description(self): req_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": "Swift-compatible service.", } } service_id = uuid.uuid4().hex resp_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": "Swift-compatible service.", "id": service_id, } } self.stub_url('POST', ['OS-KSADM', 'services'], json=resp_body) service = self.client.services.create( req_body['OS-KSADM:service']['name'], req_body['OS-KSADM:service']['type'], req_body['OS-KSADM:service']['description']) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, service_id) self.assertEqual(service.name, req_body['OS-KSADM:service']['name']) self.assertEqual(service.description, req_body['OS-KSADM:service']['description']) self.assertRequestBodyIs(json=req_body) def test_create_without_description(self): req_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": None, } } service_id = uuid.uuid4().hex resp_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "id": service_id, "description": None, } } self.stub_url('POST', ['OS-KSADM', 'services'], json=resp_body) service = self.client.services.create( req_body['OS-KSADM:service']['name'], req_body['OS-KSADM:service']['type'], req_body['OS-KSADM:service']['description']) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, service_id) self.assertEqual(service.name, req_body['OS-KSADM:service']['name']) self.assertIsNone(service.description) self.assertRequestBodyIs(json=req_body) def test_delete(self): self.stub_url('DELETE', ['OS-KSADM', 'services', self.NOVA_SERVICE_ID], status_code=204) self.client.services.delete(self.NOVA_SERVICE_ID) def test_get(self): test_services = self.TEST_SERVICES['OS-KSADM:services']['values'][0] self.stub_url('GET', ['OS-KSADM', 'services', self.NOVA_SERVICE_ID], json={'OS-KSADM:service': test_services}) service = self.client.services.get(self.NOVA_SERVICE_ID) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, self.NOVA_SERVICE_ID) self.assertEqual(service.name, 'nova') self.assertEqual(service.type, 'compute') def test_list(self): self.stub_url('GET', ['OS-KSADM', 'services'], json=self.TEST_SERVICES) service_list = self.client.services.list() [self.assertIsInstance(r, services.Service) for r in service_list] python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/utils.py0000664000175000017500000000577613644407055025604 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils class UnauthenticatedTestCase(utils.TestCase): """Class used as base for unauthenticated calls.""" 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') class TestCase(UnauthenticatedTestCase): TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v2.0" 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_IDENTITY_ENDPOINT, "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 stub_auth(self, **kwargs): self.stub_url('POST', ['tokens'], **kwargs) class ClientTestCase(utils.ClientTestCaseMixin, TestCase): scenarios = [ ('original', {'client_fixture_class': client_fixtures.OriginalV2}), ('ksc-session', {'client_fixture_class': client_fixtures.KscSessionV2}), ('ksa-session', {'client_fixture_class': client_fixtures.KsaSessionV2}), ] python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_endpoints.py0000664000175000017500000001302713644407055027472 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import endpoints class EndpointTests(utils.ClientTestCase): def setUp(self): super(EndpointTests, self).setUp() self.TEST_ENDPOINTS = { 'endpoints': [ { 'adminurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'id': '8f9531231e044e218824b0e58688d262', 'internalurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'publicurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'region': 'RegionOne', }, { 'adminurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'id': '8f9531231e044e218824b0e58688d263', 'internalurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'publicurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'region': 'RegionOne', } ] } def test_create_with_optional_params(self): req_body = { "endpoint": { "region": "RegionOne", "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "internalurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": "http://host-3:8774/v1.1/$(tenant_id)s", "service_id": uuid.uuid4().hex, } } resp_body = { "endpoint": { "adminurl": "http://host-3:8774/v1.1/$(tenant_id)s", "region": "RegionOne", "id": uuid.uuid4().hex, "internalurl": "http://host-3:8774/v1.1/$(tenant_id)s", "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint = self.client.endpoints.create( region=req_body['endpoint']['region'], publicurl=req_body['endpoint']['publicurl'], adminurl=req_body['endpoint']['adminurl'], internalurl=req_body['endpoint']['internalurl'], service_id=req_body['endpoint']['service_id'] ) self.assertIsInstance(endpoint, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body) def test_create_with_optional_params_as_none(self): req_body_without_defaults = { "endpoint": { "region": "RegionOne", "service_id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } resp_body = { "endpoint": { "region": "RegionOne", "id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint_without_defaults = self.client.endpoints.create( region=req_body_without_defaults['endpoint']['region'], publicurl=req_body_without_defaults['endpoint']['publicurl'], service_id=req_body_without_defaults['endpoint']['service_id'], adminurl=None, internalurl=None ) self.assertIsInstance(endpoint_without_defaults, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body_without_defaults) def test_create_without_optional_params(self): req_body_without_defaults = { "endpoint": { "region": "RegionOne", "service_id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } resp_body = { "endpoint": { "region": "RegionOne", "id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint_without_defaults = self.client.endpoints.create( region=req_body_without_defaults['endpoint']['region'], publicurl=req_body_without_defaults['endpoint']['publicurl'], service_id=req_body_without_defaults['endpoint']['service_id'] ) self.assertIsInstance(endpoint_without_defaults, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body_without_defaults) def test_delete(self): self.stub_url('DELETE', ['endpoints', '8f953'], status_code=204) self.client.endpoints.delete('8f953') def test_list(self): self.stub_url('GET', ['endpoints'], json=self.TEST_ENDPOINTS) endpoint_list = self.client.endpoints.list() [self.assertIsInstance(r, endpoints.Endpoint) for r in endpoint_list] python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_tenants.py0000664000175000017500000003203013644407055027136 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import fixture from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import users class TenantTests(utils.ClientTestCase): def setUp(self): super(TenantTests, self).setUp() self.INVIS_ID = uuid.uuid4().hex self.DEMO_ID = uuid.uuid4().hex self.ADMIN_ID = uuid.uuid4().hex self.EXTRAS_ID = uuid.uuid4().hex self.TEST_TENANTS = { "tenants": { "values": [ { "enabled": True, "description": "A description change!", "name": "invisible_to_admin", "id": self.INVIS_ID, }, { "enabled": True, "description": "None", "name": "demo", "id": self.DEMO_ID, }, { "enabled": True, "description": "None", "name": "admin", "id": self.ADMIN_ID, }, { "extravalue01": "metadata01", "enabled": True, "description": "For testing extras", "name": "test_extras", "id": self.EXTRAS_ID, } ], "links": [], }, } def test_create(self): req_body = { "tenant": { "name": "tenantX", "description": "Like tenant 9, but better.", "enabled": True, "extravalue01": "metadata01", }, } id_ = uuid.uuid4().hex resp_body = { "tenant": { "name": "tenantX", "enabled": True, "id": id_, "description": "Like tenant 9, but better.", "extravalue01": "metadata01", } } self.stub_url('POST', ['tenants'], json=resp_body) tenant = self.client.tenants.create( req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled'], extravalue01=req_body['tenant']['extravalue01'], name="don't overwrite priors") self.assertIsInstance(tenant, tenants.Tenant) self.assertEqual(tenant.id, id_) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "Like tenant 9, but better.") self.assertEqual(tenant.extravalue01, "metadata01") self.assertRequestBodyIs(json=req_body) def test_duplicate_create(self): req_body = { "tenant": { "name": "tenantX", "description": "The duplicate tenant.", "enabled": True }, } resp_body = { "error": { "message": "Conflict occurred attempting to store project.", "code": 409, "title": "Conflict", } } self.stub_url('POST', ['tenants'], status_code=409, json=resp_body) def create_duplicate_tenant(): self.client.tenants.create(req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled']) self.assertRaises(exceptions.Conflict, create_duplicate_tenant) def test_delete(self): self.stub_url('DELETE', ['tenants', self.ADMIN_ID], status_code=204) self.client.tenants.delete(self.ADMIN_ID) def test_get(self): resp = {'tenant': self.TEST_TENANTS['tenants']['values'][2]} self.stub_url('GET', ['tenants', self.ADMIN_ID], json=resp) t = self.client.tenants.get(self.ADMIN_ID) self.assertIsInstance(t, tenants.Tenant) self.assertEqual(t.id, self.ADMIN_ID) self.assertEqual(t.name, 'admin') def test_list(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list() [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_limit(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(limit=1) self.assertQueryStringIs('limit=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_marker(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(marker=1) self.assertQueryStringIs('marker=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_limit_marker(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(limit=1, marker=1) self.assertQueryStringIs('marker=1&limit=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_update(self): req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, "extravalue01": "metadataChanged", # "extraname": "dontoverwrite!", }, } resp_body = { "tenant": { "name": "tenantX", "enabled": False, "id": self.EXTRAS_ID, "description": "I changed you!", "extravalue01": "metadataChanged", }, } self.stub_url('POST', ['tenants', self.EXTRAS_ID], json=resp_body) tenant = self.client.tenants.update( req_body['tenant']['id'], req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled'], extravalue01=req_body['tenant']['extravalue01'], name="don't overwrite priors") self.assertIsInstance(tenant, tenants.Tenant) self.assertRequestBodyIs(json=req_body) self.assertEqual(tenant.id, self.EXTRAS_ID) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "I changed you!") self.assertFalse(tenant.enabled) self.assertEqual(tenant.extravalue01, "metadataChanged") def test_update_empty_description(self): req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "", "enabled": False, }, } resp_body = { "tenant": { "name": "tenantX", "enabled": False, "id": self.EXTRAS_ID, "description": "", }, } self.stub_url('POST', ['tenants', self.EXTRAS_ID], json=resp_body) tenant = self.client.tenants.update(req_body['tenant']['id'], req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled']) self.assertIsInstance(tenant, tenants.Tenant) self.assertRequestBodyIs(json=req_body) self.assertEqual(tenant.id, self.EXTRAS_ID) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "") self.assertFalse(tenant.enabled) def test_add_user(self): self.stub_url('PUT', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.tenants.add_user(self.EXTRAS_ID, 'foo', 'barrr') def test_remove_user(self): self.stub_url('DELETE', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.tenants.remove_user(self.EXTRAS_ID, 'foo', 'barrr') def test_tenant_add_user(self): self.stub_url('PUT', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, }, } # make tenant object with manager tenant = self.client.tenants.resource_class(self.client.tenants, req_body['tenant']) tenant.add_user('foo', 'barrr') self.assertIsInstance(tenant, tenants.Tenant) def test_tenant_remove_user(self): self.stub_url('DELETE', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, }, } # make tenant object with manager tenant = self.client.tenants.resource_class(self.client.tenants, req_body['tenant']) tenant.remove_user('foo', 'barrr') self.assertIsInstance(tenant, tenants.Tenant) def test_tenant_list_users(self): tenant_id = uuid.uuid4().hex user_id1 = uuid.uuid4().hex user_id2 = uuid.uuid4().hex tenant_resp = { 'tenant': { 'name': uuid.uuid4().hex, 'enabled': True, 'id': tenant_id, 'description': 'test tenant', } } users_resp = { 'users': { 'values': [ { 'email': uuid.uuid4().hex, 'enabled': True, 'id': user_id1, 'name': uuid.uuid4().hex, }, { 'email': uuid.uuid4().hex, 'enabled': True, 'id': user_id2, 'name': uuid.uuid4().hex, }, ] } } self.stub_url('GET', ['tenants', tenant_id], json=tenant_resp) self.stub_url('GET', ['tenants', tenant_id, 'users'], json=users_resp) tenant = self.client.tenants.get(tenant_id) user_objs = tenant.list_users() for u in user_objs: self.assertIsInstance(u, users.User) self.assertEqual(set([user_id1, user_id2]), set([u.id for u in user_objs])) def test_list_tenants_use_admin_url(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list() self.assertEqual(self.TEST_URL + '/tenants', self.requests_mock.last_request.url) [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] self.assertEqual(len(self.TEST_TENANTS['tenants']['values']), len(tenant_list)) def test_list_tenants_fallback_to_auth_url(self): new_auth_url = 'http://keystone.test:5000/v2.0' token = fixture.V2Token(token_id=self.TEST_TOKEN, user_name=self.TEST_USER, user_id=self.TEST_USER_ID) self.stub_auth(base_url=new_auth_url, json=token) self.stub_url('GET', ['tenants'], base_url=new_auth_url, json=self.TEST_TENANTS) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username=self.TEST_USER, auth_url=new_auth_url, password=uuid.uuid4().hex) self.assertIsNone(c.management_url) tenant_list = c.tenants.list() [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] self.assertEqual(len(self.TEST_TENANTS['tenants']['values']), len(tenant_list)) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_ec2.py0000664000175000017500000000730113644407055026136 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import ec2 class EC2Tests(utils.ClientTestCase): def test_create(self): user_id = 'usr' tenant_id = 'tnt' req_body = { "tenant_id": tenant_id, } resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('POST', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) cred = self.client.ec2.create(user_id, tenant_id) self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') self.assertRequestBodyIs(json=req_body) def test_get(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2', 'access'], json=resp_body) cred = self.client.ec2.get(user_id, 'access') self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_list(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credentials": { "values": [ { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, }, { "access": "another", "secret": "key", "tenant_id": tenant_id, "created": "12/12/31", "enabled": True, } ] } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) creds = self.client.ec2.list(user_id) self.assertEqual(len(creds), 2) cred = creds[0] self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_delete(self): user_id = 'usr' access = 'access' self.stub_url('DELETE', ['users', user_id, 'credentials', 'OS-EC2', access], status_code=204) self.client.ec2.delete(user_id, access) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_roles.py0000664000175000017500000001035013644407055026607 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import roles class RoleTests(utils.ClientTestCase): def setUp(self): super(RoleTests, self).setUp() self.ADMIN_ROLE_ID = uuid.uuid4().hex self.MEMBER_ROLE_ID = uuid.uuid4().hex self.TEST_ROLES = { "roles": { "values": [ { "name": "admin", "id": self.ADMIN_ROLE_ID, }, { "name": "member", "id": self.MEMBER_ROLE_ID, } ], }, } def test_create(self): req_body = { "role": { "name": "sysadmin", } } role_id = uuid.uuid4().hex resp_body = { "role": { "name": "sysadmin", "id": role_id, } } self.stub_url('POST', ['OS-KSADM', 'roles'], json=resp_body) role = self.client.roles.create(req_body['role']['name']) self.assertRequestBodyIs(json=req_body) self.assertIsInstance(role, roles.Role) self.assertEqual(role.id, role_id) self.assertEqual(role.name, req_body['role']['name']) def test_delete(self): self.stub_url('DELETE', ['OS-KSADM', 'roles', self.ADMIN_ROLE_ID], status_code=204) self.client.roles.delete(self.ADMIN_ROLE_ID) def test_get(self): self.stub_url('GET', ['OS-KSADM', 'roles', self.ADMIN_ROLE_ID], json={'role': self.TEST_ROLES['roles']['values'][0]}) role = self.client.roles.get(self.ADMIN_ROLE_ID) self.assertIsInstance(role, roles.Role) self.assertEqual(role.id, self.ADMIN_ROLE_ID) self.assertEqual(role.name, 'admin') def test_list(self): self.stub_url('GET', ['OS-KSADM', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.list() [self.assertIsInstance(r, roles.Role) for r in role_list] def test_roles_for_user(self): self.stub_url('GET', ['users', 'foo', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.roles_for_user('foo') [self.assertIsInstance(r, roles.Role) for r in role_list] def test_roles_for_user_tenant(self): self.stub_url('GET', ['tenants', 'barrr', 'users', 'foo', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.roles_for_user('foo', 'barrr') [self.assertIsInstance(r, roles.Role) for r in role_list] def test_add_user_role(self): self.stub_url('PUT', ['users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.add_user_role('foo', 'barrr') def test_add_user_role_tenant(self): id_ = uuid.uuid4().hex self.stub_url('PUT', ['tenants', id_, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.add_user_role('foo', 'barrr', id_) def test_remove_user_role(self): self.stub_url('DELETE', ['users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.remove_user_role('foo', 'barrr') def test_remove_user_role_tenant(self): id_ = uuid.uuid4().hex self.stub_url('DELETE', ['tenants', id_, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.remove_user_role('foo', 'barrr', id_) python-keystoneclient-4.0.0/keystoneclient/tests/unit/v2_0/test_tokens.py0000664000175000017500000002010313644407055026763 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import fixture from keystoneclient import access from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import tokens class TokenTests(utils.ClientTestCase): def test_delete(self): id_ = uuid.uuid4().hex self.stub_url('DELETE', ['tokens', id_], status_code=204) self.client.tokens.delete(id_) def test_user_password(self): token_fixture = fixture.V2Token(user_name=self.TEST_USER) self.stub_auth(json=token_fixture) password = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(username=self.TEST_USER, password=password) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) req_body = { 'auth': { 'passwordCredentials': { 'username': self.TEST_USER, 'password': password, } } } self.assertRequestBodyIs(json=req_body) def test_with_token_id(self): token_fixture = fixture.V2Token() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) req_body = { 'auth': { 'token': { 'id': token_id, } } } self.assertRequestBodyIs(json=req_body) def test_without_auth_params(self): self.assertRaises(ValueError, self.client.tokens.authenticate) self.assertRaises(ValueError, self.client.tokens.authenticate, tenant_id=uuid.uuid4().hex) def test_with_tenant_id(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex tenant_id = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id, tenant_id=tenant_id) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) tenant_data = {'id': token_fixture.tenant_id, 'name': token_fixture.tenant_name} self.assertEqual(tenant_data, token_ref.tenant) req_body = { 'auth': { 'token': { 'id': token_id, }, 'tenantId': tenant_id } } self.assertRequestBodyIs(json=req_body) def test_with_tenant_name(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex tenant_name = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id, tenant_name=tenant_name) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) tenant_data = {'id': token_fixture.tenant_id, 'name': token_fixture.tenant_name} self.assertEqual(tenant_data, token_ref.tenant) req_body = { 'auth': { 'token': { 'id': token_id, }, 'tenantName': tenant_name } } self.assertRequestBodyIs(json=req_body) def test_authenticate_use_admin_url(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_ref = self.client.tokens.authenticate(token=uuid.uuid4().hex) self.assertEqual(self.TEST_URL + '/tokens', self.requests_mock.last_request.url) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) def test_authenticate_fallback_to_auth_url(self): new_auth_url = 'http://keystone.test:5000/v2.0' token_fixture = fixture.V2Token() self.stub_auth(base_url=new_auth_url, json=token_fixture) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username=self.TEST_USER, auth_url=new_auth_url, password=uuid.uuid4().hex) self.assertIsNone(c.management_url) token_ref = c.tokens.authenticate(token=uuid.uuid4().hex) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) def test_validate_token(self): id_ = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=id_) self.stub_url('GET', ['tokens', id_], json=token_fixture) token_data = self.client.tokens.get_token_data(id_) self.assertEqual(token_fixture, token_data) token_ref = self.client.tokens.validate(id_) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(id_, token_ref.id) def test_validate_token_invalid_token(self): # If the token is invalid, typically a NotFound is raised. id_ = uuid.uuid4().hex # The server is expected to return 404 if the token is invalid. self.stub_url('GET', ['tokens', id_], status_code=404) self.assertRaises(exceptions.NotFound, self.client.tokens.get_token_data, id_) self.assertRaises(exceptions.NotFound, self.client.tokens.validate, id_) def test_validate_token_access_info_with_token_id(self): # Can validate a token passing a string token ID. token_id = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=token_id) self.stub_url('GET', ['tokens', token_id], json=token_fixture) access_info = self.client.tokens.validate_access_info(token_id) self.assertIsInstance(access_info, access.AccessInfoV2) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_access_info_with_access_info(self): # Can validate a token passing an access info. token_id = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=token_id) self.stub_url('GET', ['tokens', token_id], json=token_fixture) token = access.AccessInfo.factory(body=token_fixture) access_info = self.client.tokens.validate_access_info(token) self.assertIsInstance(access_info, access.AccessInfoV2) self.assertEqual(token_id, access_info.auth_token) def test_get_revoked(self): sample_revoked_response = {'signed': '-----BEGIN CMS-----\nMIIB...'} self.stub_url('GET', ['tokens', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked() self.assertEqual(sample_revoked_response, resp) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_https.py0000664000175000017500000001006513644407055026062 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock import requests from keystoneclient import httpclient from keystoneclient.tests.unit import utils FAKE_RESPONSE = utils.test_response(json={'hi': 'there'}) REQUEST_URL = 'https://127.0.0.1:5000/hi' RESPONSE_BODY = '{"hi": "there"}' def get_client(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test", cacert="ca.pem", cert=('cert.pem', "key.pem")) return cl def get_authed_client(): cl = get_client() cl.management_url = "https://127.0.0.1:5000" cl.auth_token = "token" return cl class ClientTest(utils.TestCase): @mock.patch.object(requests, 'request') def test_get(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() with self.deprecations.expect_deprecations_here(): resp, body = cl.get("/hi") # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'GET') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') # Automatic JSON parsing self.assertEqual(body, {"hi": "there"}) @mock.patch.object(requests, 'request') def test_post(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'POST') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['data'], '[1, 2, 3]') self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') @mock.patch.object(requests, 'request') def test_post_auth(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient( username="username", password="password", project_id="tenant", auth_url="auth_test", cacert="ca.pem", cert=('cert.pem', 'key.pem')) cl.management_url = "https://127.0.0.1:5000" cl.auth_token = "token" with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'POST') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['data'], '[1, 2, 3]') self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') python-keystoneclient-4.0.0/keystoneclient/tests/unit/utils.py0000664000175000017500000001437613644407055025032 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 logging import sys import uuid import fixtures from oslo_serialization import jsonutils import requests import requests_mock from requests_mock.contrib import fixture from six.moves.urllib import parse as urlparse import testscenarios import testtools from keystoneclient.tests.unit import client_fixtures 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.deprecations = self.useFixture(client_fixtures.Deprecations()) self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) 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['Content-Type'] = 'application/json' if parts: url = '/'.join([p.strip('/') for p in [base_url] + parts]) else: url = base_url url = url.replace("/?", "?") 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 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 test_response(**kwargs): r = requests.Request(method='GET', url='http://localhost:5000').prepare() return requests_mock.create_response(r, **kwargs) class DisableModuleFixture(fixtures.Fixture): """A fixture to provide support for unloading/disabling modules.""" def __init__(self, module, *args, **kw): super(DisableModuleFixture, self).__init__(*args, **kw) self.module = module self._finders = [] self._cleared_modules = {} def tearDown(self): super(DisableModuleFixture, self).tearDown() for finder in self._finders: sys.meta_path.remove(finder) sys.modules.update(self._cleared_modules) def clear_module(self): cleared_modules = {} for fullname in list(sys.modules): if (fullname == self.module or fullname.startswith(self.module + '.')): cleared_modules[fullname] = sys.modules.pop(fullname) return cleared_modules def setUp(self): """Ensure ImportError for the specified module.""" super(DisableModuleFixture, self).setUp() # Clear 'module' references in sys.modules self._cleared_modules.update(self.clear_module()) finder = NoModuleFinder(self.module) self._finders.append(finder) sys.meta_path.insert(0, finder) class ClientTestCaseMixin(testscenarios.WithScenarios): client_fixture_class = None data_fixture_class = None def setUp(self): super(ClientTestCaseMixin, self).setUp() self.data_fixture = None self.client_fixture = None self.client = None if self.client_fixture_class: fix = self.client_fixture_class(self.requests_mock, self.deprecations) self.client_fixture = self.useFixture(fix) self.client = self.client_fixture.client self.TEST_USER_ID = self.client_fixture.user_id if self.data_fixture_class: fix = self.data_fixture_class(self.requests_mock) self.data_fixture = self.useFixture(fix) class NoModuleFinder(object): """Disallow further imports of 'module'.""" def __init__(self, module): self.module = module def find_module(self, fullname, path): if fullname == self.module or fullname.startswith(self.module + '.'): raise ImportError python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_keyring.py0000664000175000017500000001777713644407055026411 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import mock from oslo_utils import timeutils from keystoneclient import access from keystoneclient import httpclient from keystoneclient.tests.unit import utils from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient import utils as client_utils try: import keyring # noqa import pickle # noqa except ImportError: keyring = None PROJECT_SCOPED_TOKEN = client_fixtures.project_scoped_token() # These mirror values from PROJECT_SCOPED_TOKEN USERNAME = 'exampleuser' AUTH_URL = 'http://public.com:5000/v2.0' TOKEN = '04c7d5ffaeef485f9dc69c06db285bdb' PASSWORD = 'password' TENANT = 'tenant' TENANT_ID = 'tenant_id' class KeyringTest(utils.TestCase): def setUp(self): if keyring is None: self.skipTest( 'optional package keyring or pickle is not installed') class MemoryKeyring(keyring.backend.KeyringBackend): """A Simple testing keyring. This class supports stubbing an initial password to be returned by setting password, and allows easy password and key retrieval. Also records if a password was retrieved. """ def __init__(self): self.key = None self.password = None self.fetched = False self.get_password_called = False self.set_password_called = False def supported(self): return 1 def get_password(self, service, username): self.get_password_called = True key = username + '@' + service # make sure we don't get passwords crossed if one is enforced. if self.key and self.key != key: return None if self.password: self.fetched = True return self.password def set_password(self, service, username, password): self.set_password_called = True self.key = username + '@' + service self.password = password super(KeyringTest, self).setUp() self.memory_keyring = MemoryKeyring() keyring.set_keyring(self.memory_keyring) def test_no_keyring_key(self): """Test case when no keyring set. Ensure that if we don't have use_keyring set in the client that the keyring is never accessed. """ # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL) # stub and check that a new token is received method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # make sure that we never touched the keyring self.assertFalse(self.memory_keyring.get_password_called) self.assertFalse(self.memory_keyring.set_password_called) def test_build_keyring_key(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL) keyring_key = cl._build_keyring_key(auth_url=AUTH_URL, username=USERNAME, tenant_name=TENANT, tenant_id=TENANT_ID, token=TOKEN) self.assertEqual(keyring_key, '%s/%s/%s/%s/%s' % (AUTH_URL, TENANT_ID, TENANT, TOKEN, USERNAME)) def test_set_and_get_keyring_expired(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # set an expired token into the keyring auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN) expired = timeutils.utcnow() - datetime.timedelta(minutes=30) auth_ref['token']['expires'] = client_utils.isotime(expired) self.memory_keyring.password = pickle.dumps(auth_ref) # stub and check that a new token is received, so not using expired method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # check that a value was returned from the keyring self.assertTrue(self.memory_keyring.fetched) # check that the new token has been loaded into the keyring new_auth_ref = pickle.loads(self.memory_keyring.password) self.assertEqual(new_auth_ref['token']['expires'], PROJECT_SCOPED_TOKEN['access']['token']['expires']) def test_get_keyring(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # set an token into the keyring auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN) future = timeutils.utcnow() + datetime.timedelta(minutes=30) auth_ref['token']['expires'] = client_utils.isotime(future) self.memory_keyring.password = pickle.dumps(auth_ref) # don't stub get_raw_token so will fail if authenticate happens self.assertTrue(cl.authenticate()) self.assertTrue(self.memory_keyring.fetched) def test_set_keyring(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # stub and check that a new token is received method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # we checked the keyring, but we didn't find anything self.assertTrue(self.memory_keyring.get_password_called) self.assertFalse(self.memory_keyring.fetched) # check that the new token has been loaded into the keyring self.assertTrue(self.memory_keyring.set_password_called) new_auth_ref = pickle.loads(self.memory_keyring.password) self.assertEqual(new_auth_ref.auth_token, TOKEN) self.assertEqual(new_auth_ref['token'], PROJECT_SCOPED_TOKEN['access']['token']) self.assertEqual(new_auth_ref.username, USERNAME) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/0000775000175000017500000000000013644407143024244 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_access.py0000664000175000017500000000456413644407055027131 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 plugin from keystoneclient import access from keystoneclient.auth.identity import access as access_plugin from keystoneclient import session from keystoneclient.tests.unit import utils class AccessInfoPluginTests(utils.TestCase): def setUp(self): super(AccessInfoPluginTests, self).setUp() with self.deprecations.expect_deprecations_here(): 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.AccessInfo.factory(body=token, auth_token=self.auth_token) with self.deprecations.expect_deprecations_here(): return access_plugin.AccessInfoPlugin(auth_ref, **kwargs) def test_auth_ref(self): plugin = self._plugin() self.assertEqual(self.TEST_ROOT_URL, plugin.get_endpoint(self.session, service_type='identity', interface='public')) self.assertEqual(self.auth_token, plugin.get_token(session)) def test_auth_url(self): auth_url = 'http://keystone.test.url' plug = self._plugin(auth_url=auth_url) self.assertEqual(auth_url, plug.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) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_default_cli.py0000664000175000017500000000623013644407055030133 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock from keystoneclient.auth.identity.generic import cli from keystoneclient import exceptions from keystoneclient.tests.unit import utils class DefaultCliTests(utils.TestCase): def setUp(self): super(DefaultCliTests, self).setUp() self.deprecations.expect_deprecations() def new_plugin(self, argv): parser = argparse.ArgumentParser() cli.DefaultCLI.register_argparse_arguments(parser) opts = parser.parse_args(argv) return cli.DefaultCLI.load_from_argparse_arguments(opts) def test_endpoint_override(self): password = uuid.uuid4().hex url = uuid.uuid4().hex p = self.new_plugin(['--os-auth-url', 'url', '--os-endpoint', url, '--os-password', password]) self.assertEqual(url, p.get_endpoint(None)) self.assertEqual(password, p._password) def test_token_only_override(self): self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-token', uuid.uuid4().hex]) def test_token_endpoint_override(self): token = uuid.uuid4().hex endpoint = uuid.uuid4().hex p = self.new_plugin(['--os-endpoint', endpoint, '--os-token', token]) self.assertEqual(endpoint, p.get_endpoint(None)) self.assertEqual(token, p.get_token(None)) def test_no_auth_url(self): exc = self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-username', uuid.uuid4().hex]) self.assertIn('auth-url', str(exc)) @mock.patch('sys.stdin', autospec=True) @mock.patch('getpass.getpass') def test_prompt_password(self, mock_getpass, mock_stdin): password = uuid.uuid4().hex mock_stdin.isatty = lambda: True mock_getpass.return_value = password p = self.new_plugin(['--os-auth-url', uuid.uuid4().hex, '--os-username', uuid.uuid4().hex]) self.assertEqual(password, p._password) @mock.patch('sys.stdin', autospec=True) @mock.patch('getpass.getpass') def test_prompt_no_password(self, mock_getpass, mock_stdin): mock_stdin.isatty = lambda: True mock_getpass.return_value = '' exc = self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-auth-url', uuid.uuid4().hex, '--os-username', uuid.uuid4().hex]) self.assertIn('password', str(exc)) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/__init__.py0000664000175000017500000000000013644407055026345 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_identity_v2.py0000664000175000017500000003162413644407055030125 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 copy import uuid import mock from keystoneclient.auth.identity import v2 from keystoneclient import exceptions from keystoneclient import session from keystoneclient.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.deprecations.expect_deprecations() self.TEST_RESPONSE_DICT = { "access": { "token": { "expires": "2999-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) 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) 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.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) 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') 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) with self.deprecations.expect_deprecations_here(): self.assertEqual('token1', s.get_token()) self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) a.invalidate() with self.deprecations.expect_deprecations_here(): 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) with self.deprecations.expect_deprecations_here(): 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) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() v2.Password.register_argparse_arguments(parser) username = uuid.uuid4().hex auth_url = uuid.uuid4().hex tenant_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-tenant-id', tenant_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = v2.Password.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin.username) self.assertEqual(tenant_id, plugin.tenant_id) self.assertEqual(password, plugin.password) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_conf.py0000664000175000017500000001565613644407055026621 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth import base from keystoneclient.auth import conf from keystoneclient.auth.identity import v2 as v2_auth from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient import exceptions from keystoneclient.tests.unit.auth import utils class ConfTests(utils.TestCase): def setUp(self): super(ConfTests, self).setUp() self.deprecations.expect_deprecations() 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. conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) def test_loading_v2(self): section = 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) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(v2_auth.Password.get_options(), group=section) self.conf_fixture.config(auth_plugin=self.V2PASS, username=username, password=password, trust_id=trust_id, tenant_id=tenant_id, group=section) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) 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 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) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(v3_auth.Token.get_options(), group=section) self.conf_fixture.config(auth_plugin=self.V3TOKEN, token=token, trust_id=trust_id, project_id=project_id, project_domain_name=project_domain_name, group=section) a = conf.load_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_plugin = uuid.uuid4().hex self.conf_fixture.config(auth_plugin=auth_plugin, group=self.GROUP) e = self.assertRaises(exceptions.NoMatchingPlugin, conf.load_from_conf_options, self.conf_fixture.conf, self.GROUP) self.assertEqual(auth_plugin, e.name) def test_loading_with_no_data(self): self.assertIsNone(conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP)) @mock.patch('stevedore.DriverManager') def test_other_params(self, m): m.return_value = utils.MockManager(utils.MockPlugin) driver_name = uuid.uuid4().hex self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=self.GROUP) self.conf_fixture.config(auth_plugin=driver_name, group=self.GROUP, **self.TEST_VALS) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) m.assert_called_once_with(namespace=base.PLUGIN_NAMESPACE, name=driver_name, invoke_on_load=False) @utils.mock_plugin def test_same_section(self, m): self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.config(auth_plugin=uuid.uuid4().hex, group=self.GROUP, **self.TEST_VALS) a = conf.load_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) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=section) self.conf_fixture.config(group=section, auth_plugin=uuid.uuid4().hex, **self.TEST_VALS) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) def test_plugins_are_all_opts(self): manager = stevedore.ExtensionManager(base.PLUGIN_NAMESPACE, invoke_on_load=False, propagate_map_exceptions=True) def inner(driver): for p in driver.plugin.get_options(): self.assertIsInstance(p, cfg.Opt) manager.map(inner) def test_get_common(self): opts = conf.get_common_conf_options() for opt in opts: self.assertIsInstance(opt, cfg.Opt) self.assertEqual(2, len(opts)) def test_get_named(self): loaded_opts = conf.get_plugin_options('v2password') plugin_opts = v2_auth.Password.get_options() self.assertEqual(plugin_opts, loaded_opts) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_loading.py0000664000175000017500000000265013644407055027277 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.tests.unit.auth import utils class TestOtherLoading(utils.TestCase): 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.MockPlugin.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']) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_auth.py0000664000175000017500000000301613644407055026620 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import auth from keystoneclient.auth import identity from keystoneclient.tests.unit.auth import utils class AuthTests(utils.TestCase): def test_plugin_names_in_available(self): with self.deprecations.expect_deprecations_here(): plugins = auth.get_available_plugin_names() for p in ('password', 'v2password', 'v3password', 'token', 'v2token', 'v3token'): self.assertIn(p, plugins) def test_plugin_classes_in_available(self): with self.deprecations.expect_deprecations_here(): plugins = auth.get_available_plugin_classes() self.assertIs(plugins['password'], identity.Password) self.assertIs(plugins['v2password'], identity.V2Password) self.assertIs(plugins['v3password'], identity.V3Password) self.assertIs(plugins['token'], identity.Token) self.assertIs(plugins['v2token'], identity.V2Token) self.assertIs(plugins['v3token'], identity.V3Token) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_identity_v3.py0000664000175000017500000005263313644407055030131 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 copy import uuid from keystoneauth1 import fixture import mock from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import base as v3_base from keystoneclient import client from keystoneclient import exceptions from keystoneclient import session from keystoneclient.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" }] def setUp(self): super(V3IdentityPlugin, self).setUp() self.TEST_DISCOVERY_RESPONSE = { 'versions': {'values': [fixture.V3Discovery(self.TEST_URL)]}} self.deprecations.expect_deprecations() self.TEST_RESPONSE_DICT = { "token": { "methods": [ "token", "password" ], "expires_at": "2999-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 }, } 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) 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_unscoped(self): del self.TEST_RESPONSE_DICT['token']['catalog'] del self.TEST_RESPONSE_DICT['token']['project'] self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url(method="GET", json=self.TEST_DISCOVERY_RESPONSE) test_user_id = self.TEST_RESPONSE_DICT['token']['user']['id'] self.stub_url(method="GET", json=self.TEST_PROJECTS_RESPONSE, parts=['users', test_user_id, 'projects']) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) cs = client.Client(session=s) # As a sanity check on the auth_ref, make sure client has the # proper user id, that it fetches the right project response self.assertEqual(test_user_id, a.auth_ref.user_id) t = cs.projects.list(user=a.auth_ref.user_id) self.assertEqual(2, len(t)) 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) 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) 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.AccessInfo.factory(body=d) s = session.Session(auth=a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) self.assertEqual(a.auth_ref['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.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') 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') 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_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) with self.deprecations.expect_deprecations_here(): self.assertEqual('token1', s.get_token()) self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) a.invalidate() with self.deprecations.expect_deprecations_here(): 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) with self.deprecations.expect_deprecations_here(): 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_auth_headers() 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) with self.deprecations.expect_deprecations_here(): 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({}, body['auth']['scope']['unscoped']) 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) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() v3.Password.register_argparse_arguments(parser) username = uuid.uuid4().hex user_domain_id = uuid.uuid4().hex auth_url = uuid.uuid4().hex project_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-user-domain-id', user_domain_id, '--os-project-id', project_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = v3.Password.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin.auth_methods[0].username) self.assertEqual(project_id, plugin.project_id) self.assertEqual(user_domain_id, plugin.auth_methods[0].user_domain_id) self.assertEqual(password, plugin.auth_methods[0].password) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_identity_common.py0000664000175000017500000004230713644407055031066 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 datetime import uuid from keystoneauth1 import fixture from keystoneauth1 import plugin import mock from oslo_utils import timeutils import six from keystoneclient import access from keystoneclient.auth import base from keystoneclient.auth import identity from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils @six.add_metaclass(abc.ABCMeta) class CommonIdentityTests(object): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_COMPUTE_PUBLIC = 'http://nova/novapi/public' TEST_COMPUTE_INTERNAL = 'http://nova/novapi/internal' TEST_COMPUTE_ADMIN = 'http://nova/novapi/admin' TEST_PASS = uuid.uuid4().hex def setUp(self): super(CommonIdentityTests, self).setUp() self.deprecations.expect_deprecations() 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. """ pass @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. """ pass 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_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) # now either of the two plugins I use, it should not cause a second # request to the discovery url. s = session.Session() 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) 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. 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_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_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 = timeutils.utcnow() - datetime.timedelta(minutes=20) expired_token = self.get_auth_data(expires=expires) expired_auth_ref = access.AccessInfo.factory(body=expired_token) body = 'SUCCESS' self.stub_url('GET', ['path'], base_url=self.TEST_COMPUTE_ADMIN, text=body) 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)) class V3(CommonIdentityTests, utils.TestCase): @property def version(self): return 'v3' def get_auth_data(self, **kwargs): 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) 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) return identity.V2Password(**kwargs) def get_auth_data(self, **kwargs): 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) 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 setUp(self): super(CatalogHackTests, self).setUp() self.deprecations.expect_deprecations() 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) 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_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) class GenericPlugin(base.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() with self.deprecations.expect_deprecations_here(): 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) with self.deprecations.expect_deprecations_here(): 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 with mock.patch.object(self.session.session, 'request') as mocked: mocked.return_value = utils.test_response(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. mocked.assert_called_once_with('GET', self.auth.url('prefix'), headers=mock.ANY, allow_redirects=False, cert=self.auth.cert, verify=False) 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)) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_token_endpoint.py0000664000175000017500000000446613644407055030711 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth import token_endpoint from keystoneclient import session from keystoneclient.tests.unit import utils class TokenEndpointTest(utils.TestCase): TEST_TOKEN = 'aToken' TEST_URL = 'http://server/prefix' def setUp(self): super(TokenEndpointTest, self).setUp() self.deprecations.expect_deprecations() 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_options(self): opt_names = [opt.name for opt in token_endpoint.Token.get_options()] self.assertThat(opt_names, matchers.HasLength(2)) self.assertIn('token', opt_names) self.assertIn('endpoint', opt_names) 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)) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_token.py0000664000175000017500000000352413644407055027003 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.identity.generic import token from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import token as v3_token from keystoneclient.tests.unit.auth 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_options(self): opts = [o.name for o in self.PLUGIN_CLASS.get_options()] allowed_opts = ['token', 'domain-id', 'domain-name', 'tenant-id', 'tenant-name', 'project-id', 'project-name', 'project-domain-id', 'project-domain-name', 'trust-id', 'auth-url'] self.assertEqual(set(allowed_opts), set(opts)) self.assertEqual(len(allowed_opts), len(opts)) def test_symbols(self): self.assertIs(v3.Token, v3_token.Token) self.assertIs(v3.TokenMethod, v3_token.TokenMethod) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_cli.py0000664000175000017500000001542513644407055026435 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_config import cfg from keystoneclient.auth import base from keystoneclient.auth import cli from keystoneclient.tests.unit.auth import utils class TesterPlugin(base.BaseAuthPlugin): def get_token(self, *args, **kwargs): return None @classmethod def get_options(cls): # NOTE(jamielennox): this is kind of horrible. If you specify this as # a deprecated_name= value it will convert - to _ which is not what we # want for a CLI option. deprecated = [cfg.DeprecatedOpt('test-other')] return [ cfg.StrOpt('test-opt', help='tester', deprecated_opts=deprecated) ] class CliTests(utils.TestCase): def setUp(self): super(CliTests, self).setUp() self.deprecations.expect_deprecations() 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 = cli.register_argparse_arguments(self.p, []) self.assertIsNone(ret) self.assertIn('--os-auth-plugin', self.p.format_usage()) def test_load_with_nothing(self): cli.register_argparse_arguments(self.p, []) opts = self.p.parse_args([]) self.assertIsNone(cli.load_from_argparse_arguments(opts)) @utils.mock_plugin def test_basic_params_added(self, m): name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] ret = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, ret) 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-plugin', name, '--os-a-int', str(self.a_int), '--os-a-float', str(self.a_float), '--os-a-bool', str(self.a_bool)] klass = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) a = cli.load_from_argparse_arguments(opts) self.assertTestVals(a) self.assertEqual(name, opts.os_auth_plugin) 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-plugin', name, '--os-a-float', str(self.a_float)] klass = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) a = cli.load_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 = cli.register_argparse_arguments(self.p, [], default=name) self.assertIs(utils.MockPlugin, klass) 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-plugin', name] klass = cli.register_argparse_arguments(self.p, argv, default=default) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @utils.mock_plugin def test_with_default_type_value(self, m): klass = cli.register_argparse_arguments(self.p, [], default=utils.MockPlugin) self.assertIs(utils.MockPlugin, klass) 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 TestPlugin(object): pass name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] klass = cli.register_argparse_arguments(self.p, argv, default=TestPlugin) self.assertIs(utils.MockPlugin, klass) 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 = cli.register_argparse_arguments(self.p, [], default=name) opts = self.p.parse_args([]) a = klass.load_from_argparse_arguments(opts) self.assertEqual(val, a['a_str']) def test_deprecated_cli_options(self): TesterPlugin.register_argparse_arguments(self.p) 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): TesterPlugin.register_argparse_arguments(self.p) 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}): TesterPlugin.register_argparse_arguments(self.p) 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}): TesterPlugin.register_argparse_arguments(self.p) opts = self.p.parse_args([]) self.assertEqual(val1, opts.os_test_opt) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/utils.py0000664000175000017500000001460713644407055025770 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from keystoneauth1 import fixture import mock from oslo_config import cfg from keystoneclient import access from keystoneclient.auth import base from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils class MockPlugin(base.BaseAuthPlugin): INT_DESC = 'test int' FLOAT_DESC = 'test float' BOOL_DESC = 'test bool' STR_DESC = 'test str' STR_DEFAULT = uuid.uuid4().hex def __init__(self, **kwargs): self._data = kwargs def __getitem__(self, key): """Get the data of the key.""" return self._data[key] def get_token(self, *args, **kwargs): return 'aToken' def get_endpoint(self, *args, **kwargs): return 'http://test' @classmethod def get_options(cls): return [ cfg.IntOpt('a-int', default='3', help=cls.INT_DESC), cfg.BoolOpt('a-bool', help=cls.BOOL_DESC), cfg.FloatOpt('a-float', help=cls.FLOAT_DESC), cfg.StrOpt('a-str', help=cls.STR_DESC, default=cls.STR_DEFAULT), ] class MockManager(object): def __init__(self, driver): self.driver = driver def mock_plugin(f): @functools.wraps(f) def inner(*args, **kwargs): with mock.patch.object(base, 'get_plugin_class') as m: m.return_value = MockPlugin args = list(args) + [m] return f(*args, **kwargs) return inner 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]) 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.deprecations.expect_deprecations() 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_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() 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() python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_password.py0000664000175000017500000000715513644407055027531 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 mock from keystoneclient.auth.identity.generic import password from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import password as v3_password from keystoneclient.tests.unit.auth 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_options(self): opts = [o.name for o in self.PLUGIN_CLASS.get_options()] allowed_opts = ['username', 'user-domain-id', 'user-domain-name', 'user-id', 'password', 'domain-id', 'domain-name', 'tenant-id', 'tenant-name', 'project-id', 'project-name', 'project-domain-id', 'project-domain-name', 'trust-id', 'auth-url'] self.assertEqual(set(allowed_opts), set(opts)) self.assertEqual(len(allowed_opts), len(opts)) def test_symbols(self): self.assertIs(v3.Password, v3_password.Password) self.assertIs(v3.PasswordMethod, v3_password.PasswordMethod) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() self.PLUGIN_CLASS.register_argparse_arguments(parser) username = uuid.uuid4().hex user_domain_id = uuid.uuid4().hex auth_url = uuid.uuid4().hex project_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-user-domain-id', user_domain_id, '--os-project-id', project_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = self.PLUGIN_CLASS.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(project_id, plugin._project_id) self.assertEqual(user_domain_id, plugin._user_domain_id) self.assertEqual(password, plugin._password) python-keystoneclient-4.0.0/keystoneclient/tests/unit/auth/test_identity_v3_federated.py0000664000175000017500000000735113644407055032131 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from keystoneauth1 import fixture from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient import session from keystoneclient.tests.unit import utils class TesterFederationPlugin(v3.FederatedBaseAuth): 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.AccessInfo.factory(resp=resp, body=resp.json()) class V3FederatedPlugin(utils.TestCase): AUTH_URL = 'http://keystone/v3' def setUp(self): super(V3FederatedPlugin, self).setUp() self.deprecations.expect_deprecations() 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) def test_options(self): opts = [o.name for o in v3.FederatedBaseAuth.get_options()] self.assertIn('protocol', opts) self.assertIn('identity-provider', opts) python-keystoneclient-4.0.0/keystoneclient/tests/unit/test_http.py0000664000175000017500000001776113644407055025711 0ustar zuulzuul00000000000000# 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 logging import six from testtools import matchers from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient import session from keystoneclient.tests.unit import utils RESPONSE_BODY = '{"hi": "there"}' def get_client(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test") return cl def get_authed_client(): cl = get_client() cl.management_url = "http://127.0.0.1:5000" cl.auth_token = "token" return cl class ClientTest(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/hi' def test_unauthorized_client_requests(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_client() self.assertRaises(exceptions.AuthorizationFailure, cl.get, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.post, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.put, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.delete, '/hi') def test_get(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('GET', text=RESPONSE_BODY) with self.deprecations.expect_deprecations_here(): resp, body = cl.get("/hi") self.assertEqual(self.requests_mock.last_request.method, 'GET') self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL) self.assertRequestHeaderEqual('X-Auth-Token', 'token') self.assertRequestHeaderEqual('User-Agent', httpclient.USER_AGENT) # Automatic JSON parsing self.assertEqual(body, {"hi": "there"}) def test_get_error_with_plaintext_resp(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('GET', status_code=400, text='Some evil plaintext string') self.assertRaises(exceptions.BadRequest, cl.get, '/hi') def test_get_error_with_json_resp(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() err_response = { "error": { "code": 400, "title": "Error title", "message": "Error message string" } } self.stub_url('GET', status_code=400, json=err_response) exc_raised = False try: with self.deprecations.expect_deprecations_here(): cl.get('/hi') except exceptions.BadRequest as exc: exc_raised = True self.assertEqual(exc.message, "Error message string (HTTP 400)") self.assertTrue(exc_raised, 'Exception not raised.') def test_post(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('POST') with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) self.assertEqual(self.requests_mock.last_request.method, 'POST') self.assertEqual(self.requests_mock.last_request.body, '[1, 2, 3]') self.assertRequestHeaderEqual('X-Auth-Token', 'token') self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('User-Agent', httpclient.USER_AGENT) def test_forwarded_for(self): ORIGINAL_IP = "10.100.100.1" # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test", original_ip=ORIGINAL_IP) self.stub_url('GET') with self.deprecations.expect_deprecations_here(): cl.request(self.TEST_URL, 'GET') forwarded = "for=%s;by=%s" % (ORIGINAL_IP, httpclient.USER_AGENT) self.assertRequestHeaderEqual('Forwarded', forwarded) def test_client_deprecated(self): # Can resolve symbols from the keystoneclient.client module. # keystoneclient.client was deprecated and renamed to # keystoneclient.httpclient. This tests that keystoneclient.client # can still be used. from keystoneclient import client # These statements will raise an AttributeError if the symbol isn't # defined in the module. client.HTTPClient class BasicRequestTests(utils.TestCase): url = 'http://keystone.test.com/' def setUp(self): super(BasicRequestTests, self).setUp() self.logger_message = six.moves.cStringIO() handler = logging.StreamHandler(self.logger_message) handler.setLevel(logging.DEBUG) self.logger = logging.getLogger(session.__name__) level = self.logger.getEffectiveLevel() self.logger.setLevel(logging.DEBUG) self.logger.addHandler(handler) self.addCleanup(self.logger.removeHandler, handler) self.addCleanup(self.logger.setLevel, level) def request(self, method='GET', response='Test Response', status_code=200, url=None, headers={}, **kwargs): if not url: url = self.url self.requests_mock.register_uri(method, url, text=response, status_code=status_code, headers=headers) with self.deprecations.expect_deprecations_here(): return httpclient.request(url, method, headers=headers, **kwargs) def test_basic_params(self): method = 'GET' response = 'Test Response' status = 200 self.request(method=method, status_code=status, response=response, headers={'Content-Type': 'application/json'}) self.assertEqual(self.requests_mock.last_request.method, method) logger_message = self.logger_message.getvalue() self.assertThat(logger_message, matchers.Contains('curl')) self.assertThat(logger_message, matchers.Contains('-X %s' % method)) self.assertThat(logger_message, matchers.Contains(self.url)) self.assertThat(logger_message, matchers.Contains(str(status))) self.assertThat(logger_message, matchers.Contains(response)) def test_headers(self): headers = {'key': 'val', 'test': 'other'} self.request(headers=headers) for k, v in headers.items(): self.assertRequestHeaderEqual(k, v) for header in headers.items(): self.assertThat(self.logger_message.getvalue(), matchers.Contains('-H "%s: %s"' % header)) def test_body(self): data = "BODY DATA" self.request(response=data, headers={'Content-Type': 'application/json'}) logger_message = self.logger_message.getvalue() self.assertThat(logger_message, matchers.Contains('BODY:')) self.assertThat(logger_message, matchers.Contains(data)) python-keystoneclient-4.0.0/keystoneclient/baseclient.py0000664000175000017500000000276713644407055023663 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 warnings class Client(object): def __init__(self, session): warnings.warn( 'keystoneclient.baseclient.Client is deprecated as of the 2.1.0 ' 'release. It will be removed in future releases.', DeprecationWarning) self.session = session def request(self, url, method, **kwargs): kwargs.setdefault('authenticated', True) return self.session.request(url, method, **kwargs) 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) python-keystoneclient-4.0.0/keystoneclient/utils.py0000664000175000017500000001031013644407055022671 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 getpass import hashlib import sys from keystoneauth1 import exceptions as ksa_exceptions from oslo_utils import timeutils import six from keystoneclient import exceptions as ksc_exceptions def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try the entity as a string try: return manager.get(name_or_id) except (ksa_exceptions.NotFound): # nosec(cjschaef): try to find # 'name_or_id' as a six.binary_type instead pass # finally try to find entity by name try: if isinstance(name_or_id, six.binary_type): name_or_id = name_or_id.decode('utf-8', 'strict') return manager.find(name=name_or_id) except ksa_exceptions.NotFound: msg = ("No %s with a name or ID of '%s' exists." % (manager.resource_class.__name__.lower(), name_or_id)) raise ksc_exceptions.CommandError(msg) except ksc_exceptions.NoUniqueMatch: msg = ("Multiple %s matches found for '%s', use an ID to be more" " specific." % (manager.resource_class.__name__.lower(), name_or_id)) raise ksc_exceptions.CommandError(msg) def hash_signed_token(signed_text, mode='md5'): hash_ = hashlib.new(mode) hash_.update(signed_text) return hash_.hexdigest() def prompt_user_password(): """Prompt user for a password. Prompt for a password if stdin is a tty. """ password = None # If stdin is a tty, try prompting for the password if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: password = getpass.getpass('Password: ') except EOFError: # nosec(cjschaef): return password, which is None if # password was not found pass return password def prompt_for_password(): """Prompt user for password if not provided. Prompt is used so the password doesn't show up in the bash history. """ if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): # nothing to do return while True: try: new_passwd = getpass.getpass('New Password: ') rep_passwd = getpass.getpass('Repeat New Password: ') if new_passwd == rep_passwd: return new_passwd except EOFError: return _ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' _ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' def isotime(at=None, subsecond=False): """Stringify time in ISO 8601 format.""" # Python provides a similar instance method for datetime.datetime objects # called isoformat(). The format of the strings generated by isoformat() # have a couple of problems: # 1) The strings generated by isotime are used in tokens and other public # APIs that we can't change without a deprecation period. The strings # generated by isoformat are not the same format, so we can't just # change to it. # 2) The strings generated by isoformat do not include the microseconds if # the value happens to be 0. This will likely show up as random failures # as parsers may be written to always expect microseconds, and it will # parse correctly most of the time. if not at: at = timeutils.utcnow() st = at.strftime(_ISO8601_TIME_FORMAT if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' st += ('Z' if (tz == 'UTC' or tz == 'UTC+00:00') else tz) return st def strtime(at=None): at = at or timeutils.utcnow() return at.strftime(timeutils.PERFECT_TIME_FORMAT) python-keystoneclient-4.0.0/keystoneclient/i18n.py0000664000175000017500000000153313644407055022317 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='keystoneclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-keystoneclient-4.0.0/keystoneclient/exceptions.py0000664000175000017500000003031313644407055023717 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Exception definitions.""" from keystoneauth1 import exceptions as _exc from keystoneclient.i18n import _ ClientException = _exc.ClientException """The base exception class for all exceptions this library raises. An alias of :py:exc:`keystoneauth1.exceptions.base.ClientException` """ ConnectionError = _exc.ConnectionError """Cannot connect to API service. An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectionError` """ ConnectionRefused = _exc.ConnectFailure """Connection refused while trying to connect to API service. An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectFailure` """ SSLError = _exc.SSLError """An SSL error occurred. An alias of :py:exc:`keystoneauth1.exceptions.connection.SSLError` """ AuthorizationFailure = _exc.AuthorizationFailure """Cannot authorize API client. An alias of :py:exc:`keystoneauth1.exceptions.auth.AuthorizationFailure` """ class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass EndpointException = _exc.CatalogException """Something is rotten in Service Catalog. An alias of :py:exc:`keystoneauth1.exceptions.catalog.CatalogException` """ EndpointNotFound = _exc.EndpointNotFound """Could not find requested endpoint in Service Catalog. An alias of :py:exc:`keystoneauth1.exceptions.catalog.EndpointNotFound` """ class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints HttpError = _exc.HttpError """The base exception class for all HTTP exceptions. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpError` """ HTTPClientError = _exc.HTTPClientError """Client-side HTTP error. Exception for cases in which the client seems to have erred. An alias of :py:exc:`keystoneauth1.exceptions.http.HTTPClientError` """ HttpServerError = _exc.HttpServerError """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpServerError` """ class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = 300 message = _("Multiple Choices") BadRequest = _exc.BadRequest """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. An alias of :py:exc:`keystoneauth1.exceptions.http.BadRequest` """ Unauthorized = _exc.Unauthorized """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. An alias of :py:exc:`keystoneauth1.exceptions.http.Unauthorized` """ PaymentRequired = _exc.PaymentRequired """HTTP 402 - Payment Required. Reserved for future use. An alias of :py:exc:`keystoneauth1.exceptions.http.PaymentRequired` """ Forbidden = _exc.Forbidden """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. An alias of :py:exc:`keystoneauth1.exceptions.http.Forbidden` """ NotFound = _exc.NotFound """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. An alias of :py:exc:`keystoneauth1.exceptions.http.NotFound` """ MethodNotAllowed = _exc.MethodNotAllowed """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. An alias of :py:exc:`keystoneauth1.exceptions.http.MethodNotAllowed` """ NotAcceptable = _exc.NotAcceptable """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. An alias of :py:exc:`keystoneauth1.exceptions.http.NotAcceptable` """ ProxyAuthenticationRequired = _exc.ProxyAuthenticationRequired """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. An alias of :py:exc:`keystoneauth1.exceptions.http.ProxyAuthenticationRequired` """ RequestTimeout = _exc.RequestTimeout """HTTP 408 - Request Timeout. The server timed out waiting for the request. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestTimeout` """ Conflict = _exc.Conflict """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. An alias of :py:exc:`keystoneauth1.exceptions.http.Conflict` """ Gone = _exc.Gone """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. An alias of :py:exc:`keystoneauth1.exceptions.http.Gone` """ LengthRequired = _exc.LengthRequired """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. An alias of :py:exc:`keystoneauth1.exceptions.http.LengthRequired` """ PreconditionFailed = _exc.PreconditionFailed """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. An alias of :py:exc:`keystoneauth1.exceptions.http.PreconditionFailed` """ RequestEntityTooLarge = _exc.RequestEntityTooLarge """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestEntityTooLarge` """ RequestUriTooLong = _exc.RequestUriTooLong """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestUriTooLong` """ UnsupportedMediaType = _exc.UnsupportedMediaType """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. An alias of :py:exc:`keystoneauth1.exceptions.http.UnsupportedMediaType` """ RequestedRangeNotSatisfiable = _exc.RequestedRangeNotSatisfiable """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestedRangeNotSatisfiable` """ ExpectationFailed = _exc.ExpectationFailed """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. An alias of :py:exc:`keystoneauth1.exceptions.http.ExpectationFailed` """ UnprocessableEntity = _exc.UnprocessableEntity """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. An alias of :py:exc:`keystoneauth1.exceptions.http.UnprocessableEntity` """ InternalServerError = _exc.InternalServerError """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. An alias of :py:exc:`keystoneauth1.exceptions.http.InternalServerError` """ HttpNotImplemented = _exc.HttpNotImplemented """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpNotImplemented` """ BadGateway = _exc.BadGateway """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. An alias of :py:exc:`keystoneauth1.exceptions.http.BadGateway` """ ServiceUnavailable = _exc.ServiceUnavailable """HTTP 503 - Service Unavailable. The server is currently unavailable. An alias of :py:exc:`keystoneauth1.exceptions.http.ServiceUnavailable` """ GatewayTimeout = _exc.GatewayTimeout """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. An alias of :py:exc:`keystoneauth1.exceptions.http.GatewayTimeout` """ HttpVersionNotSupported = _exc.HttpVersionNotSupported """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpVersionNotSupported` """ from_response = _exc.from_response """Return an instance of :class:`HttpError` or subclass based on response. An alias of :py:func:`keystoneauth1.exceptions.http.from_response` """ # NOTE(akurilin): This alias should be left here to support backwards # compatibility until we are sure that usage of these exceptions in # projects is correct. HTTPNotImplemented = HttpNotImplemented Timeout = RequestTimeout HTTPError = HttpError class CertificateConfigError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output msg = _('Unable to load certificate.') super(CertificateConfigError, self).__init__(msg) class CMSError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output msg = _('Unable to sign or verify data.') super(CMSError, self).__init__(msg) EmptyCatalog = _exc.EmptyCatalog """The service catalog is empty. An alias of :py:exc:`keystoneauth1.exceptions.catalog.EmptyCatalog` """ DiscoveryFailure = _exc.DiscoveryFailure """Discovery of client versions failed. An alias of :py:exc:`keystoneauth1.exceptions.discovery.DiscoveryFailure` """ VersionNotAvailable = _exc.VersionNotAvailable """Discovery failed as the version you requested is not available. An alias of :py:exc:`keystoneauth1.exceptions.discovery.VersionNotAvailable` """ class MethodNotImplemented(ClientException): """Method not implemented by the keystoneclient API.""" MissingAuthPlugin = _exc.MissingAuthPlugin """An authenticated request is required but no plugin available. An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin` """ NoMatchingPlugin = _exc.NoMatchingPlugin """There were no auth plugins that could be created from the parameters provided. An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin` """ class UnsupportedParameters(ClientException): """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 InvalidResponse(ClientException): """The response from the server is not valid for this request.""" def __init__(self, response): super(InvalidResponse, self).__init__() self.response = response python-keystoneclient-4.0.0/keystoneclient/auth/0000775000175000017500000000000013644407143022123 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/auth/token_endpoint.py0000664000175000017500000000400513644407055025516 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 warnings from oslo_config import cfg from keystoneclient.auth import base class Token(base.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 warnings.warn( 'TokenEndpoint plugin is deprecated as of the 2.1.0 release in ' 'favor of keystoneauth1.token_endpoint.Token. It will be removed ' 'in future releases.', DeprecationWarning) 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 @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('endpoint', help='The endpoint that will always be used'), cfg.StrOpt('token', secret=True, help='The token that will always be used'), ]) return options python-keystoneclient-4.0.0/keystoneclient/auth/__init__.py0000664000175000017500000000220313644407055024233 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # flake8: noqa: F405 from keystoneclient.auth.base import * # noqa from keystoneclient.auth.cli import * # noqa from keystoneclient.auth.conf import * # noqa __all__ = ( # auth.base 'AUTH_INTERFACE', 'BaseAuthPlugin', 'get_available_plugin_names', 'get_available_plugin_classes', 'get_plugin_class', 'IDENTITY_AUTH_HEADER_NAME', 'PLUGIN_NAMESPACE', # auth.cli 'load_from_argparse_arguments', 'register_argparse_arguments', # auth.conf 'get_common_conf_options', 'get_plugin_options', 'load_from_conf_options', 'register_conf_options', ) python-keystoneclient-4.0.0/keystoneclient/auth/identity/0000775000175000017500000000000013644407143023754 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/0000775000175000017500000000000013644407143024304 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/__init__.py0000664000175000017500000000202613644407055026417 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # flake8: noqa: F405 from keystoneclient.auth.identity.v3.base import * # noqa from keystoneclient.auth.identity.v3.federated import * # noqa from keystoneclient.auth.identity.v3.password import * # noqa from keystoneclient.auth.identity.v3.token import * # noqa __all__ = ('Auth', 'AuthConstructor', 'AuthMethod', 'BaseAuth', 'FederatedBaseAuth', 'Password', 'PasswordMethod', 'Token', 'TokenMethod') python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/token.py0000664000175000017500000000435513644407055026007 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.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} 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) @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('token', secret=True, help='Token to authenticate with'), ]) return options python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/federated.py0000664000175000017500000001062013644407055026602 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 oslo_config import cfg import six from keystoneclient.auth.identity.v3 import base from keystoneclient.auth.identity.v3 import token __all__ = ('FederatedBaseAuth',) @six.add_metaclass(abc.ABCMeta) class FederatedBaseAuth(base.BaseAuth): rescoping_plugin = token.Token def __init__(self, auth_url, identity_provider, protocol, **kwargs): """Class constructor for federated authentication plugins. 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 protocol: Protocol name configured on the keystone service provider side :type protocol: string """ super(FederatedBaseAuth, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.protocol = protocol @classmethod def get_options(cls): options = super(FederatedBaseAuth, cls).get_options() options.extend([ cfg.StrOpt('identity-provider', help="Identity Provider's name"), cfg.StrOpt('protocol', help="Name of the federated protocol used " "for federated authentication. Must " "match its counterpart name " "configured at the keystone service " "provider. Typically values would be " "'saml2' or 'oidc'.") ]) return options @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 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: keystoneclient.session.Session :returns: a token data representation :rtype: :py:class:`keystoneclient.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.""" pass # pragma: no cover python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/base.py0000664000175000017500000002474113644407055025602 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 logging from oslo_config import cfg from oslo_serialization import jsonutils import six from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient.i18n import _ _logger = logging.getLogger(__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 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. """ 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 trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value @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 # pragma: no cover @classmethod def get_options(cls): options = super(BaseAuth, cls).get_options() options.extend([ cfg.StrOpt('domain-id', help='Domain ID to scope to'), cfg.StrOpt('domain-name', help='Domain name to scope to'), cfg.StrOpt('project-id', help='Project ID to scope to'), cfg.StrOpt('project-name', help='Project name to scope to'), cfg.StrOpt('project-domain-id', help='Domain ID containing project'), cfg.StrOpt('project-domain-name', help='Domain name containing project'), cfg.StrOpt('trust-id', help='Trust ID'), ]) return options 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(jsonutils.dumps(resp.json())) resp_data = resp.json()['token'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(resp.headers['X-Subject-Token'], **resp_data) @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) 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: keystoneclient.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) """ pass # pragma: no cover @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) python-keystoneclient-4.0.0/keystoneclient/auth/identity/v3/password.py0000664000175000017500000000743513644407055026533 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.identity.v3 import base from keystoneclient import utils __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} 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 @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend([ cfg.StrOpt('user-id', help='User ID'), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('user-domain-id', help="User's domain id"), cfg.StrOpt('user-domain-name', help="User's domain name"), cfg.StrOpt('password', secret=True, help="User's password"), ]) return options @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) python-keystoneclient-4.0.0/keystoneclient/auth/identity/__init__.py0000664000175000017500000000210113644407055026061 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.identity import base from keystoneclient.auth.identity import generic from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 BaseIdentityPlugin = base.BaseIdentityPlugin V2Password = v2.Password V2Token = v2.Token V3Password = v3.Password V3Token = v3.Token Password = generic.Password Token = generic.Token __all__ = ('BaseIdentityPlugin', 'Password', 'Token', 'V2Password', 'V2Token', 'V3Password', 'V3Token') python-keystoneclient-4.0.0/keystoneclient/auth/identity/base.py0000664000175000017500000003675013644407055025255 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 logging import threading import warnings from oslo_config import cfg import six from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions LOG = logging.getLogger(__name__) def get_options(): return [ cfg.StrOpt('auth-url', help='Authentication URL'), ] @six.add_metaclass(abc.ABCMeta) class BaseIdentityPlugin(base.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, username=None, password=None, token=None, trust_id=None, reauthenticate=True): super(BaseIdentityPlugin, self).__init__() warnings.warn( 'keystoneclient auth plugins are deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1 plugins. They will be removed ' 'in future releases.', DeprecationWarning) self.auth_url = auth_url self.auth_ref = None self.reauthenticate = reauthenticate self._endpoint_cache = {} self._lock = threading.Lock() self._username = username self._password = password self._token = token self._trust_id = trust_id @property def username(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'username is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._username @username.setter def username(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'username is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._username = value @property def password(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'password is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._password @password.setter def password(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'password is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._password = value @property def token(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'token is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._token @token.setter def token(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'token is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._token = value @property def trust_id(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'trust_id is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._trust_id @trust_id.setter def trust_id(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'trust_id is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._trust_id = value @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 method should not be called independently and is expected to be invoked via the do_authenticate() method. This method will be invoked if the AccessInfo 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: keystoneclient.session.Session :raises keystoneclient.exceptions.InvalidResponse: The response returned wasn't appropriate. :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: Token access information. :rtype: :py:class:`keystoneclient.access.AccessInfo` """ pass # pragma: no cover 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: keystoneclient.session.Session :raises keystoneclient.exceptions.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: keystoneclient.session.Session :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: Valid AccessInfo :rtype: :py:class:`keystoneclient.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(self, session, service_type=None, interface=None, region_name=None, service_name=None, 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. :param session: A session object that can be used for communication. :type session: keystoneclient.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 string interface: The exposure of the endpoint. Should be `public`, `internal`, `admin`, or `auth`. `auth` is special here to use the `auth_url` rather than a URL extracted from the service catalog. 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 tuple version: The minimum version number required for this endpoint. (optional) :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :return: A valid endpoint URL or None if not available. :rtype: string or None """ # 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 base.AUTH_INTERFACE: url = self.auth_url service_type = service_type or 'identity' 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 if not interface: interface = 'public' service_catalog = self.get_access(session).service_catalog url = service_catalog.url_for(service_type=service_type, endpoint_type=interface, region_name=region_name, service_name=service_name) if not version: # 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 url # NOTE(jamielennox): For backwards compatibility people might have a # versioned endpoint in their catalog even though they want to use # other endpoint versions. So we support a list of client defined # situations where we can strip the version component from a URL before # doing discovery. hacked_url = _discover.get_catalog_discover_hack(service_type, url) try: disc = self.get_discovery(session, hacked_url, authenticated=False) except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): # NOTE(jamielennox): Again if we can't contact the server we fall # back to just returning the URL from the catalog. This may not be # the best default but we need it for now. LOG.warning( 'Failed to contact the endpoint at %s for discovery. Fallback ' 'to using that endpoint as the base url.', url) else: url = disc.url_for(version) return url 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_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: keystoneclient.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 keystoneclient.exceptions.DiscoveryFailure: if for some reason the lookup fails. :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: A discovery object with the results of looking up that URL. """ # NOTE(jamielennox): we want to cache endpoints on the session as well # so that they maintain sharing between auth plugins. Create a cache on # the session if it doesn't exist already. try: session_endpoint_cache = session._identity_endpoint_cache except AttributeError: session_endpoint_cache = session._identity_endpoint_cache = {} # NOTE(jamielennox): There is a cache located on both the session # object and the auth plugin object so that they can be shared and the # cache is still usable for cache in (self._endpoint_cache, session_endpoint_cache): disc = cache.get(url) if disc: break else: disc = _discover.Discover(session, url, authenticated=authenticated) self._endpoint_cache[url] = disc session_endpoint_cache[url] = disc return disc @classmethod def get_options(cls): options = super(BaseIdentityPlugin, cls).get_options() options.extend(get_options()) return options python-keystoneclient-4.0.0/keystoneclient/auth/identity/v2.py0000664000175000017500000001722013644407055024661 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 logging from oslo_config import cfg import six from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient import utils _logger = logging.getLogger(__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 """ @classmethod def get_options(cls): options = super(Auth, cls).get_options() options.extend([ cfg.StrOpt('tenant-id', help='Tenant ID'), cfg.StrOpt('tenant-name', help='Tenant Name'), cfg.StrOpt('trust-id', help='Trust ID'), ]) return options 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 @property def trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value 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()['access'] except (KeyError, ValueError): 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 """ pass # pragma: no cover _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. """ 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 @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value 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} @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend([ cfg.StrOpt('username', dest='username', deprecated_name='user-name', help='Username to login with'), cfg.StrOpt('user-id', help='User ID to login with'), cfg.StrOpt('password', secret=True, help='Password to use'), ]) return options 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 @property def token(self): # Override to remove deprecation. return self._token @token.setter def token(self, value): # Override to remove deprecation. self._token = value def get_auth_data(self, headers=None): if headers is not None: headers['X-Auth-Token'] = self.token return {'token': {'id': self.token}} @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('token', secret=True, help='Token'), ]) return options python-keystoneclient-4.0.0/keystoneclient/auth/identity/access.py0000664000175000017500000000350313644407055025572 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.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: keystoneclient.access.AccessInfo :param auth_url: the url where this AccessInfo was retrieved from. Required if using the AUTH_INTERFACE with get_endpoint. (optional) """ 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 python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/0000775000175000017500000000000013644407143025370 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/__init__.py0000664000175000017500000000153413644407055027506 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.identity.generic.base import BaseGenericPlugin # noqa from keystoneclient.auth.identity.generic.password import Password # noqa from keystoneclient.auth.identity.generic.token import Token # noqa __all__ = ('BaseGenericPlugin', 'Password', 'Token', ) python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/token.py0000664000175000017500000000310513644407055027063 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import _discover from keystoneclient.auth.identity.generic import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 def get_options(): return [ cfg.StrOpt('token', secret=True, help='Token to authenticate with'), ] 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) @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend(get_options()) return options python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/base.py0000664000175000017500000001562013644407055026662 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 logging from oslo_config import cfg import six import six.moves.urllib.parse as urlparse from keystoneclient import _discover from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient.i18n import _ LOG = logging.getLogger(__name__) def get_options(): return [ cfg.StrOpt('domain-id', help='Domain ID to scope to'), cfg.StrOpt('domain-name', help='Domain name to scope to'), cfg.StrOpt('tenant-id', help='Tenant ID to scope to'), cfg.StrOpt('tenant-name', help='Tenant name to scope to'), cfg.StrOpt('project-id', help='Project ID to scope to'), cfg.StrOpt('project-name', help='Project name to scope to'), cfg.StrOpt('project-domain-id', help='Domain ID containing project'), cfg.StrOpt('project-domain-name', help='Domain name containing project'), cfg.StrOpt('trust-id', help='Trust ID'), ] @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): super(BaseGenericPlugin, self).__init__(auth_url=auth_url) 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._plugin = None @property def trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value @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: keystoneclient.session.Session :param tuple version: A tuple of the API version at the URL. :param string url: The base URL for this version. :param string raw_status: The status that was in the discovery field. :returns: A plugin that can match the parameters or None if nothing. """ return None # pragma: no cover @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 parameters that are common to v2 plugins.""" return {'trust_id': self._trust_id, 'tenant_id': self._project_id, 'tenant_name': self._project_name} @property def _v3_params(self): """Return 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} 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('Discovering versions from the identity service ' 'failed when creating the password plugin. ' 'Attempting to determine version from URL.') url_parts = urlparse.urlparse(self.auth_url) path = url_parts.path.lower() if path.startswith('/v2.0') and not self._has_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: disc_data = disc.version_data() 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. continue plugin = self.create_plugin(session, version, data['url'], raw_status=data['raw_status']) if plugin: break if plugin: return plugin # so there were no URLs that i could use for auth of any version. msg = _('Could not determine a suitable URL for the plugin') raise exceptions.DiscoveryFailure(msg) 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) @classmethod def get_options(cls): options = super(BaseGenericPlugin, cls).get_options() options.extend(get_options()) return options python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/cli.py0000664000175000017500000000650213644407055026516 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient.auth.identity.generic import password from keystoneclient import exceptions as exc from keystoneclient.i18n import _ class DefaultCLI(password.Password): """A Plugin that provides typical authentication options for CLIs. This plugin provides standard username and password authentication options as well as allowing users to override with a custom token and endpoint. """ def __init__(self, endpoint=None, token=None, **kwargs): super(DefaultCLI, self).__init__(**kwargs) self._token = token self._endpoint = endpoint @classmethod def get_options(cls): options = super(DefaultCLI, cls).get_options() options.extend([cfg.StrOpt('endpoint', help='A URL to use instead of a catalog'), cfg.StrOpt('token', secret=True, help='Always use the specified token')]) return options def get_token(self, *args, **kwargs): if self._token: return self._token return super(DefaultCLI, self).get_token(*args, **kwargs) def get_endpoint(self, *args, **kwargs): if self._endpoint: return self._endpoint return super(DefaultCLI, self).get_endpoint(*args, **kwargs) @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): token = kwargs.get('token') or namespace.os_token endpoint = kwargs.get('endpoint') or namespace.os_endpoint auth_url = kwargs.get('auth_url') or namespace.os_auth_url if token and not endpoint: # if a user provides a token then they must also provide an # endpoint because we aren't fetching a token to get a catalog from msg = _('A service URL must be provided with a token') raise exc.CommandError(msg) elif (not token) and (not auth_url): # if you don't provide a token you are going to provide at least an # auth_url with which to authenticate. raise exc.CommandError(_('Expecting an auth URL via either ' '--os-auth-url or env[OS_AUTH_URL]')) plugin = super(DefaultCLI, cls).load_from_argparse_arguments(namespace, **kwargs) if (not token) and (not plugin._password): # we do this after the load so that the base plugin has an # opportunity to prompt the user for a password raise exc.CommandError(_('Expecting a password provided via ' 'either --os-password, env[OS_PASSWORD], ' 'or prompted response')) return plugin python-keystoneclient-4.0.0/keystoneclient/auth/identity/generic/password.py0000664000175000017500000000673513644407055027621 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient import _discover from keystoneclient.auth.identity.generic import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient import utils def get_options(): return [ cfg.StrOpt('user-id', help='User id'), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('user-domain-id', help="User's domain id"), cfg.StrOpt('user-domain-name', help="User's domain name"), cfg.StrOpt('password', secret=True, help="User's password"), ] 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. """ 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: # If you specify any domain parameters it won't work so quit. 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): return v3.Password(auth_url=url, user_id=self._user_id, username=self._username, user_domain_id=self._user_domain_id, user_domain_name=self._user_domain_name, password=self._password, **self._v3_params) @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend(get_options()) return options @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) python-keystoneclient-4.0.0/keystoneclient/auth/base.py0000664000175000017500000003316313644407055023417 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from keystoneauth1 import plugin import six import stevedore from keystoneclient import exceptions # 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 = plugin.AUTH_INTERFACE PLUGIN_NAMESPACE = 'keystoneclient.auth.plugin' IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token' @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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.ExtensionManager(namespace=PLUGIN_NAMESPACE, invoke_on_load=False) return frozenset(mgr.names()) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_available_plugin_classes(): """Retrieve all the plugin classes available on the system. :returns: A dict with plugin entrypoint name as the key and the plugin class as the value. :rtype: dict """ mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE, propagate_map_exceptions=True, invoke_on_load=False) return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.plugin))) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_plugin_class(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:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ try: mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE, name=name, invoke_on_load=False) except RuntimeError: raise exceptions.NoMatchingPlugin(name) return mgr.driver class BaseAuthPlugin(object): """The basic structure of an authentication plugin.""" 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: keystoneclient.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: 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 """ 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: keystoneclient.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: keystoneclient.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: keystoneclient.session.Session :returns: A project identifier or None if one is not available. :rtype: str """ return None @classmethod def get_options(cls): """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 [] @classmethod def load_from_options(cls, **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. """ return cls(**kwargs) @classmethod def register_argparse_arguments(cls, parser): """Register the CLI options provided by a specific plugin. Given a plugin class convert it's options into argparse arguments and add them to a parser. :param parser: the parser to attach argparse options. :type parser: argparse.ArgumentParser """ # NOTE(jamielennox): ideally oslo_config would be smart enough to # handle all the Opt manipulation that goes on in this file. However it # is currently not. Options are handled in as similar a way as # possible to oslo_config such that when available we should be able to # transition. for opt in cls.get_options(): args = [] envs = [] for o in [opt] + opt.deprecated_opts: args.append('--os-%s' % o.name) envs.append('OS_%s' % o.name.replace('-', '_').upper()) # select the first ENV that is not false-y or return None env_vars = (os.environ.get(e) for e in envs) default = six.next(six.moves.filter(None, env_vars), None) parser.add_argument(*args, default=default or opt.default, metavar=opt.metavar, help=opt.help, dest='os_%s' % opt.dest) @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): """Load a specific plugin object from an argparse result. Convert the results of a parse into the specified plugin. :param namespace: The result from CLI parsing. :type namespace: argparse.Namespace :returns: An auth plugin, or None if a name is not provided. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` """ def _getter(opt): return getattr(namespace, 'os_%s' % opt.dest) return cls.load_from_options_getter(_getter, **kwargs) @classmethod def register_conf_options(cls, conf, group): """Register the oslo_config options that are needed for a plugin. :param conf: A config object. :type conf: oslo_config.cfg.ConfigOpts :param string group: The group name that options should be read from. """ plugin_opts = cls.get_options() conf.register_opts(plugin_opts, group=group) @classmethod def load_from_conf_options(cls, conf, group, **kwargs): """Load the plugin from a CONF object. Convert the options already registered into a real plugin. :param conf: A config object. :type conf: oslo_config.cfg.ConfigOpts :param string group: The group name that options should be read from. :returns: An authentication Plugin. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` """ def _getter(opt): return conf[group][opt.dest] return cls.load_from_options_getter(_getter, **kwargs) @classmethod def load_from_options_getter(cls, getter, **kwargs): """Load a plugin from a getter function returning 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 one value, an :py:class:`oslo_config.cfg.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:`keystoneclient.auth.BaseAuthPlugin` """ plugin_opts = cls.get_options() for opt in plugin_opts: val = getter(opt) if val is not None: val = opt.type(val) kwargs.setdefault(opt.dest, val) return cls.load_from_options(**kwargs) python-keystoneclient-4.0.0/keystoneclient/auth/cli.py0000664000175000017500000000670013644407055023251 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from keystoneclient.auth import base @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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 argparse.ArgumentParser: the parser to attach argparse options to. :param List argv: the arguments provided to the application. :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: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ in_parser = argparse.ArgumentParser(add_help=False) env_plugin = os.environ.get('OS_AUTH_PLUGIN', default) for p in (in_parser, parser): p.add_argument('--os-auth-plugin', metavar='', default=env_plugin, help='The auth plugin to load') options, _args = in_parser.parse_known_args(argv) if not options.os_auth_plugin: return None if isinstance(options.os_auth_plugin, type): msg = 'Default Authentication options' plugin = options.os_auth_plugin else: msg = 'Options specific to the %s plugin.' % options.os_auth_plugin plugin = base.get_plugin_class(options.os_auth_plugin) group = parser.add_argument_group('Authentication Options', msg) plugin.register_argparse_arguments(group) return plugin @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ if not namespace.os_auth_plugin: return None if isinstance(namespace.os_auth_plugin, type): plugin = namespace.os_auth_plugin else: plugin = base.get_plugin_class(namespace.os_auth_plugin) return plugin.load_from_argparse_arguments(namespace, **kwargs) python-keystoneclient-4.0.0/keystoneclient/auth/conf.py0000664000175000017500000001137013644407055023426 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 debtcollector import removals from oslo_config import cfg from keystoneclient.auth import base _AUTH_PLUGIN_OPT = cfg.StrOpt('auth_plugin', help='Name of the plugin to load') _section_help = 'Config Section from which to load plugin specific options' _AUTH_SECTION_OPT = cfg.StrOpt('auth_section', help=_section_help) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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_plugin: 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_PLUGIN_OPT, _AUTH_SECTION_OPT] @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_plugin_options(name): """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. :returns: A list of oslo_config options. """ return base.get_plugin_class(name).get_options() @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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_plugin: 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, 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_PLUGIN_OPT, group=group) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) 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 string group: The group name that options should be read from. :returns: An authentication Plugin or None if a name is not provided :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.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_plugin if not name: return None plugin_class = base.get_plugin_class(name) plugin_class.register_conf_options(conf, group) return plugin_class.load_from_conf_options(conf, group, **kwargs) python-keystoneclient-4.0.0/keystoneclient/session.py0000664000175000017500000012451413644407055023230 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 functools import hashlib import logging import os import socket import time import warnings from debtcollector import removals from oslo_config import cfg from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils import requests import six from six.moves import urllib from keystoneclient import exceptions from keystoneclient.i18n import _ osprofiler_web = importutils.try_import("osprofiler.web") USER_AGENT = 'python-keystoneclient' # 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 = logging.getLogger(__name__) 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 def request(url, method='GET', **kwargs): return Session().request(url, method=method, **kwargs) def _remove_service_catalog(body): try: data = jsonutils.loads(body) # V3 token if 'token' in data and 'catalog' in data['token']: data['token']['catalog'] = '' return jsonutils.dumps(data) # V2 token if 'serviceCatalog' in data['access']: data['access']['serviceCatalog'] = '' return jsonutils.dumps(data) except Exception: # nosec(cjschaef): multiple exceptions can be raised # Don't fail trying to clean up the request body. pass return body 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 requests library. :param auth: An authentication plugin to authenticate the session with. (optional, defaults to None) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :param requests.Session session: A requests session object that can be used for issuing requests. (optional) :param string 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 string user_agent: A User-Agent header string to use for the request. If not provided a default is used. (optional, defaults to 'python-keystoneclient') :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) """ user_agent = None _REDIRECT_STATUSES = (301, 302, 303, 305, 307) REDIRECT_STATUSES = _REDIRECT_STATUSES """This property is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release.""" _DEFAULT_REDIRECT_LIMIT = 30 DEFAULT_REDIRECT_LIMIT = _DEFAULT_REDIRECT_LIMIT """This property is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release.""" def __init__(self, auth=None, session=None, original_ip=None, verify=True, cert=None, timeout=None, user_agent=None, redirect=_DEFAULT_REDIRECT_LIMIT): warnings.warn( 'keystoneclient.session.Session is deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1.session.Session. It will be ' 'removed in future releases.', DeprecationWarning) if not session: session = requests.Session() # Use TCPKeepAliveAdapter to fix bug 1323862 for scheme in list(session.adapters): session.mount(scheme, TCPKeepAliveAdapter()) self.auth = auth self.session = session self.original_ip = original_ip self.verify = verify self.cert = cert self.timeout = None self.redirect = redirect if timeout is not None: self.timeout = float(timeout) # don't override the class variable if none provided if user_agent is not None: self.user_agent = user_agent @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: # hashlib.sha1() bandit nosec, as it is HMAC-SHA1 in # keystone, which is considered secure (unlike just sha1) token_hasher = hashlib.sha1() # nosec(lhinds) token_hasher.update(header[1].encode('utf-8')) token_hash = token_hasher.hexdigest() return (header[0], '{SHA1}%s' % token_hash) return header def _http_log_request(self, url, method=None, data=None, headers=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]) string_parts.append(url) if headers: for header in headers.items(): string_parts.append('-H "%s: %s"' % self._process_header(header)) if data: if isinstance(data, six.binary_type): try: data = data.decode("ascii") except UnicodeDecodeError: data = "" string_parts.append("-d '%s'" % data) try: logger.debug(' '.join(string_parts)) except UnicodeDecodeError: logger.debug("Replaced characters that could not be decoded" " in log output, original caused UnicodeDecodeError") string_parts = [ encodeutils.safe_decode( part, errors='replace') for part in string_parts] logger.debug(' '.join(string_parts)) def _http_log_response(self, response, logger): if not logger.isEnabledFor(logging.DEBUG): return # 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 = _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)) string_parts = [ 'RESP:', '[%s]' % response.status_code ] for header in response.headers.items(): string_parts.append('%s: %s' % self._process_header(header)) string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text)) logger.debug(' '.join(string_parts)) # NOTE(artmr): parameter 'original_ip' value is never used 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, **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 string 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 string method: The http method to use. (e.g. 'GET', 'POST') :param string 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 string 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. (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: :py:class:`keystoneclient.auth.base.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 keystoneclient.session default logger will be used. :type logger: logging.Logger :param kwargs: any other parameter that can be passed to requests.Session.request (such as `headers`). Except: 'data' will be overwritten by the data in 'json' param. 'allow_redirects' is ignored as redirects are handled by the session. :raises keystoneclient.exceptions.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) 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 elif endpoint_filter: base_url = self.get_endpoint(auth, **endpoint_filter) if not base_url: service_type = (endpoint_filter or {}).get('service_type', 'unknown') msg = _('Endpoint for %s service') % service_type raise exceptions.EndpointNotFound(msg) 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: user_agent = headers.setdefault('User-Agent', USER_AGENT) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers['Content-Type'] = 'application/json' kwargs['data'] = jsonutils.dumps(json) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, 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: # nosec(cjschaef) # 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) # 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.RequestTimeout(msg) except requests.exceptions.ConnectionError: msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) 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(resp, 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) @classmethod def construct(cls, kwargs): """Handle constructing a session from both old and new arguments. Support constructing a session from the old :py:class:`~keystoneclient.httpclient.HTTPClient` args as well as the new request-style arguments. .. warning:: *DEPRECATED as of 1.7.0*: This function is purely for bridging the gap between older client arguments and the session arguments that they relate to. It is not intended to be used as a generic Session Factory. This function may be removed in the 2.0.0 release. This function purposefully modifies the input kwargs dictionary so that the remaining kwargs dict can be reused and passed on to other functions without session arguments. """ warnings.warn( 'Session.construct() is deprecated as of the 1.7.0 release in ' 'favor of using session constructor and may be removed in the ' '2.0.0 release.', DeprecationWarning) return cls._construct(kwargs) @classmethod def _construct(cls, kwargs): params = {} for attr in ('verify', 'cacert', 'cert', 'key', 'insecure', 'timeout', 'session', 'original_ip', 'user_agent'): try: params[attr] = kwargs.pop(attr) except KeyError: # nosec(cjschaef): we are brute force # identifying possible attributes for kwargs pass return cls._make(**params) @classmethod def _make(cls, 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: warnings.warn( 'Passing cert and key together is deprecated as of the 1.7.0 ' 'release in favor of the requests library form of having the ' 'cert and key as a tuple and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) cert = (cert, key) return cls(verify=verify, cert=cert, **kwargs) def _auth_required(self, auth, msg): if not auth: auth = self.auth if not auth: raise exceptions.MissingAuthPlugin(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: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Authentication headers or None for failure. :rtype: dict """ msg = _('An auth plugin is required to fetch a token') auth = self._auth_required(auth, msg) return auth.get_headers(self, **kwargs) @removals.remove(message='Use get_auth_headers instead.', version='1.7.0', removal_version='2.0.0') 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: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. .. warning:: This method is deprecated as of the 1.7.0 release in favor of :meth:`get_auth_headers` and may be removed in the 2.0.0 release. This method assumes that the only header that is used to authenticate a message is 'X-Auth-Token' which may not be correct. :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: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: An endpoint if available or None. :rtype: string """ msg = _('An auth plugin is required to determine endpoint URL') auth = self._auth_required(auth, msg) return auth.get_endpoint(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 parameters. 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: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :raises keystoneclient.exceptions.UnsupportedParameters: if the plugin returns a parameter that is not supported by this session. :returns: Authentication headers or None for failure. :rtype: dict """ msg = _('An auth plugin is required to fetch connection params') auth = self._auth_required(auth, msg) 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: # nosec(cjschaef): we are brute force # identifying and removing values in params_copy pass if params_copy: raise exceptions.UnsupportedParameters(list(params_copy)) 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: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` """ msg = _('An auth plugin is required to validate') auth = self._auth_required(auth, msg) 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: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns string: Current user_id or None if not supported by plugin. """ msg = _('An auth plugin is required to get user_id') auth = self._auth_required(auth, msg) 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: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns string: Current project_id or None if not supported by plugin. """ msg = _('An auth plugin is required to get project_id') auth = self._auth_required(auth, msg) return auth.get_project_id(self) @classmethod def get_conf_options(cls, 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. """ 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'), ] @classmethod def register_conf_options(cls, 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 = cls.get_conf_options(deprecated_opts=deprecated_opts) conf.register_group(cfg.OptGroup(group)) conf.register_opts(opts, group=group) return opts @classmethod def load_from_conf_options(cls, 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['insecure'] = c.insecure kwargs['cacert'] = c.cafile if c.certfile and c.keyfile: kwargs['cert'] = (c.certfile, c.keyfile) kwargs['timeout'] = c.timeout return cls._make(**kwargs) @staticmethod def register_cli_options(parser): """Register the argparse arguments that are needed for a session. :param argparse.ArgumentParser parser: parser to add to. """ parser.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.') parser.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].') parser.add_argument('--os-cert', metavar='', default=os.environ.get('OS_CERT'), help='Defaults to env[OS_CERT].') parser.add_argument('--os-key', metavar='', default=os.environ.get('OS_KEY'), help='Defaults to env[OS_KEY].') parser.add_argument('--timeout', default=600, type=_positive_non_zero_float, metavar='', help='Set request timeout (in seconds).') @classmethod def load_from_cli_options(cls, args, **kwargs): """Create a :py:class:`.Session` object from CLI arguments. The CLI arguments must have been registered with :py:meth:`.register_cli_options`. :param Namespace args: result of parsed arguments. :returns: A new session object. :rtype: :py:class:`.Session` """ kwargs['insecure'] = args.insecure kwargs['cacert'] = args.os_cacert if args.os_cert and args.os_key: kwargs['cert'] = (args.os_cert, args.os_key) kwargs['timeout'] = args.timeout return cls._make(**kwargs) 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: http://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: 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) ] # TODO(claudiub): Windows does not contain the TCP_KEEPCNT and # TCP_KEEPINTVL socket attributes. Instead, it contains # SIO_KEEPALIVE_VALS, which can be set via ioctl, which should be # set once it is available in requests. # https://msdn.microsoft.com/en-us/library/dd877220%28VS.85%29.aspx if hasattr(socket, 'TCP_KEEPCNT'): 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) python-keystoneclient-4.0.0/keystoneclient/common/0000775000175000017500000000000013644407143022452 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/common/__init__.py0000664000175000017500000000000013644407055024553 0ustar zuulzuul00000000000000python-keystoneclient-4.0.0/keystoneclient/common/cms.py0000664000175000017500000004056713644407055023624 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Certificate signing functions. Call set_subprocess() with the subprocess module. Either Python's subprocess or eventlet.green.subprocess can be used. If set_subprocess() is not called, this module will pick Python's subprocess or eventlet.green.subprocess based on if os module is patched by eventlet. """ import base64 import errno import hashlib import logging import zlib from debtcollector import removals import six from keystoneclient import exceptions from keystoneclient.i18n import _ subprocess = None LOG = logging.getLogger(__name__) PKI_ASN1_PREFIX = 'MII' PKIZ_PREFIX = 'PKIZ_' PKIZ_CMS_FORM = 'DER' PKI_ASN1_FORM = 'PEM' # Adding nosec since this fails bandit B105, 'Possible hardcoded password'. DEFAULT_TOKEN_DIGEST_ALGORITHM = 'sha256' # nosec # The openssl cms command exits with these status codes. # See https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES class OpensslCmsExitStatus(object): SUCCESS = 0 COMMAND_OPTIONS_PARSING_ERROR = 1 INPUT_FILE_READ_ERROR = 2 CREATE_CMS_READ_MIME_ERROR = 3 def _ensure_subprocess(): # NOTE(vish): late loading subprocess so we can # use the green version if we are in # eventlet. global subprocess if not subprocess: try: from eventlet import patcher if patcher.already_patched: from eventlet.green import subprocess else: import subprocess # nosec(cjschaef): we must be careful when # using subprocess.Popen with possibly untrusted data, # assumption is that the certificate/key files provided are # trustworthy except ImportError: import subprocess # noqa # nosec(cjschaef): we must be careful # when using subprocess.Popen with possibly untrusted data, # assumption is that the certificate/key files provided are # trustworthy def set_subprocess(_subprocess=None): """Set subprocess module to use. The subprocess could be eventlet.green.subprocess if using eventlet, or Python's subprocess otherwise. """ global subprocess subprocess = _subprocess def _check_files_accessible(files): err = None retcode = -1 try: for try_file in files: with open(try_file, 'r'): pass except IOError as e: # Catching IOError means there is an issue with # the given file. err = try_file, e.strerror # Emulate openssl behavior, which returns with code 2 when # access to a file failed. retcode = OpensslCmsExitStatus.INPUT_FILE_READ_ERROR return retcode, err def _process_communicate_handle_oserror(process, data, files): """Wrapper around process.communicate that checks for OSError.""" try: output, err = process.communicate(data) except OSError as e: if e.errno != errno.EPIPE: raise # OSError with EPIPE only occurs with old Python 2.7.x versions # http://bugs.python.org/issue10963 # The quick exit is typically caused by the openssl command not being # able to read an input file, so check ourselves if can't read a file. retcode, err = _check_files_accessible(files) if process.stderr: msg = process.stderr.read() if isinstance(msg, six.binary_type): msg = msg.decode('utf-8') if err: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): ' '%(stderr)s\nLikely due to %(file)s: %(error)s') % {'stderr': msg, 'file': err[0], 'error': err[1]}) else: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): %s') % msg) output = '' else: retcode = process.poll() if err is not None: if isinstance(err, six.binary_type): err = err.decode('utf-8') return output, err, retcode def _encoding_for_form(inform): if inform == PKI_ASN1_FORM: encoding = 'UTF-8' elif inform == PKIZ_CMS_FORM: encoding = 'hex' else: raise ValueError( _('"inform" must be one of: %s') % ','.join((PKI_ASN1_FORM, PKIZ_CMS_FORM))) return encoding def cms_verify(formatted, signing_cert_file_name, ca_file_name, inform=PKI_ASN1_FORM): """Verify the signature of the contents IAW CMS syntax. :raises subprocess.CalledProcessError: :raises keystoneclient.exceptions.CertificateConfigError: if certificate is not configured properly. """ _ensure_subprocess() if isinstance(formatted, six.string_types): data = bytearray(formatted, _encoding_for_form(inform)) else: data = formatted process = subprocess.Popen(['openssl', 'cms', '-verify', '-certfile', signing_cert_file_name, '-CAfile', ca_file_name, '-inform', 'PEM', '-nosmimecap', '-nodetach', '-nocerts', '-noattr'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, ca_file_name)) # Do not log errors, as some happen in the positive thread # instead, catch them in the calling code and log them there. # When invoke the openssl >= 1.1.0 with not exist file, return code should # be 2 instead of 1 and error msg will be returned. # You can get more from # https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES # # $ openssl cms -verify -certfile not_exist_file -CAfile # not_exist_file -inform PEM -nosmimecap -nodetach # -nocerts -noattr # openssl < 1.1.0 returns # Error opening certificate file not_exist_file # openssl >= 1.1.0 returns # cms: Cannot open input file not_exist_file, No such file or directory # if retcode == OpensslCmsExitStatus.INPUT_FILE_READ_ERROR: if err.startswith('Error reading S/MIME message'): raise exceptions.CMSError(err) else: raise exceptions.CertificateConfigError(err) # workaround for OpenSSL >= 1.1.0, # should return OpensslCmsExitStatus.INPUT_FILE_READ_ERROR elif retcode == OpensslCmsExitStatus.COMMAND_OPTIONS_PARSING_ERROR: if err.startswith('cms: Cannot open input file'): raise exceptions.CertificateConfigError(err) else: raise subprocess.CalledProcessError(retcode, 'openssl', output=err) elif retcode != OpensslCmsExitStatus.SUCCESS: raise subprocess.CalledProcessError(retcode, 'openssl', output=err) return output def is_pkiz(token_text): """Determine if a token is PKIZ. Checks if the string has the prefix that indicates it is a Crypto Message Syntax, Z compressed token. """ return token_text.startswith(PKIZ_PREFIX) def pkiz_sign(text, signing_cert_file_name, signing_key_file_name, compression_level=6, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): signed = cms_sign_data(text, signing_cert_file_name, signing_key_file_name, PKIZ_CMS_FORM, message_digest=message_digest) compressed = zlib.compress(signed, compression_level) encoded = PKIZ_PREFIX + base64.urlsafe_b64encode( compressed).decode('utf-8') return encoded def pkiz_uncompress(signed_text): text = signed_text[len(PKIZ_PREFIX):].encode('utf-8') unencoded = base64.urlsafe_b64decode(text) uncompressed = zlib.decompress(unencoded) return uncompressed def pkiz_verify(signed_text, signing_cert_file_name, ca_file_name): uncompressed = pkiz_uncompress(signed_text) return cms_verify(uncompressed, signing_cert_file_name, ca_file_name, inform=PKIZ_CMS_FORM) def token_to_cms(signed_text): """Convert a custom formatted token to a PEM-formatted token. See documentation for cms_to_token() for details on the custom formatting. """ copy_of_text = signed_text.replace('-', '/') lines = ['-----BEGIN CMS-----'] lines += [copy_of_text[n:n + 64] for n in range(0, len(copy_of_text), 64)] lines.append('-----END CMS-----\n') return '\n'.join(lines) def verify_token(token, signing_cert_file_name, ca_file_name): return cms_verify(token_to_cms(token), signing_cert_file_name, ca_file_name) def is_asn1_token(token): """Determine if a token appears to be PKI-based. thx to ayoung for sorting this out. base64 decoded hex representation of MII is 3082:: In [3]: binascii.hexlify(base64.b64decode('MII=')) Out[3]: '3082' re: http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf :: pg4: For tags from 0 to 30 the first octet is the identfier pg10: Hex 30 means sequence, followed by the length of that sequence. pg5: Second octet is the length octet first bit indicates short or long form, next 7 bits encode the number of subsequent octets that make up the content length octets as an unsigned binary int 82 = 10000010 (first bit indicates long form) 0000010 = 2 octets of content length so read the next 2 octets to get the length of the content. In the case of a very large content length there could be a requirement to have more than 2 octets to designate the content length, therefore requiring us to check for MIM, MIQ, etc. :: In [4]: base64.b64encode(binascii.a2b_hex('3083')) Out[4]: 'MIM=' In [5]: base64.b64encode(binascii.a2b_hex('3084')) Out[5]: 'MIQ=' Checking for MI would become invalid at 16 octets of content length 10010000 = 90 In [6]: base64.b64encode(binascii.a2b_hex('3090')) Out[6]: 'MJA=' Checking for just M is insufficient But we will only check for MII: Max length of the content using 2 octets is 3FFF or 16383. It's not practical to support a token of this length or greater in http therefore, we will check for MII only and ignore the case of larger tokens """ return token[:3] == PKI_ASN1_PREFIX @removals.remove(message='Use is_asn1_token() instead.', version='1.7.0', removal_version='2.0.0') def is_ans1_token(token): """Deprecated. This function is deprecated as of the 1.7.0 release in favor of :func:`is_asn1_token` and may be removed in the 2.0.0 release. """ return is_asn1_token(token) def cms_sign_text(data_to_sign, signing_cert_file_name, signing_key_file_name, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): return cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, message_digest=message_digest) def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, outform=PKI_ASN1_FORM, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): """Use OpenSSL to sign a document. Produces a Base64 encoding of a DER formatted CMS Document http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax :param data_to_sign: data to sign :param signing_cert_file_name: path to the X509 certificate containing the public key associated with the private key used to sign the data :param signing_key_file_name: path to the private key used to sign the data :param outform: Format for the signed document PKIZ_CMS_FORM or PKI_ASN1_FORM :param message_digest: Digest algorithm to use when signing or resigning """ _ensure_subprocess() if isinstance(data_to_sign, six.string_types): data = bytearray(data_to_sign, encoding='utf-8') else: data = data_to_sign process = subprocess.Popen(['openssl', 'cms', '-sign', '-signer', signing_cert_file_name, '-inkey', signing_key_file_name, '-outform', 'PEM', '-nosmimecap', '-nodetach', '-nocerts', '-noattr', '-md', message_digest, ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, signing_key_file_name)) if retcode != OpensslCmsExitStatus.SUCCESS or ('Error' in err): if retcode == OpensslCmsExitStatus.CREATE_CMS_READ_MIME_ERROR: LOG.error('Signing error: Unable to load certificate - ' 'ensure you have configured PKI with ' '"keystone-manage pki_setup"') else: LOG.error('Signing error: %s', err) raise subprocess.CalledProcessError(retcode, 'openssl') if outform == PKI_ASN1_FORM: return output.decode('utf-8') else: return output def cms_sign_token(text, signing_cert_file_name, signing_key_file_name, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): output = cms_sign_data(text, signing_cert_file_name, signing_key_file_name, message_digest=message_digest) return cms_to_token(output) def cms_to_token(cms_text): """Convert a CMS-signed token in PEM format to a custom URL-safe format. The conversion consists of replacing '/' char in the PEM-formatted token with the '-' char and doing other such textual replacements to make the result marshallable via HTTP. The return value can thus be used as the value of a HTTP header such as "X-Auth-Token". This ad-hoc conversion is an unfortunate oversight since the returned value now does not conform to any of the standard variants of base64 encoding. It would have been better to use base64url encoding (either on the PEM formatted text or, perhaps even better, on the inner CMS-signed binary value without any PEM formatting). In any case, the same conversion is done in reverse in the other direction (for token verification), so there are no correctness issues here. Note that the non-standard encoding of the token will be preserved so as to not break backward compatibility. The conversion issue is detailed by the code author in a blog post at http://adam.younglogic.com/2014/02/compressed-tokens/. """ start_delim = '-----BEGIN CMS-----' end_delim = '-----END CMS-----' signed_text = cms_text signed_text = signed_text.replace('/', '-') signed_text = signed_text.replace(start_delim, '') signed_text = signed_text.replace(end_delim, '') signed_text = signed_text.replace('\n', '') return signed_text def cms_hash_token(token_id, mode='md5'): """Hash PKI tokens. return: for asn1 or pkiz tokens, returns the hash of the passed in token otherwise, returns what it was passed in. """ if token_id is None: return None if is_asn1_token(token_id) or is_pkiz(token_id): hasher = hashlib.new(mode) if isinstance(token_id, six.text_type): token_id = token_id.encode('utf-8') hasher.update(token_id) return hasher.hexdigest() else: return token_id python-keystoneclient-4.0.0/keystoneclient/_discover.py0000664000175000017500000002657013644407055023525 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneclient 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 logging import re from keystoneclient import exceptions from keystoneclient.i18n import _ _LOGGER = logging.getLogger(__name__) def get_version_data(session, url, authenticated=None): """Retrieve raw version data from a url.""" headers = {'Accept': 'application/json'} resp = session.get(url, headers=headers, authenticated=authenticated) try: body_resp = resp.json() except ValueError: # nosec(cjschaef): raise a DiscoveryFailure below 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): # nosec(cjschaef): attempt to return # versions dict or query the endpoint or raise a DiscoveryFailure pass # Most servers don't have a 'values' element so accept a simple # versions dict if available. try: return body_resp['versions'] except KeyError: # nosec(cjschaef): query the endpoint or raise a # DiscoveryFailure 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: # nosec(cjschaef): raise a DiscoveryFailure pass err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text msg = _('Invalid Response - Bad version data returned: %s') % err_text raise exceptions.DiscoveryFailure(msg) def normalize_version_number(version): """Turn a version representation into a tuple.""" # trim the v from a 'v2.0' or similar try: version = version.lstrip('v') except AttributeError: # nosec(cjschaef): 'version' is not a str, try a # different type or raise a TypeError pass # if it's an integer or a numeric as a string then normalize it # to a string, this ensures 1 decimal point try: num = float(version) except Exception: # nosec(cjschaef): 'version' is not a float, try a # different type or raise a TypeError pass else: version = str(num) # if it's a string (or an integer) from above break it on . try: return tuple(map(int, version.split('.'))) except Exception: # nosec(cjschaef): 'version' is not str (or an int), # try a different type or raise a TypeError pass # last attempt, maybe it's a list or iterable. try: return tuple(map(int, version)) except Exception: # nosec(cjschaef): 'version' is not an expected type, # raise a TypeError pass raise TypeError(_('Invalid version specified: %s') % version) 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 class Discover(object): CURRENT_STATUSES = ('stable', 'current', 'supported') DEPRECATED_STATUSES = ('deprecated',) EXPERIMENTAL_STATUSES = ('experimental',) def __init__(self, session, url, authenticated=None): 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 def version_data(self, **kwargs): """Get normalized version data. Return version data in a structured way. :returns: A list of version data dictionaries sorted by version number. Each data element in the returned list is a dictionary consisting of at least: :version tuple: The normalized version of the endpoint. :url str: The url for the endpoint. :raw_status str: The status as provided by the server :rtype: list(dict) """ if kwargs.pop('unstable', None): kwargs.setdefault('allow_experimental', True) kwargs.setdefault('allow_unknown', True) 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) for link in links: try: rel = link['rel'] url = link['href'] except (KeyError, TypeError): _LOGGER.info('Skipping invalid version link. ' 'Missing link URL or relationship.') continue if rel.lower() == 'self': break else: _LOGGER.info('Skipping invalid version data. ' 'Missing link to endpoint.') continue versions.append({'version': version_number, 'url': url, 'raw_status': v['status']}) versions.sort(key=lambda v: v['version']) return versions def data_for(self, version, **kwargs): """Return endpoint data for a version. :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) version_data = self.version_data(**kwargs) for data in reversed(version_data): if version_match(version, data['version']): return data return None def url_for(self, version, **kwargs): """Get the endpoint url for a version. :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 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 get_catalog_discover_hack(service_type, url): """Apply the catalog hacks and figure out an unversioned endpoint. This function is internal to keystoneclient. :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. """ return _VERSION_HACKS.get_discover_hack(service_type, url) python-keystoneclient-4.0.0/playbooks/0000775000175000017500000000000013644407143020125 5ustar zuulzuul00000000000000python-keystoneclient-4.0.0/playbooks/run-ds-tox.yaml0000664000175000017500000000010413644407055023026 0ustar zuulzuul00000000000000- hosts: all roles: - run-devstack - ensure-tox - tox python-keystoneclient-4.0.0/playbooks/tox-post.yaml0000664000175000017500000000011013644407055022600 0ustar zuulzuul00000000000000- hosts: all roles: - fetch-tox-output - fetch-subunit-output python-keystoneclient-4.0.0/.mailmap0000664000175000017500000000035413644407055017547 0ustar zuulzuul00000000000000# Format is: # #