diff --git a/.gitignore b/.gitignore index 6b6f10dee..f24746a37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .coverage -.testrepository +.stestr/ subunit.log .venv *,cover diff --git a/.gitreview b/.gitreview index 56224f5de..e26745728 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] -host=review.openstack.org +host=review.opendev.org port=29418 project=openstack/python-keystoneclient.git diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 000000000..73c0a5172 --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_path=${OS_TEST_PATH:-./keystoneclient/tests/unit} +top_dir=./ + diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 3d3e1e6ee..000000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystoneclient/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 000000000..8b2b1a6d1 --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,31 @@ +- 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-python3-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 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b7139eccc..604d3ac77 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,7 +1,7 @@ If you would like to contribute to the development of OpenStack, you must follow the steps documented at: - http://docs.openstack.org/infra/manual/developers.html + 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 @@ -9,7 +9,7 @@ development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. diff --git a/HACKING.rst b/HACKING.rst index 0dfef9905..6ea94ff6f 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -2,7 +2,7 @@ Keystone Style Commandments =========================== - Step 1: Read the OpenStack Style Commandments - http://docs.openstack.org/developer/hacking/ + https://docs.openstack.org/hacking/latest/ - Step 2: Read on Exceptions @@ -17,7 +17,7 @@ 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 http://wiki.openstack.org/testr. If you'd like to learn more +be found at https://wiki.openstack.org/testr. If you'd like to learn more in depth: https://testtools.readthedocs.org/ diff --git a/README.rst b/README.rst index 6b01afd27..6b27711a8 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,19 @@ +======================== +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.python.org/pypi/python-keystoneclient/ + :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version -.. image:: https://img.shields.io/pypi/dm/python-keystoneclient.svg - :target: https://pypi.python.org/pypi/python-keystoneclient/ - :alt: Downloads - 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 @@ -22,16 +27,18 @@ OpenStack's Identity Service. For command line interface support, use * `Source`_ * `Specs`_ * `How to Contribute`_ +* `Release Notes`_ -.. _PyPi: https://pypi.python.org/pypi/python-keystoneclient -.. _Online Documentation: http://docs.openstack.org/developer/python-keystoneclient +.. _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://git.openstack.org/cgit/openstack/python-keystoneclient -.. _OpenStackClient: https://pypi.python.org/pypi/python-openstackclient -.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html -.. _Specs: http://specs.openstack.org/openstack/keystone-specs/ +.. _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: diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index efceab818..000000000 --- a/babel.cfg +++ /dev/null @@ -1 +0,0 @@ -[python: **.py] diff --git a/bindep.txt b/bindep.txt index bcd43fb87..fde5b372e 100644 --- a/bindep.txt +++ b/bindep.txt @@ -1,8 +1,9 @@ # This is a cross-platform list tracking distribution packages needed by tests; -# see http://docs.openstack.org/infra/bindep/ for additional information. +# see https://docs.openstack.org/infra/bindep/ for additional information. gettext -libssl-dev +libssl-dev [platform:dpkg] +openssl-devel [platform:rpm] dbus-devel [platform:rpm] dbus-glib-devel [platform:rpm] @@ -13,11 +14,8 @@ 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] +python3-devel [platform:rpm] diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 000000000..6c96d9ac2 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,7 @@ +# These are needed for docs generation +openstackdocstheme>=2.2.1 # Apache-2.0 +sphinx>=2.0.0 # BSD +sphinxcontrib-apidoc>=0.2.0 # BSD +reno>=3.1.0 # Apache-2.0 +lxml>=3.4.1 # BSD +fixtures>=3.0.0 # Apache-2.0/BSD diff --git a/doc/source/conf.py b/doc/source/conf.py index 1d5cb109c..9c984584d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # python-keystoneclient documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # @@ -12,14 +10,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -from __future__ import unicode_literals - import os -import subprocess import sys -import warnings - -import pbr.version sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), @@ -35,11 +27,11 @@ # 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', +extensions = ['sphinxcontrib.apidoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', - 'oslosphinx', + 'openstackdocstheme', ] todo_include_todos = True @@ -57,18 +49,8 @@ master_doc = 'index' # General information about the project. -project = 'python-keystoneclient' copyright = 'OpenStack Contributors' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -version_info = pbr.version.VersionInfo('python-keystoneclient') -# The short X.Y version. -version = version_info.version_string() -# The full version, including alpha/beta/rc tags. -release = version_info.release_string() - # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None @@ -102,7 +84,7 @@ #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['keystoneclient.'] @@ -118,6 +100,7 @@ # 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 @@ -148,17 +131,6 @@ # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['static'] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local", - "-n1"] -try: - html_last_updated_fmt = subprocess.Popen(git_cmd, - stdout=subprocess.PIPE).communicate()[0] -except Exception: - warnings.warn('Cannot get last updated time from git repository. ' - 'Not setting "html_last_updated_fmt".') - # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True @@ -193,6 +165,14 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'python-keystoneclientdoc' +# -- sphinxcontrib.apidoc configuration -------------------------------------- + +apidoc_module_dir = '../../keystoneclient' +apidoc_output_dir = 'api' +apidoc_excluded_paths = [ + 'fixture', + 'tests', +] # -- Options for LaTeX output ------------------------------------------------- @@ -206,10 +186,9 @@ # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ - ('index', 'python-keystoneclient.tex', + ('index', 'doc-python-keystoneclient.tex', 'python-keystoneclient Documentation', - 'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss', - 'manual'), + 'OpenStack', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -229,9 +208,27 @@ # If false, no module index is generated. #latex_use_modindex = True -keystoneauth_url = 'http://docs.openstack.org/developer/keystoneauth/' +# 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': ('http://docs.python.org/', None), - 'osloconfig': ('http://docs.openstack.org/developer/oslo.config/', None), + 'python': ('https://docs.python.org/', None), + 'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None), 'keystoneauth1': (keystoneauth_url, None), } + +# -- Options for openstackdocstheme ------------------------------------------- +openstackdocs_repo_name = 'openstack/python-keystoneclient' +openstackdocs_bug_project = 'python-keystoneclient' +openstackdocs_bug_tag = '' +openstackdocs_pdf_link = True diff --git a/doc/source/history.rst b/doc/source/history.rst deleted file mode 100644 index 69ed4fe6c..000000000 --- a/doc/source/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../ChangeLog diff --git a/doc/source/index.rst b/doc/source/index.rst index c5f526094..f1114b67c 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,3 +1,4 @@ +======================================================== Python bindings to the OpenStack Identity API (Keystone) ======================================================== @@ -21,27 +22,27 @@ 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`: http://docs.openstack.org/developer/keystone/ -.. _`WSGI Middleware`: http://docs.openstack.org/developer/keystonemiddleware/ +.. _`Identity Service`: https://docs.openstack.org/keystone/latest/ +.. _`WSGI Middleware`: https://docs.openstack.org/keystonemiddleware/latest/ Release Notes ============= -.. toctree:: - :maxdepth: 1 - history +Read also the `Keystoneclient Release Notes +`_. + Contributing ============ -Code is hosted `on GitHub`_. Submit bugs to the Keystone project on +Code is hosted `on OpenDev`_. 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 +.. _on OpenDev: https://opendev.org/openstack/python-keystoneclient .. _Launchpad: https://launchpad.net/python-keystoneclient -.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow +.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox``. diff --git a/doc/source/using-api-v2.rst b/doc/source/using-api-v2.rst index 1b7c5deaf..7b0815f49 100644 --- a/doc/source/using-api-v2.rst +++ b/doc/source/using-api-v2.rst @@ -79,7 +79,7 @@ This example will create a tenant named *openstackDemo*:: >>> keystone = client.Client(...) >>> keystone.tenants.create(tenant_name="openstackDemo", ... description="Default Tenant", enabled=True) - + Creating users ============== diff --git a/doc/source/using-api-v3.rst b/doc/source/using-api-v3.rst index 8e2093da2..4f305e81f 100644 --- a/doc/source/using-api-v3.rst +++ b/doc/source/using-api-v3.rst @@ -8,6 +8,7 @@ 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` @@ -101,6 +102,31 @@ 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) ======================================= diff --git a/examples/pki/certs/cacert.pem b/examples/pki/certs/cacert.pem index 952bdaea3..6519671a5 100644 --- a/examples/pki/certs/cacert.pem +++ b/examples/pki/certs/cacert.pem @@ -1,23 +1,23 @@ -----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= +MIID4TCCAsmgAwIBAgIUD+MH2KCtZgLgFWNghxF+xZQZx98wDQYJKoZIhvcNAQEL +BQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAG +A1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsMCEtl +eXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQw +EgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0yMjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1 +MzUxOFowgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES +MBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsM +CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn +MRQwEgYDVQQDDAtTZWxmIFNpZ25lZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAL/+AsUcqyF2b/3gaHGtUZx+mReX2QMv+gXmW2KUj1CTmSTxGXCaeoJ5 +N7PA6BeBD/HJVqTgCo/oNuHmOgtYrgRngyWpABItt9ONRmTCr2AvA23AZIjfUdwR +ZceRHf67H6N1NOttr8IFkuQFhTAKuRHJGcXNqNMJrNv2v5ha3GNeAhxZd965ok9B +GSd+hvibjZ2mDBZ8kiJ9BGf53TDie/zg+q5CkgqLArgR30pGCe+ZLXPLrhekpyet +BR3guKTV2PMCeIh7Yg/uTJMe22qZ87M6Q1DosSKC/1l+/1ArBve6msc8JEElnc32 +HJ7NuTTJreZKEvPmUI2oTcdvokWXtRsCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEAmdWRPzpI5pAoVn9GX1KNgiN/e9oCpnHYIofV +fX7i70OBYOwBoyQhFqniHtGH4uqYIxGJdbDtHzsSYCSV1mGqHhK+kStLy4MULUUV +cNM5yDYDPEtjgJy7G90z/ksX5WuXQgktx9N8emdI6yH8C1b6sHMtHcfnb6O0waMH +HQ9QpZFQapwuIjeWU0zRDFZkdEAkx6wfVuoMhHOjy1WRAuIOL2ELa6h0GL2d+bmw +x4Xpyi4X7pgixz3l/9Kfc6VdVrEy4H2bhldUeZ0WjzvMdYaw953+C/5YAfFYanCH +en9BebSKQMv8QI0OrNyTefMXuxWvcKSOWjQVfRk1ckz6aIrfSw== -----END CERTIFICATE----- diff --git a/examples/pki/certs/middleware.pem b/examples/pki/certs/middleware.pem deleted file mode 100644 index 7d593efd7..000000000 --- a/examples/pki/certs/middleware.pem +++ /dev/null @@ -1,50 +0,0 @@ ------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----- diff --git a/examples/pki/certs/signing_cert.pem b/examples/pki/certs/signing_cert.pem index 63ab2478d..6428be8d0 100644 --- a/examples/pki/certs/signing_cert.pem +++ b/examples/pki/certs/signing_cert.pem @@ -1,22 +1,22 @@ -----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== +MIIDpTCCAo0CAREwDQYJKoZIhvcNAQELBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQK +DAlPcGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr +ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0y +MjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1MzUxOFowgY8xCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3Rh +Y2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv +cGVuc3RhY2sub3JnMREwDwYDVQQDDAhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAJ7Gc/CGy82PWOmlGD+C8d6Kw3Q1ZodOz9EduhXecfLN ++zHXd9Qd35f5hHf4c8j0yAxI8qfeabjnOefEkoHC4W+DTTsUwq1x7FvAPHbTncSH +zePqBE8wKp4pfFuZAhMxP55nTJC/N8t1M1lUqYTANgTCAystyOJuLuMc03UBqO8b +OGzCJsAdcsBPpdYkfycrWv5ZosdaHf3OmakPtWymjSPQ8/lM4x5Fm5GaoYUqb9mo +busMp0te7MMkzWYilSqZBWHx7dsGR7HoN4zMqalttC0inJGc0wnusNrkeb3ieuXw +U6T3V3pE3yTTuHy6HcZZd/8m3O/L9F1odzDnUH10PjkCAwEAATANBgkqhkiG9w0B +AQsFAAOCAQEAiSaPtpERflBUDYtRrAPVEyM3K9DJyZ8pv1vQxCU/h4ZNttVWsRdC +gqdZg8nYSLj81ZwU1OATQhjXjGn9/mYAIzbm+HH1TMJDWqmnkSblAHGPZmswKmga +/Cns8PsgsLcMV9BA38lyBhVtgBn4QgsG9EUvscZvVUnxevgqg3a/tlfpPf7fvbmC +Efcq3liI/l+wxv4O3ET3V6rBZsmTUMNrIIhqFcicynUy3NIIRO3mL92se9X81Jpj +YxHtMt+RakM0P7yRYL2hjgQW2srssGlMt9U/OIEZQKJVBH85qYuoBAcXC7Y6xRy1 +LvQc4IKf3X4hmqZC7jhBIQAAbaDZTn8peg== -----END CERTIFICATE----- diff --git a/examples/pki/certs/ssl_cert.pem b/examples/pki/certs/ssl_cert.pem index cdd2e4c02..00d33a7bb 100644 --- a/examples/pki/certs/ssl_cert.pem +++ b/examples/pki/certs/ssl_cert.pem @@ -1,22 +1,22 @@ -----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= +MIIDpjCCAo4CARAwDQYJKoZIhvcNAQELBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQK +DAlPcGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr +ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0y +MjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1MzUxOFowgZAxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3Rh +Y2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv +cGVuc3RhY2sub3JnMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQD1i9+ydZSypNAkkVXdzIqZ8E62cqH7i0JGVGBuGdH8 +ZVF3MDcbi8VwqfNRWoWn9mrJUp5HYDV9t5WXz25Ej4EnqlJ3WLZvC1e+ldDIInmi +ULic3iIAgrWbumU3XNLHska/smoVJuLIuFUxEfRpwGOpguOzAO1M6BKCSwr+TBLY +JZxc3F7v1vtwNhisyE5S2H6Q49K0UXHTPjp+fLZAHQ5+Yxqwf0KAJqAD3vMo8Ewx +XlgJu8pQyjjxwtrwnN2WHYoJGt/OOdkLBbzdupWH9CGcxeVc5hSJ1hEKuYS8AOZI +eH6q2MKwT+QiepBsVfuy1JFt4raLht/RcX/WR8lIAzoFAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAArxwP6I5XXIl3Dhkkt6gegRNc1vYWPEIDkKqggnvntZAOZwavVQ +kiydT09io82SjD3qv/PQFH+N1KkTCIgYreHQpCQaWMvkpCD2iEcu9R75p4rnZMR2 +NwIlj4BHvXIo9ET5dkhUUxzUGK7eIymNEoMWMF6OGlQhK1FV3Tvjum0sqLOyKOgr +NFxDv7qFzoKfqjY3lfb9yqO7xC1t3CZSOsBLIaUQ9SBoRJ11UNYGq9ZXHNF3cCbC +PyE1TgVjNEvWBBRY0ofGoPmdqrTe2oZ6rAFKf0aWJ1qIq+umePp9R8ZwWLonbxQ4 +0nqaUI5AOAdsRpUJHGvW0mmMALYjHT+tF8U= -----END CERTIFICATE----- diff --git a/examples/pki/cms/auth_token_revoked.pem b/examples/pki/cms/auth_token_revoked.pem index 12e4f0ce7..b0312a9b0 100644 --- a/examples/pki/cms/auth_token_revoked.pem +++ b/examples/pki/cms/auth_token_revoked.pem @@ -1,79 +1,79 @@ -----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= +MIIOVQYJKoZIhvcNAQcCoIIORjCCDkICAQExDTALBglghkgBZQMEAgEwggxaBgkq +hkiG9w0BBwGgggxLBIIMR3sNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r +ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDM4LTAxLTE4VDIxOjE0 +OjA3WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 +MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg +ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h +bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg +ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg +ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg +IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN +CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg +ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K +ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu +MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov +LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 +MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 +cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli +YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg +XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg +ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg +ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K +ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg +ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw +Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg +ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg +ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg +ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg +ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd +LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg +ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo +dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj +Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog +InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV +UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 +ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 +YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj +NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K +ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t +cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg +ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 +c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog +ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh +ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj +VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg +ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi +dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 +c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy +Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUx +IiwNCiAgICAgICAgICAgICJyb2xlc19saW5rcyI6IFsNCiAgICAgICAgICAgICAg +ICAicm9sZTEiLA0KICAgICAgICAgICAgICAgICJyb2xlMiINCiAgICAgICAgICAg +IF0sDQogICAgICAgICAgICAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsDQogICAg +ICAgICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAg +ICAgICAgICAgICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2 +MzEiLA0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAg +ICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAg +ICAgICAgImlkIjogImYwM2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwN +CiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAg +ICAgICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInJl +dm9rZWRfdXNlcm5hbWUxIg0KICAgICAgICB9DQogICAgfQ0KfQ0KMYIBzjCCAcoC +AQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES +MBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsM +CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn +MRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEwDQYJKoZIhvcN +AQEBBQAEggEAGUm9+Jb4P4dO23cAg39q0vVDFPkiPxgxakKE+g4d7VSI7Krt5ypB +iXo4mHbLqM28zrxgCBxnq2ZhhGzk/qhVWadYWUQQ9FjUBna06Cbd5clGpP8Kp2yk ++SRydFZAw9jUviditNhZA7Nhl8Qzu6T9TB+jPI2y1PY/XXebN97dQqmc+QMGVfzz +ssU+89ASfki8vEDJWxn2RtxjaXPKeWFUw/qrvj1RkDdI3d22dOTR9p0q7Y9CKLam +Y1DkosGbBqUF8hTtJMXIfHTLh5Zp+zz1dlsY0FR9kxLKxKya9OG3ACyidL7ewM9r +pEz2NQztAoi3Guxj6793G0Sfgb0ZCTGcaA== -----END CMS----- diff --git a/examples/pki/cms/auth_token_scoped.pem b/examples/pki/cms/auth_token_scoped.pem index 8754a959c..2974358ba 100644 --- a/examples/pki/cms/auth_token_scoped.pem +++ b/examples/pki/cms/auth_token_scoped.pem @@ -1,78 +1,79 @@ -----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== +MIIOPwYJKoZIhvcNAQcCoIIOMDCCDiwCAQExDTALBglghkgBZQMEAgEwggxEBgkq +hkiG9w0BBwGgggw1BIIMMXsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r +ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDM4LTAxLTE4VDIxOjE0 +OjA3WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 +MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg +ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h +bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg +ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg +ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg +IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN +CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg +ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K +ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu +MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov +LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 +MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 +cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli +YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg +XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg +ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg +ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K +ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg +ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw +Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg +ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg +ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg +ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg +ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd +LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg +ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo +dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj +Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog +InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV +UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 +ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 +YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj +NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K +ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t +cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg +ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 +c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog +ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh +ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj +VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg +ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi +dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 +c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy +Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg +ICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xl +MSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAg +ICAgICAgICAgICJpZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMi +OiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQi +OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg +ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgICAgICB9LA0K +ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogImYw +M2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAg +ICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAgICAgfQ0KICAgICAg +ICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAg +ICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgGA1UEBRMBNTEL +MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxEjAQ +BgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3RvbmUxJTAjBgkqhkiG9w0B +CQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMMC1NlbGYgU2lnbmVk +AgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQBYn04AtTif863EsRze +H4qwhQwcDMEy9+tqm6Pof1ysl7wnqtyeJbUaEmljzYOeiYvcI9tOAj/beTpxbU3A +6vo0XkPt82Fdn3tPu2Rbr5cmCWkqRUAazEXBwfwPFBeA01Th2yxtTNUeiZJyjbcO +6DfeXeQSQWkblB375+2RQNGOuEbU1JwC8sErk8eTetA419fh+tn5m3h/FhHRiWGo +2NWy9HRx0OjCnnjNtIWY++QbVLQS7lty/f3E1c3l8ebGhBXleEmWbpp1zUi9e5oK +0NlVB+nwiw9qkzAgX5ForSaFGkGlHjjAtoIs/i3DgP+3ET/pZkislu6NpiXfklY4 +o35g -----END CMS----- diff --git a/examples/pki/cms/auth_token_scoped_expired.pem b/examples/pki/cms/auth_token_scoped_expired.pem index 43e09f333..9c4bdb9e1 100644 --- a/examples/pki/cms/auth_token_scoped_expired.pem +++ b/examples/pki/cms/auth_token_scoped_expired.pem @@ -1,76 +1,79 @@ -----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== +MIIOPwYJKoZIhvcNAQcCoIIOMDCCDiwCAQExDTALBglghkgBZQMEAgEwggxEBgkq +hkiG9w0BBwGgggw1BIIMMXsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r +ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDEwLTA2LTAyVDE0OjQ3 +OjM0WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 +MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg +ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h +bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg +ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg +ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg +IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN +CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg +ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K +ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu +MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov +LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 +MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 +cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli +YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg +XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg +ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg +ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K +ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg +ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw +Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg +ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg +ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg +ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg +ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd +LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg +ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo +dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj +Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog +InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV +UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 +ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 +YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj +NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K +ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t +cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg +ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 +c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog +ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh +ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj +VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg +ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi +dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 +c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy +Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg +ICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xl +MSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAg +ICAgICAgICAgICJpZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMi +OiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQi +OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg +ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgICAgICB9LA0K +ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogImYw +M2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAg +ICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAgICAgfQ0KICAgICAg +ICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAg +ICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgGA1UEBRMBNTEL +MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxEjAQ +BgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3RvbmUxJTAjBgkqhkiG9w0B +CQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMMC1NlbGYgU2lnbmVk +AgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQCFiZDCR4kvDWvNVo3l +306T6uMi+CuLklTr9msrA4rhRDROZ6N8y/0TrpnBInhJC9A5OGnRgiAPFrg3ksLP +ONqqtdQSgNLnzVjauJzVvejzOIYtnp6quQzxy+B1xS/QX+8ODgxz8PcvFszWDvBx +qDi/q3XAU2fvdRkq96WBGkqjAOb1pxHA1WbOklcjAm/PL5qgcFc+aryiVvPVBjFy +1KfOZjncXtDBB0Bz5+7MxAxej7LhRZ/eqlK2A/mn2vIlvPKLTGdfuQ10aIBtJ5lW +cP2miCjk179e2OU71eJdpJk1bFBXNoNbeu7dhI6/W65SKo/EbEgLO07NXW4qqcVQ +vnt9 -----END CMS----- diff --git a/examples/pki/cms/auth_token_unscoped.pem b/examples/pki/cms/auth_token_unscoped.pem index 274ac3860..bfb97968f 100644 --- a/examples/pki/cms/auth_token_unscoped.pem +++ b/examples/pki/cms/auth_token_unscoped.pem @@ -1,29 +1,29 @@ -----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= +MIIE/gYJKoZIhvcNAQcCoIIE7zCCBOsCAQExDTALBglghkgBZQMEAgEwggMDBgkq +hkiG9w0BBwGgggL0BIIC8HsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r +ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMTEyLTA4LTE3VDE1OjM1 +OjM0WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 +MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICIwMWUwMzJjOTk2ZWY0NDA2YjE0 +NDMzNTkxNWE0MWU3OSINCiAgICAgICAgfSwNCiAgICAgICAgInNlcnZpY2VDYXRh +bG9nIjoge30sDQogICAgICAgICJ1c2VyIjogew0KICAgICAgICAgICAgInVzZXJu +YW1lIjogInVzZXJfbmFtZTEiLA0KICAgICAgICAgICAgInJvbGVzX2xpbmtzIjog +W10sDQogICAgICAgICAgICAiaWQiOiAiYzljODllM2JlM2VlNDUzZmJmMDBjNzk2 +NmY2ZDNmYmQiLA0KICAgICAgICAgICAgInJvbGVzIjogWw0KICAgICAgICAgICAg +ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogIjM1OWRhNDJkMzFjMDQ0 +MzdhMzI4MTJhZWI3OWU5YzBiIiwNCiAgICAgICAgICAgICAgICAgICAgIm5hbWUi +OiAicm9sZTEiDQogICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICB7 +DQogICAgICAgICAgICAgICAgICAgICJpZCI6ICI1ODFhZjE5NzI2ZmE0YWY1YmRh +NzQ1Nzg5YWIyYmYyYiIsDQogICAgICAgICAgICAgICAgICAgICJuYW1lIjogInJv +bGUyIg0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIF0sDQogICAgICAg +ICAgICAibmFtZSI6ICJ1c2VyX25hbWUxIg0KICAgICAgICB9DQogICAgfQ0KfQ0K +MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD +VQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sx +ETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu +c3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEw +DQYJKoZIhvcNAQEBBQAEggEAYIiuAmAxllZ5FdGlJWu0bxAsxwFb114lZAq2/mju +v2Hu9505N4TbkJJI++ojNwv1extCAL0H7jmM2nbPovPa0R6RVDRgh1R03E+uLNld +rBXyiTeZh2OrbmoXdHAXdJRnBkLIfjDXISY5qN/yJhsr3g6djekGnkWzZfwtwjyb +qBbVVB9oK9p5svd85jiOcGlcBxuBhWeqwZiYTzyDtJ2SuwtvBaIDQICyS9VtGI+F +NzqtdUHw/vxsQqOq6tR4Qf32wtZnUS+komQWX3uJXwYpSZHomuGPbs0XolGXYm8D +fM/7R9AH6CUlmBmq/WVQdEMMv9VADaP9yHuGvM8w3f6ZvA== -----END CMS----- diff --git a/examples/pki/cms/auth_v3_token_revoked.pem b/examples/pki/cms/auth_v3_token_revoked.pem index ca2bf06be..0b1ecbf4a 100644 --- a/examples/pki/cms/auth_v3_token_revoked.pem +++ b/examples/pki/cms/auth_v3_token_revoked.pem @@ -1,123 +1,125 @@ -----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 +MIIW/gYJKoZIhvcNAQcCoIIW7zCCFusCAQExDTALBglghkgBZQMEAgEwghUDBgkq +hkiG9w0BBwGgghT0BIIU8HsNCiAgICAidG9rZW4iOiB7DQogICAgICAgICJjYXRh +bG9nIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJlbmRwb2lu +dHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAg +ICAgICAgICJpZCI6ICIzYjVlNTU0YmNmMTE0ZjI0ODNlOGExYmU3YTA1MDZkMSIs +DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImFkbWluIiwN +CiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAu +MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg +ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAg +ICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAg +ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiNTRhYmQyZGM0NjNjNGJhNGE3Mjkx +NTQ5OGY4ZWNhZDEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj +ZSI6ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjog +Imh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj +Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog +InJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAg +ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjcwYTdl +ZmE0YjFiOTQxOTY4MzU3Y2M0M2FlMTQxOWVlIiwNCiAgICAgICAgICAgICAgICAg +ICAgICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAg +ICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2Zi +Y2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAg +ICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9 +DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQiOiAiNTcw +N2MzZmMwYTI5NDcwM2EzYzYzOGU5Y2Y2YTZjM2EiLA0KICAgICAgICAgICAgICAg +ICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAidm9s +dW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAg +ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg +ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiOTIyMTdhM2I5NTM5NDQ5Mjg1OWJj +NDlmZDQ3NDM4MmYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj +ZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 +dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAg +ICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwN +CiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAg +ImlkIjogImYyMDU2M2JkZjY2ZjRlZmE4YTFmMTFkOTliNjcyYmUxIiwNCiAgICAg +ICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiaW50ZXJuYWwiLA0KICAg +ICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjky +OTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdp +b25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAg +ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzNzVmOWJhNDU5 +YTQ0NzczOGZiNjBmZTVmYzI2ZTlhYSIsDQogICAgICAgICAgICAgICAgICAgICAg +ICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQogICAgICAgICAgICAgICAgICAgICAg +ICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAg +ICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAg +ICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAg +ImlkIjogIjE1YzIxYWFlNmIyNzRhOGRhNTJlMGEwNjhlOTA4YWFjIiwNCiAgICAg +ICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAgICAgICAgIm5h +bWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAg +ICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAg +ICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiZWRiZDlmNTBmNjY3 +NDZhZTllZDExZGMzYjFhZTM1ZGEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg +ImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAi +dXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz +NWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJy ZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAg ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi -OiAiZjIwNTYzYmRmNjZmNGVmYThhMWYxMWQ5OWI2NzJiZTEiLA0KICAgICAgICAg +OiAiOWUwM2M0NmM4MGEzNGExNTljYjM5ZjVjYjA0OThiOTIiLA0KICAgICAgICAg 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 +ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92 +MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAg +ICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAg +ICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAg +ICAgICAgICAgICAgICAiaWQiOiAiMWRmMGI0NGQ5MjYzNGQ1OWJkMGUwZDYwY2Y3 +Y2U0MzIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJw +dWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8v +MTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2 +NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lv +bk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s +DQogICAgICAgICAgICAgICAgImlkIjogIjJmNDA0ZmRiODkxNTRjNTg5ZWZiYzEw +NzI2YjAyOWVjIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRlIiwN +CiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAgfSwN +CiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0K +ICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAi +aWQiOiAiYTQ1MDFlMTQxYTRiNGUxNGJmMjgyZTdiZmZkODFkYzUiLA0KICAgICAg +ICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAg +ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcv +djMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25P +bmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAg +IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzZDE3ZTMyMjdiZmM0 +NDgzYjU4ZGU1ZWFhNTg0ZTM2MCIsDQogICAgICAgICAgICAgICAgICAgICAgICAi +aW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAg +ICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTozNTM1Ny92MyIsDQogICAgICAgICAg +ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSINCiAgICAgICAgICAg +ICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAg +ICAgICAgICAgICAgImlkIjogIjhjZDRiOTU3MDkwZjRjYTU4NDJhMjJlOWE3NDA5 +OWNkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVi +bGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEy +Ny4wLjAuMTo1MDAwL3YzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdp +b24iOiAiUmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAg +ICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQiOiAiYzVkOTI2ZDU2NjQy +NGU0ZmJhNGY4MGMzNzkxNmNkZTUiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjog +ImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9uZSIN +CiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgImlzc3VlZF9hdCI6 +ICIyMDAyLTAxLTE4VDIxOjE0OjA3WiIsDQogICAgICAgICJleHBpcmVzX2F0Ijog +IjIwMzgtMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImF1ZGl0X2lkcyI6IFsi +Wnp6WjJaWllxVDhPemZVVnZyakVJVFEiLCAiY0NDQ0NDY3RUek8xLVhVazVTVHli +dyJdLA0KICAgICAgICAicHJvamVjdCI6IHsNCiAgICAgICAgICAgICJlbmFibGVk +IjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6IG51bGwsDQogICAg +ICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLA0KICAgICAgICAgICAgImlk +IjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRvbWFpbiI6IHsNCiAgICAg +ICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQogICAgICAgICAgICAgICAg +Im5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAgICAgfQ0KICAgICAgICB9 +LA0KICAgICAgICAidXNlciI6IHsNCiAgICAgICAgICAgICJuYW1lIjogInJldm9r +ZWRfdXNlcm5hbWUxIiwNCiAgICAgICAgICAgICJpZCI6ICJyZXZva2VkX3VzZXJf +aWQxIiwNCiAgICAgICAgICAgICJkb21haW4iOiB7DQogICAgICAgICAgICAgICAg +ImlkIjogImRvbWFpbl9pZDEiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImRv +bWFpbl9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwNCiAgICAgICAg +InJvbGVzIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJpZCI6 +ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAg +ICAgICAgIm5hbWUiOiAicm9sZTEiDQogICAgICAgICAgICB9LA0KICAgICAgICAg +ICAgew0KICAgICAgICAgICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcw +ZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIi +DQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJtZXRob2RzIjog +Ww0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAgICBdDQogICAgfQ0KfQ0K +MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD +VQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sx +ETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu +c3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEw +DQYJKoZIhvcNAQEBBQAEggEAh4ESJgQ+2tuZrQdOUGXKi5uO56bbX5c15WdhPcHA +gXK4TUXC8Gb0pTyAGXXWkHwErf+7NHrZbA5Y8zqTor8qjig+tT8Ywh82lzY+mXOE +HgnkKNoGUmgv1auJuECjysLHX6T5c0AUOxPSBvCQtvmGl33/riX5/gM+D/Dkptul +iEOGXZc/S8qoOJE/SoJnFhimbJ7BuECKO8euTXeQrpKVyAULTm0RSQogWMTlXHzk +hY71+Qn0dpp4ZCQTKaFO2u6aYQ2fjJ3BlH8UK0edIUJ4cHqKfMm/TCiIHQ/jbEOp +cy83wzZMoDNK+7r/fxdUz7CJA1r2LrbWJMOhDVgKIYE6TA== -----END CMS----- diff --git a/examples/pki/cms/auth_v3_token_scoped.pem b/examples/pki/cms/auth_v3_token_scoped.pem index 50641147f..ae3f4f37c 100644 --- a/examples/pki/cms/auth_v3_token_scoped.pem +++ b/examples/pki/cms/auth_v3_token_scoped.pem @@ -1,123 +1,125 @@ -----BEGIN CMS----- -MIIWmgYJKoZIhvcNAQcCoIIWizCCFocCAQExCTAHBgUrDgMCGjCCFKcGCSqGSIb3 -DQEHAaCCFJgEghSUew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgIm1ldGhvZHMi -OiBbDQogICAgICAgICAgICAicGFzc3dvcmQiDQogICAgICAgIF0sDQogICAgICAg -ICJyb2xlcyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiaWQi -OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg -ICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg -ICAgIHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3 -MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogInJvbGUy -Ig0KICAgICAgICAgICAgfQ0KICAgICAgICBdLA0KICAgICAgICAiaXNzdWVkX2F0 -IjogIjIwMDItMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImV4cGlyZXNfYXQi -OiAiMjAzOC0wMS0xOFQyMToxNDowN1oiLA0KICAgICAgICAicHJvamVjdCI6IHsN -CiAgICAgICAgICAgICJpZCI6ICJ0ZW5hbnRfaWQxIiwNCiAgICAgICAgICAgICJk -b21haW4iOiB7DQogICAgICAgICAgICAgICAgImlkIjogImRvbWFpbl9pZDEiLA0K -ICAgICAgICAgICAgICAgICJuYW1lIjogImRvbWFpbl9uYW1lMSINCiAgICAgICAg -ICAgIH0sDQogICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAgICAgICAg -ICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgIm5hbWUiOiAidGVu -YW50X25hbWUxIg0KICAgICAgICB9LA0KICAgICAgICAiY2F0YWxvZyI6IFsNCiAg -ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAg -ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi -OiAiM2I1ZTU1NGJjZjExNGYyNDgzZThhMWJlN2EwNTA2ZDEiLA0KICAgICAgICAg -ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAg +MIIW7gYJKoZIhvcNAQcCoIIW3zCCFtsCAQExDTALBglghkgBZQMEAgEwghTzBgkq +hkiG9w0BBwGgghTkBIIU4HsNCiAgICAidG9rZW4iOiB7DQogICAgICAgICJtZXRo +b2RzIjogWw0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAgICBdLA0KICAg +ICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAg +ImlkIjogImYwM2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAg +ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgIH0sDQogICAg +ICAgICAgICB7DQogICAgICAgICAgICAgICAgImlkIjogImYwM2ZkYThmOGEzMjQ5 +YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJy +b2xlMiINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgImlzc3Vl +ZF9hdCI6ICIyMDAyLTAxLTE4VDIxOjE0OjA3WiIsDQogICAgICAgICJleHBpcmVz +X2F0IjogIjIwMzgtMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImF1ZGl0X2lk +cyI6IFsiVmN4VTJKWXFUOE96ZlVWdnJqRUlUUSIsICJxTlVUSUpudFR6TzEtWFVr +NVNUeWJ3Il0sDQogICAgICAgICJwcm9qZWN0Ijogew0KICAgICAgICAgICAgImlk +IjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRvbWFpbiI6IHsNCiAgICAg +ICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQogICAgICAgICAgICAgICAg +Im5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg +ICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6 +IG51bGwsDQogICAgICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAg +ICAgIH0sDQogICAgICAgICJjYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg +ICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAg +IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzYjVlNTU0YmNmMTE0 +ZjI0ODNlOGExYmU3YTA1MDZkMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAi +aW50ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1 +cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4 +YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdp +b24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAg +ICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAi +NTRhYmQyZGM0NjNjNGJhNGE3MjkxNTQ5OGY4ZWNhZDEiLA0KICAgICAgICAgICAg +ICAgICAgICAgICAgImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAgICAg 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 +ICAgICAgICAgImlkIjogIjcwYTdlZmE0YjFiOTQxOTY4MzU3Y2M0M2FlMTQxOWVl +IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVibGlj +IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4w +LjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwN +CiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0K +ICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAg +ICAgICAgICAgICAiaWQiOiAiNTcwN2MzZmMwYTI5NDcwM2EzYzYzOGU5Y2Y2YTZj +M2EiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAg +ICAgICAgICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAg +ICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAg +ICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAi +OTIyMTdhM2I5NTM5NDQ5Mjg1OWJjNDlmZDQ3NDM4MmYiLA0KICAgICAgICAgICAg +ICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAg +ICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQog +ICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAg +ICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAg +ICAgICAgICAgICAgICAgICAgICAgImlkIjogImYyMDU2M2JkZjY2ZjRlZmE4YTFm +MTFkOTliNjcyYmUxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZh +Y2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6 +ICJodHRwOi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAg +ICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAg +IH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAg +ICAgICJpZCI6ICIzNzVmOWJhNDU5YTQ0NzczOGZiNjBmZTVmYzI2ZTlhYSIsDQog +ICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQog +ICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6 +OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJl +Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAg +IF0sDQogICAgICAgICAgICAgICAgImlkIjogIjE1YzIxYWFlNmIyNzRhOGRhNTJl +MGEwNjhlOTA4YWFjIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIs +DQogICAgICAgICAgICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAg +fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjog +Ww0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAg +ICAiaWQiOiAiZWRiZDlmNTBmNjY3NDZhZTllZDExZGMzYjFhZTM1ZGEiLA0KICAg +ICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAg +ICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3 +NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAg +ICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAg +ICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAg +ICAgICAgICAgICAgICAgICAiaWQiOiAiOWUwM2M0NmM4MGEzNGExNTljYjM5ZjVj +YjA0OThiOTIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6 +ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 +dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNm +ODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAi +cmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAg +ICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMWRmMGI0 +NGQ5MjYzNGQ1OWJkMGUwZDYwY2Y3Y2U0MzIiLA0KICAgICAgICAgICAgICAgICAg +ICAgICAgImludGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAg ICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAg -fSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg -ICAgImlkIjogIjFkZjBiNDRkOTI2MzRkNTliZDBlMGQ2MGNmN2NlNDMyIiwNCiAg -ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAg -ICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4 -Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAg -ICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAg -ICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAg -ICAgICAgICJpZCI6ICIyZjQwNGZkYjg5MTU0YzU4OWVmYmMxMDcyNmIwMjllYyIs -DQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIsDQogICAgICAgICAg -ICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg -ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAg -ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogImE0NTAx -ZTE0MWE0YjRlMTRiZjI4MmU3YmZmZDgxZGM1IiwNCiAgICAgICAgICAgICAgICAg -ICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4iLA0KICAgICAgICAgICAgICAgICAg -ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAg -ICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAg -ICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAg -ICAgICAgICAgICAgICAgICAiaWQiOiAiM2QxN2UzMjI3YmZjNDQ4M2I1OGRlNWVh -YTU4NGUzNjAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6 -ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 +fQ0KICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgImlkIjogIjJm +NDA0ZmRiODkxNTRjNTg5ZWZiYzEwNzI2YjAyOWVjIiwNCiAgICAgICAgICAgICAg +ICAidHlwZSI6ICJjb21wdXRlIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJu +b3ZhIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAg +ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg +ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiYTQ1MDFlMTQxYTRiNGUxNGJmMjgy +ZTdiZmZkODFkYzUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj +ZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 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 +ICJpZCI6ICIzZDE3ZTMyMjdiZmM0NDgzYjU4ZGU1ZWFhNTg0ZTM2MCIsDQogICAg +ICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAg +ICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMToz +NTM1Ny92MyIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJl +Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAg +ICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjhjZDRiOTU3 +MDkwZjRjYTU4NDJhMjJlOWE3NDA5OWNkIiwNCiAgICAgICAgICAgICAgICAgICAg +ICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAgICAg +ICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YzIiwNCiAgICAgICAg +ICAgICAgICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAgICAg +ICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAg +ICAiaWQiOiAiYzVkOTI2ZDU2NjQyNGU0ZmJhNGY4MGMzNzkxNmNkZTUiLA0KICAg +ICAgICAgICAgICAgICJ0eXBlIjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAg +ICAibmFtZSI6ICJrZXlzdG9uZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwN +CiAgICAgICAgInVzZXIiOiB7DQogICAgICAgICAgICAiZG9tYWluIjogew0KICAg +ICAgICAgICAgICAgICJpZCI6ICJkb21haW5faWQxIiwNCiAgICAgICAgICAgICAg +ICAibmFtZSI6ICJkb21haW5fbmFtZTEiDQogICAgICAgICAgICB9LA0KICAgICAg +ICAgICAgIm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAgICAgICAiaWQiOiAi +dXNlcl9pZDEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCB +njEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQH +DAlTdW5ueXZhbGUxEjAQBgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3Rv +bmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNV +BAMMC1NlbGYgU2lnbmVkAgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASC +AQAKyFuKuktH9YR3TfKG4NAEBXDkEAkWA+fVWREyEnYoCYE2YDiqrVRwpFqR6p0V +PGRUPcDLTtPw+6p+ywNbP3F3AU0LNqjs3zl8HxvHij91CzZOQIykUcX4ToLJhBAD +shZzSucjk5y9zTLN4bl+AX/NJ/GYT2chFVjYJUW+sbIVoT5u0V9K602OV9OAyvpY +a0YzFmnEzplkg2U0qzZjx5b143ASQnaCtaf4rK7HCOIz11I6aaL3yhGSxnqE6yyA +9FdYOwB14E1GWeLVx0xeM8D/qMesqANsk0WQ19LnvKB7ry5EnDCGeBRYTMR+u0B2 +gnCYSRs9m+gg1rL889d53c/q -----END CMS----- diff --git a/examples/pki/cms/revocation_list.pem b/examples/pki/cms/revocation_list.pem index 0e81998b8..e37f9de4a 100644 --- a/examples/pki/cms/revocation_list.pem +++ b/examples/pki/cms/revocation_list.pem @@ -1,20 +1,20 @@ -----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= +MIIDVwYJKoZIhvcNAQcCoIIDSDCCA0QCAQExDTALBglghkgBZQMEAgEwggFcBgkq +hkiG9w0BBwGgggFNBIIBSXsicmV2b2tlZCI6IFt7ImV4cGlyZXMiOiAiMjExMi0w +OC0xNFQxNzo1ODo0OFoiLCAiaWQiOiAiZGI5OGVkMmFmNmM2NzA3YmVjNmRjNmM2 +ODkyNzg5YTAifSwgeyJleHBpcmVzIjogIjIxMTItMDgtMTRUMTc6NTg6NDhaIiwg +ImlkIjogIjE1Y2UwNWZkNDkxYjc5NzkxMDY4ZWQ4MGE5YzdmNWU3In0sIHsiZXhw +aXJlcyI6ICIyMTEyLTA4LTE0VDE3OjU4OjQ4WiIsICJpZCI6ICJkYjk4ZWQyYWY2 +YzY3MDdiZWM2ZGM2YzY4OTI3ODlhMCJ9LCB7ImV4cGlyZXMiOiAiMjExMi0wOC0x +NFQxNzo1ODo0OFoiLCAiaWQiOiAiMTVjZTA1ZmQ0OTFiNzk3OTEwNjhlZDgwYTlj +N2Y1ZTcifV19MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYT +AlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlP +cGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlz +dG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglg +hkgBZQMEAgEwDQYJKoZIhvcNAQEBBQAEggEAVsD6Zy+bXz9D9ZJ32CbG4ggTisgs +4vZw9xemVMQ9J8+3iLWE2Z+NrqO1T4Q81iu9cKZ/zTFuu99B5X9ZHE57QLrOd+Jb +Ld3E8HYVj8ZpGQfXAcq3ybIkT2SeEddQmW+MlHHkkfu41D1dQ8jgkIcbLjpeRCFk +GA5Zj6Xdnozos/qMLqd1o4ufJ+dQAeFH/fviLkS5fiTPK6GmhqkwBQgffkOHTo/S +xZQeq7GhFKijg5p60AZRc1Q+WlrmkY9W0FrX7bt6iJodWBc2YSEZ5Mr7BpvIu5JH +rg282z/D4Xwj+USzn7jvqZFlmkG9o6s6YKjsrIpnPFH92EWv0+D5NuOuOA== -----END CMS----- diff --git a/examples/pki/gen_cmsz.py b/examples/pki/gen_cmsz.py index 6840c08e5..35a6a8f8c 100644 --- a/examples/pki/gen_cmsz.py +++ b/examples/pki/gen_cmsz.py @@ -12,9 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. -import json import os +from oslo_serialization import jsonutils + from keystoneclient.common import cms from keystoneclient import utils @@ -44,7 +45,7 @@ def generate_revocation_list(): 'id': id, "expires": "2112-08-14T17:58:48Z" }) - revoked_json = json.dumps({"revoked": revoked_list}) + 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, @@ -83,6 +84,7 @@ def generate_der_form(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') @@ -91,12 +93,12 @@ def generate_der_form(name): # validate the JSON try: - token_data = json.loads(string_data) + 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 = json.dumps(token_data).encode('utf-8') + text = jsonutils.dumps(token_data).encode('utf-8') # Uncomment to record the token uncompressed, # useful for debugging diff --git a/examples/pki/gen_pki.sh b/examples/pki/gen_pki.sh index 8e2b59f98..f3a3fddb0 100755 --- a/examples/pki/gen_pki.sh +++ b/examples/pki/gen_pki.sh @@ -42,7 +42,7 @@ function generate_ca_conf { [ req ] default_bits = 2048 default_keyfile = cakey.pem -default_md = default +default_md = sha256 prompt = no distinguished_name = ca_distinguished_name @@ -69,7 +69,7 @@ function generate_ssl_req_conf { [ req ] default_bits = 2048 default_keyfile = keystonekey.pem -default_md = default +default_md = sha256 prompt = no distinguished_name = distinguished_name @@ -90,7 +90,7 @@ function generate_cms_signing_req_conf { [ req ] default_bits = 2048 default_keyfile = keystonekey.pem -default_md = default +default_md = sha256 prompt = no distinguished_name = distinguished_name @@ -122,7 +122,7 @@ private_key = $dir/private/cakey.pem default_days = 21360 default_crl_days = 30 -default_md = default +default_md = sha256 policy = policy_any @@ -157,14 +157,14 @@ function check_error { 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 + openssl req -x509 -newkey rsa:2048 -sha256 -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 + openssl req -newkey rsa:2048 -sha256 -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 } @@ -172,7 +172,7 @@ function ssl_cert_req { 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 + openssl req -newkey rsa:2048 -sha256 -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 } diff --git a/examples/pki/private/cakey.pem b/examples/pki/private/cakey.pem index 1c93ee18c..9c204c500 100644 --- a/examples/pki/private/cakey.pem +++ b/examples/pki/private/cakey.pem @@ -1,28 +1,28 @@ -----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= +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC//gLFHKshdm/9 +4GhxrVGcfpkXl9kDL/oF5ltilI9Qk5kk8RlwmnqCeTezwOgXgQ/xyVak4AqP6Dbh +5joLWK4EZ4MlqQASLbfTjUZkwq9gLwNtwGSI31HcEWXHkR3+ux+jdTTrba/CBZLk +BYUwCrkRyRnFzajTCazb9r+YWtxjXgIcWXfeuaJPQRknfob4m42dpgwWfJIifQRn ++d0w4nv84PquQpIKiwK4Ed9KRgnvmS1zy64XpKcnrQUd4Lik1djzAniIe2IP7kyT +HttqmfOzOkNQ6LEigv9Zfv9QKwb3uprHPCRBJZ3N9hyezbk0ya3mShLz5lCNqE3H +b6JFl7UbAgMBAAECggEAVCmMm03S8utRcrBB+LsqkHiqsb3+AriwWI+/tbo8DO12 +78vFBCij1bg/o8vHsi4AiFRjaAlSd/0queJLxZeNSR77TbIE9vMVp2ZB2n/Bk19o +mF8Dc0C6SMdTn6VMydLLrsL9fMrrhhkdaFnHJeU9db97Tcu22zRdk1taZ/ZEsEXN +5Ij4GSaylemUsdi2GKWCydDje3M60rGRWiVlMAsgyLhGRjBS0dfi2VRSbjE/Mi+d +vrHGWyKfh/Dgmt5XdljubZE6fbogXtksBl/dvHytQIyYddSTYHWJoCHb0DhBV25V +/m5SNTRoBeRtTy9s7UdMxTR9mpPscWCAonCZVh3nQQKBgQDqwE80+CaYInrNlZIM +6rX3sdHgcnlP8a2gSRfkmpTQbf5os6jHBwBp3UAqTpKZd6mjrbXXel4Hj8ccZMW9 +SqReQJiJwENxhvT5Bz7YGERZbVGvchXeeQBjhOy8D+Smv85I+F+cP/7efutrzFgf +M5mwlic5z58ijJlloGzgP4GquwKBgQDRXuJFzfbjY7DS0vgoz5Gh8GTfB/EPZkNL +ULt22Zz6coMpZ+en8HBrc7gSlAjvf3C4MAon344VIaW6bK1tx4amuJVK3gbbk7Kg +9YOkXUg60WaBeX9zed+eljV3LIcgRJ10Zu5rJ8ZCLgp1DTF1kh7afmqyufU5828b +vOOpN+5pIQKBgQC7qpGnntn7tVTHFVN00A44vgcyj1E7/9D12nknYAyns8c2nKnI +smg6OY4aREYeOfN7zlsYr9KL6P0cTdNmyE0urCVFulYwY9tjWc97oarCcwpiX6nr ++H+/D3zRu0Lnq16WJzkICIEQDhbWTr4D85Rh/yfMp5ZoYE4hWGaxvxNCEQKBgQCL +bD4N8fworGhB3E95DdCTIDxr8SPr91N0wgw0NvG8Lal+Vz0CrrCOPX8kkAPrSNhN +L2Bz8QDyvXdZT6ml4yqdt2ljc7rpWc+oNBY3zA6fbHZwXfIrecsaFjkAZVyOdmLL +8wdtwAzcYUCBdgmrm2SEZ46x+fd9Ychplj2coCxZQQKBgEtdtXU93fZ6vUUTN6Rj +DbWQPxktPClfBvxEr7jfdMLO33UziVQU9ZoXpj5fplWFtjLw09W1bNa6VYKGkcVK +ZYCHni/J440p7v9NvHZHX8kqYkFvzs0djFXzkLTzSKk6ErDMjvWUcQq7IvB3JLNo +xrS0SJBTBskGUKX/1OEP69B1 -----END PRIVATE KEY----- diff --git a/examples/pki/private/signing_key.pem b/examples/pki/private/signing_key.pem index 758c0ffe1..ddf3066d5 100644 --- a/examples/pki/private/signing_key.pem +++ b/examples/pki/private/signing_key.pem @@ -1,28 +1,28 @@ -----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== +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCexnPwhsvNj1jp +pRg/gvHeisN0NWaHTs/RHboV3nHyzfsx13fUHd+X+YR3+HPI9MgMSPKn3mm45znn +xJKBwuFvg007FMKtcexbwDx2053Eh83j6gRPMCqeKXxbmQITMT+eZ0yQvzfLdTNZ +VKmEwDYEwgMrLcjibi7jHNN1AajvGzhswibAHXLAT6XWJH8nK1r+WaLHWh39zpmp +D7Vspo0j0PP5TOMeRZuRmqGFKm/ZqG7rDKdLXuzDJM1mIpUqmQVh8e3bBkex6DeM +zKmpbbQtIpyRnNMJ7rDa5Hm94nrl8FOk91d6RN8k07h8uh3GWXf/Jtzvy/RdaHcw +51B9dD45AgMBAAECggEAVYj/6LIVlTYGZkiEmaKHfqYuyaoDBB3XIwbquuFNbcq9 +6onzihhV3l+Tl7YHWllUdBnQb9MIDY6zyUJC0xkTramEr7Ftd1cKSBt192Xldnza +1E+75pVCQFaFIit5zLEZXtKzkr8Q5dDLyvIrKNMLxuBmKJrPv/wv0jYzTLOKONUO +BRxEb2c13lvq/RqT4xpxU8wadk/tC4N1tvLGdUnq/+1+GcmswwChBPnYafY+pzF3 +ylyoQUtirIZ0TAkSBwZZGkZC137OPs6CmbBqxLcPT2swUSQpUSAglwG7PklRAW1u +2Qin1gka7J3l6erIYfeJ87C7day+3+uGtlYxplMRIQKBgQDJobPdn/YZakdI4ZaH +YNL67qtO5A410vb0Sm6UXWASAyfRq7nMG1CyWvlfTpPTeao9bBD0oKKIteW7RTo5 +mSozpKOt2Rgb3auyOkuswFbSxRuufgRDayUnyYMaHfhfp/pHEpXg/aiwNckiZ6Mi +iOwIlLN+ytyzadfXZSrlPtVpvQKBgQDJlnD1hnOtWRgWeB4uUXzJPa9MJBK7JLRW ++FSMrUxssw14vScmLO3kV/pN1J++FMkq7Y5ZmOpy34sGc3iMaM+H4JuNCvP5SwtG +626rOm5enH2sKd4BubiZcln5zrjgw/RV/a7aQQep4cQpaEgNoyTAu6BuU0fid6xc +jVQJAXnILQKBgQDII7YB2vHRMGkpsqJUJovFgHqSiFSCoLF4sxkoM7dUqcUwniCC +tOpY32yAaeLaGv4ckdQSvhAXW1Z5mLG+0oXNVTMTMVZ48oOnGa5b/18vP2/GuFdL +BGORJrj3h6AucvI+8ffLqH10yy6m8/A+K2L+8Xtp87s2a21P5J+7ha8YkQKBgB4m +fBqc02xX6PxjVtBCq9FFgpR2yL5ozPg9CBhKSyXu2dL3J4XULnh6mBtP89xwK25a +PXI1Jsurl5WNa7hEbNW7yEgeHUNp7/PZfqHpiVxpN3qqgGPtrSh2K/Lq8kfbxw2d +dat7EnRcKgSvbidsATE6XtJhblz23TayhKEcMWS5AoGBAItYfoHpgnQXk1gXal6e +incHFVTgrUQlDKZ5UQn5/HvK1Gb3mAtPbLWJZ2Ej70lW5BQnBrCLI8188Z6PJY3w +8KJ4T2KOu+9qClnezLy2A+bZ+MU+XZrVv+65vjJ5oGhARmRLmYlasBmxwUEEKqlO +ovIe7UVLD9CyaB+MFxFpFwE6 -----END PRIVATE KEY----- diff --git a/examples/pki/private/ssl_key.pem b/examples/pki/private/ssl_key.pem index 363ce94bd..d339ea213 100644 --- a/examples/pki/private/ssl_key.pem +++ b/examples/pki/private/ssl_key.pem @@ -1,28 +1,28 @@ -----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 +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD1i9+ydZSypNAk +kVXdzIqZ8E62cqH7i0JGVGBuGdH8ZVF3MDcbi8VwqfNRWoWn9mrJUp5HYDV9t5WX +z25Ej4EnqlJ3WLZvC1e+ldDIInmiULic3iIAgrWbumU3XNLHska/smoVJuLIuFUx +EfRpwGOpguOzAO1M6BKCSwr+TBLYJZxc3F7v1vtwNhisyE5S2H6Q49K0UXHTPjp+ +fLZAHQ5+Yxqwf0KAJqAD3vMo8EwxXlgJu8pQyjjxwtrwnN2WHYoJGt/OOdkLBbzd +upWH9CGcxeVc5hSJ1hEKuYS8AOZIeH6q2MKwT+QiepBsVfuy1JFt4raLht/RcX/W +R8lIAzoFAgMBAAECggEAQbeB0z1s4rMBkgfjt0z6+2A5cNMVT0FiJ3iFpnH6pVZo +i0G4PgMWgKS7nlZf1yg4RFF8UxYIuvDbdJnrpSXTJ06Ka66uhOHARh3KlwXDEBIS +lslMyF4zRM6KMFsDfrbUAJI7mhWiNJ5BDrUDeRookkGZt1rUJ/UknwJ+mri5gmdo +UYyHuelIcVZfX6mkDCKrVHCCNbdnUgWOkXtPi25n4lqjolwEeBx5y4cu4z6tkqxM +2hcnKUyj0YKScnhnTqprkbiMpPST4y8I190Q3Eo0Q8w9hvgEUa1xezfVzICScUjG +VXid7aRyq7sXGmCMWyMUAvc4+5/qanreEU1686CrMQKBgQD7FDKV1pcWGJMoL5VI +xiJ8jpDpnRIL940732ODjCQRgH4K9u40OmZ36W880q6o5ZXLLoWQIYZf5Hs1lq7f +3djqHFqqXtW7P7JU3mW5n/ejbMHlxra4uoTkjR6wUF+g2kW2y4ysI/t8HxSeaVjO +E4cJfoaNufgmgpeKa1QnLrMpqwKBgQD6W+qgx7w2dBXSkYBDhjDoRrCwmqOuWKwG +DMTDrlbu2j7hag6Xdy92CqjGAYXfhny4bpCkFLRjyrDZS4a/cObGKYYG4jVXTYfj +5cqLUiLmQFrZiK0uA7ek+qMp06FJX45iyECJlXX4gNU8e/WMfujlFLwjZ+72znQE +iTMW5xxbDwKBgEBa1vRtAmDZf66HM75peqFucVpPtjZ3By5XfcxT+VK7GpN442lj +pqwJm0d9wOLtpc1kaTuePDEMAUClFMGwvU6UYfDVSfcqxmzWbEB97h1nXPOmUWNb ++4ARY9JRZ5F1IPVPiwj8WBNibAiGfAqmGrCmS5q8FgzY4DrMc89vOuDtAoGBAJfe +aB6t6ssxcgdwwdi0LzjHoOkQdVgObBOjbTyypgNwGpLMrhtNblnxr12lkNr+Duwm +DdGqyZ57VvoJaaz5xNPSXn4QfIEAA/3H6CzJX2hDA5lP4pW2JZGLhKybtwv2Tj43 +8YZERvK+3Bs7qsFWPtqv0Ey+AGRw6knSHE65VScbAoGBAPUwAUmBZ6F22M6ftNL7 +G/qZJsP6OmDSyvJDN9SXiLvbpXQVd6RrZrni6xzwKWAFncFMVo+HWmeAavM8zxsc +a/XVSVvRj0kwg94HotHCF7/nHqYIqyF7hLZipdIphVGXx+c8dbKeVg0fgTnLRkzx +tbxgs3HWa6pgQvxtVAN4Jl3W -----END PRIVATE KEY----- diff --git a/keystoneclient/_discover.py b/keystoneclient/_discover.py index a6d572734..a27236cdc 100644 --- a/keystoneclient/_discover.py +++ b/keystoneclient/_discover.py @@ -24,16 +24,13 @@ import logging import re -from positional import positional - from keystoneclient import exceptions -from keystoneclient.i18n import _, _LI, _LW +from keystoneclient.i18n import _ _LOGGER = logging.getLogger(__name__) -@positional() def get_version_data(session, url, authenticated=None): """Retrieve raw version data from a url.""" headers = {'Accept': 'application/json'} @@ -141,7 +138,6 @@ class Discover(object): DEPRECATED_STATUSES = ('deprecated',) EXPERIMENTAL_STATUSES = ('experimental',) - @positional() def __init__(self, session, url, authenticated=None): self._data = get_version_data(session, url, authenticated=authenticated) @@ -167,8 +163,8 @@ def raw_version_data(self, allow_experimental=False, try: status = v['status'] except KeyError: - _LOGGER.warning(_LW('Skipping over invalid version data. ' - 'No stability status in version.')) + _LOGGER.warning('Skipping over invalid version data. ' + 'No stability status in version.') continue status = status.lower() @@ -210,14 +206,13 @@ def version_data(self, **kwargs): try: version_str = v['id'] except KeyError: - _LOGGER.info(_LI('Skipping invalid version data. Missing ID.')) + _LOGGER.info('Skipping invalid version data. Missing ID.') continue try: links = v['links'] except KeyError: - _LOGGER.info( - _LI('Skipping invalid version data. Missing links')) + _LOGGER.info('Skipping invalid version data. Missing links') continue version_number = normalize_version_number(version_str) @@ -227,15 +222,15 @@ def version_data(self, **kwargs): rel = link['rel'] url = link['href'] except (KeyError, TypeError): - _LOGGER.info(_LI('Skipping invalid version link. ' - 'Missing link URL or relationship.')) + _LOGGER.info('Skipping invalid version link. ' + 'Missing link URL or relationship.') continue if rel.lower() == 'self': break else: - _LOGGER.info(_LI('Skipping invalid version data. ' - 'Missing link to endpoint.')) + _LOGGER.info('Skipping invalid version data. ' + 'Missing link to endpoint.') continue versions.append({'version': version_number, diff --git a/keystoneclient/access.py b/keystoneclient/access.py index 74ca62e18..a93da0a1e 100644 --- a/keystoneclient/access.py +++ b/keystoneclient/access.py @@ -554,9 +554,9 @@ def scoped(self): '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']): + if ('serviceCatalog' in self and + self['serviceCatalog'] and + 'tenant' in self['token']): return True return False @@ -599,7 +599,7 @@ def project_id(self): try: return self['user']['tenantId'] except KeyError: # nosec(cjschaef): no 'user' key or 'tenantId' in - # 'user', attempt to retrive from 'token' or return None + # 'user', attempt to retrieve from 'token' or return None pass # pre diablo diff --git a/keystoneclient/adapter.py b/keystoneclient/adapter.py index faa61a69b..94cd81dc6 100644 --- a/keystoneclient/adapter.py +++ b/keystoneclient/adapter.py @@ -13,7 +13,6 @@ import warnings from oslo_serialization import jsonutils -from positional import positional class Adapter(object): @@ -46,7 +45,6 @@ class Adapter(object): :type logger: logging.Logger """ - @positional() def __init__(self, session, service_type=None, service_name=None, interface=None, region_name=None, endpoint_override=None, version=None, auth=None, user_agent=None, diff --git a/keystoneclient/auth/__init__.py b/keystoneclient/auth/__init__.py index eeae768fa..c9acef819 100644 --- a/keystoneclient/auth/__init__.py +++ b/keystoneclient/auth/__init__.py @@ -10,6 +10,7 @@ # 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 diff --git a/keystoneclient/auth/base.py b/keystoneclient/auth/base.py index df7521cbd..b6753cdf1 100644 --- a/keystoneclient/auth/base.py +++ b/keystoneclient/auth/base.py @@ -14,7 +14,6 @@ from debtcollector import removals from keystoneauth1 import plugin -import six import stevedore from keystoneclient import exceptions @@ -252,7 +251,7 @@ def get_options(cls): :returns: A list of Param objects describing available plugin parameters. - :rtype: list + :rtype: List """ return [] @@ -292,7 +291,7 @@ def register_argparse_arguments(cls, parser): # 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) + default = next(filter(None, env_vars), None) parser.add_argument(*args, default=default or opt.default, diff --git a/keystoneclient/auth/cli.py b/keystoneclient/auth/cli.py index d8cc820af..d18baec7e 100644 --- a/keystoneclient/auth/cli.py +++ b/keystoneclient/auth/cli.py @@ -14,7 +14,6 @@ import os from debtcollector import removals -from positional import positional from keystoneclient.auth import base @@ -24,7 +23,6 @@ version='2.1.0', removal_version='3.0.0' ) -@positional() def register_argparse_arguments(parser, argv, default=None): """Register CLI options needed to create a plugin. @@ -32,7 +30,7 @@ def register_argparse_arguments(parser, argv, default=None): 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 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. diff --git a/keystoneclient/auth/identity/access.py b/keystoneclient/auth/identity/access.py index 5849b7575..3e096b7af 100644 --- a/keystoneclient/auth/identity/access.py +++ b/keystoneclient/auth/identity/access.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient.auth.identity import base @@ -32,7 +30,6 @@ class AccessInfoPlugin(base.BaseIdentityPlugin): if using the AUTH_INTERFACE with get_endpoint. (optional) """ - @positional() def __init__(self, auth_ref, auth_url=None): super(AccessInfoPlugin, self).__init__(auth_url=auth_url, reauthenticate=False) diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py index 29ab121fd..b27d349e6 100644 --- a/keystoneclient/auth/identity/base.py +++ b/keystoneclient/auth/identity/base.py @@ -16,13 +16,10 @@ import warnings from oslo_config import cfg -from positional import positional -import six from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions -from keystoneclient.i18n import _LW LOG = logging.getLogger(__name__) @@ -33,8 +30,7 @@ def get_options(): ] -@six.add_metaclass(abc.ABCMeta) -class BaseIdentityPlugin(base.BaseAuthPlugin): +class BaseIdentityPlugin(base.BaseAuthPlugin, metaclass=abc.ABCMeta): # we count a token as valid (not needing refreshing) if it is valid for at # least this many seconds before the token expiry time @@ -317,10 +313,10 @@ def get_endpoint(self, session, service_type=None, interface=None, else: if not service_type: - LOG.warning(_LW( + LOG.warning( 'Plugin cannot return an endpoint without knowing the ' 'service type that is required. Add service_type to ' - 'endpoint filtering data.')) + 'endpoint filtering data.') return None if not interface: @@ -353,10 +349,9 @@ def get_endpoint(self, session, service_type=None, interface=None, # 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(_LW( + LOG.warning( 'Failed to contact the endpoint at %s for discovery. Fallback ' - 'to using that endpoint as the base url.'), - url) + 'to using that endpoint as the base url.', url) else: url = disc.url_for(version) @@ -368,7 +363,6 @@ def get_user_id(self, session, **kwargs): def get_project_id(self, session, **kwargs): return self.get_access(session).project_id - @positional() def get_discovery(self, session, url, authenticated=None): """Return the discovery object for a URL. diff --git a/keystoneclient/auth/identity/generic/base.py b/keystoneclient/auth/identity/generic/base.py index eab04023c..1cf3e0a9a 100644 --- a/keystoneclient/auth/identity/generic/base.py +++ b/keystoneclient/auth/identity/generic/base.py @@ -12,15 +12,14 @@ import abc import logging +import urllib.parse as urlparse 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 _, _LW +from keystoneclient.i18n import _ LOG = logging.getLogger(__name__) @@ -42,11 +41,10 @@ def get_options(): ] -@six.add_metaclass(abc.ABCMeta) -class BaseGenericPlugin(base.BaseIdentityPlugin): - """An identity plugin that is not version dependant. +class BaseGenericPlugin(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): + """An identity plugin that is not version dependent. - Internally we will construct a version dependant plugin with the resolved + 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. """ @@ -140,9 +138,9 @@ def _do_create_plugin(self, session): except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): - LOG.warning(_LW('Discovering versions from the identity service ' - 'failed when creating the password plugin. ' - 'Attempting to determine version from URL.')) + 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() diff --git a/keystoneclient/auth/identity/generic/cli.py b/keystoneclient/auth/identity/generic/cli.py index 9debf631e..de1d74895 100644 --- a/keystoneclient/auth/identity/generic/cli.py +++ b/keystoneclient/auth/identity/generic/cli.py @@ -11,7 +11,6 @@ # under the License. from oslo_config import cfg -from positional import positional from keystoneclient.auth.identity.generic import password from keystoneclient import exceptions as exc @@ -25,7 +24,6 @@ class DefaultCLI(password.Password): as well as allowing users to override with a custom token and endpoint. """ - @positional() def __init__(self, endpoint=None, token=None, **kwargs): super(DefaultCLI, self).__init__(**kwargs) diff --git a/keystoneclient/auth/identity/generic/password.py b/keystoneclient/auth/identity/generic/password.py index 873e25396..ddcdba8e3 100644 --- a/keystoneclient/auth/identity/generic/password.py +++ b/keystoneclient/auth/identity/generic/password.py @@ -11,7 +11,6 @@ # under the License. from oslo_config import cfg -from positional import positional from keystoneclient import _discover from keystoneclient.auth.identity.generic import base @@ -42,7 +41,6 @@ class Password(base.BaseGenericPlugin): """ - @positional() def __init__(self, auth_url, username=None, user_id=None, password=None, user_domain_id=None, user_domain_name=None, **kwargs): super(Password, self).__init__(auth_url=auth_url, **kwargs) diff --git a/keystoneclient/auth/identity/v2.py b/keystoneclient/auth/identity/v2.py index 6a403dcd5..b2ecb4b59 100644 --- a/keystoneclient/auth/identity/v2.py +++ b/keystoneclient/auth/identity/v2.py @@ -14,8 +14,6 @@ import logging from oslo_config import cfg -from positional import positional -import six from keystoneclient import access from keystoneclient.auth.identity import base @@ -25,8 +23,7 @@ _logger = logging.getLogger(__name__) -@six.add_metaclass(abc.ABCMeta) -class Auth(base.BaseIdentityPlugin): +class Auth(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): """Identity V2 Authentication Plugin. :param string auth_url: Identity service endpoint for authorization. @@ -49,7 +46,6 @@ def get_options(cls): return options - @positional() def __init__(self, auth_url, trust_id=None, tenant_id=None, @@ -128,7 +124,6 @@ class Password(Auth): :raises TypeError: if a user_id or username is not provided. """ - @positional(4) def __init__(self, auth_url, username=_NOT_PASSED, password=None, user_id=_NOT_PASSED, **kwargs): super(Password, self).__init__(auth_url, **kwargs) diff --git a/keystoneclient/auth/identity/v3/__init__.py b/keystoneclient/auth/identity/v3/__init__.py index f25bf5e22..abbaa658d 100644 --- a/keystoneclient/auth/identity/v3/__init__.py +++ b/keystoneclient/auth/identity/v3/__init__.py @@ -10,6 +10,8 @@ # 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 diff --git a/keystoneclient/auth/identity/v3/base.py b/keystoneclient/auth/identity/v3/base.py index d82c28953..c055d4ff7 100644 --- a/keystoneclient/auth/identity/v3/base.py +++ b/keystoneclient/auth/identity/v3/base.py @@ -11,12 +11,10 @@ # under the License. import abc -import json import logging from oslo_config import cfg -from positional import positional -import six +from oslo_serialization import jsonutils from keystoneclient import access from keystoneclient.auth.identity import base @@ -28,12 +26,11 @@ __all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth') -@six.add_metaclass(abc.ABCMeta) -class BaseAuth(base.BaseIdentityPlugin): +class BaseAuth(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): """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 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. @@ -47,7 +44,6 @@ class BaseAuth(base.BaseIdentityPlugin): token. (optional) default True. """ - @positional() def __init__(self, auth_url, trust_id=None, domain_id=None, @@ -111,7 +107,7 @@ 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 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. @@ -191,7 +187,7 @@ def get_auth_ref(self, session, **kwargs): authenticated=False, log=False, **rkwargs) try: - _logger.debug(json.dumps(resp.json())) + _logger.debug(jsonutils.dumps(resp.json())) resp_data = resp.json()['token'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) @@ -200,8 +196,7 @@ def get_auth_ref(self, session, **kwargs): **resp_data) -@six.add_metaclass(abc.ABCMeta) -class AuthMethod(object): +class AuthMethod(object, metaclass=abc.ABCMeta): """One part of a V3 Authentication strategy. V3 Tokens allow multiple methods to be presented when authentication @@ -235,7 +230,7 @@ def get_auth_data(self, session, auth, headers, **kwargs): :param session: The communication session. :type session: keystoneclient.session.Session - :param Auth auth: The auth plugin calling the method. + :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 @@ -245,8 +240,7 @@ def get_auth_data(self, session, auth, headers, **kwargs): pass # pragma: no cover -@six.add_metaclass(abc.ABCMeta) -class AuthConstructor(Auth): +class AuthConstructor(Auth, metaclass=abc.ABCMeta): """Abstract base class for creating an Auth Plugin. The Auth Plugin created contains only one authentication method. This diff --git a/keystoneclient/auth/identity/v3/federated.py b/keystoneclient/auth/identity/v3/federated.py index 97d83e8f9..755e7f51b 100644 --- a/keystoneclient/auth/identity/v3/federated.py +++ b/keystoneclient/auth/identity/v3/federated.py @@ -13,7 +13,6 @@ import abc from oslo_config import cfg -import six from keystoneclient.auth.identity.v3 import base from keystoneclient.auth.identity.v3 import token @@ -21,8 +20,7 @@ __all__ = ('FederatedBaseAuth',) -@six.add_metaclass(abc.ABCMeta) -class FederatedBaseAuth(base.BaseAuth): +class FederatedBaseAuth(base.BaseAuth, metaclass=abc.ABCMeta): rescoping_plugin = token.Token diff --git a/keystoneclient/base.py b/keystoneclient/base.py index 8bc4c825e..4f3ed227a 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -20,46 +20,56 @@ import abc import copy import functools +import urllib 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. """ - try: - if obj.uuid: - return obj.uuid - except AttributeError: # nosec(cjschaef): 'obj' doesn't contain attribute - # 'uuid', return attribute 'id' or the 'obj' - pass - try: - return obj.id - except AttributeError: - return obj + 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 six.iteritems(kwargs) if v is not 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 six.iteritems(kwargs): + for key, ref in kwargs.items(): if ref is None: # drop null values continue @@ -107,6 +117,11 @@ def api(self): '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. @@ -137,7 +152,8 @@ def _list(self, url, response_key, obj_class=None, body=None, **kwargs): # are already returned in a list (so simply utilize that list) pass - return [obj_class(self, res, loaded=True) for res in data if res] + 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. @@ -148,7 +164,8 @@ def _get(self, url, response_key, **kwargs): :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.get(url, **kwargs) - return self.resource_class(self, body[response_key], loaded=True) + 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. @@ -157,7 +174,7 @@ def _head(self, url, **kwargs): :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.head(url, **kwargs) - return resp.status_code == 204 + return self._prepare_return_value(resp, resp.status_code == 204) def _post(self, url, body, response_key, return_raw=False, **kwargs): """Create an object. @@ -174,7 +191,8 @@ def _post(self, url, body, response_key, return_raw=False, **kwargs): resp, body = self.client.post(url, body=body, **kwargs) if return_raw: return body[response_key] - return self.resource_class(self, 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. @@ -190,9 +208,15 @@ def _put(self, url, body=None, response_key=None, **kwargs): # PUT requests may not return a body if body is not None: if response_key is not None: - return self.resource_class(self, body[response_key]) + return self._prepare_return_value( + resp, self.resource_class(self, body[response_key])) else: - return self.resource_class(self, body) + 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. @@ -206,9 +230,11 @@ def _patch(self, url, body=None, response_key=None, **kwargs): """ resp, body = self.client.patch(url, body=body, **kwargs) if response_key is not None: - return self.resource_class(self, body[response_key]) + return self._prepare_return_value( + resp, self.resource_class(self, body[response_key])) else: - return self.resource_class(self, body) + return self._prepare_return_value( + resp, self.resource_class(self, body)) def _delete(self, url, **kwargs): """Delete an object. @@ -216,7 +242,8 @@ def _delete(self, url, **kwargs): :param url: a partial URL, e.g., '/servers/my-server' :param kwargs: Additional arguments will be passed to the request. """ - return self.client.delete(url, **kwargs) + 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): @@ -231,11 +258,13 @@ def _update(self, url, body=None, response_key=None, method="PUT", % method) # PUT requests may not return a body if body: - return self.resource_class(self, body[response_key]) + 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): +class ManagerWithFind(Manager, metaclass=abc.ABCMeta): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod @@ -249,16 +278,20 @@ def find(self, **kwargs): the Python side. """ rl = self.findall(**kwargs) - num = len(rl) - if num == 0: + 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 num > 1: + elif len(rl) > 1: raise ksc_exceptions.NoUniqueMatch else: - return rl[0] + return base_response if self.client.include_metadata else rl[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. @@ -269,15 +302,23 @@ def findall(self, **kwargs): found = [] searches = kwargs.items() - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue + 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 - return found + 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): @@ -353,7 +394,17 @@ def head(self, **kwargs): return self._head(self.build_url(dict_args_in_out=kwargs)) def _build_query(self, params): - return '?%s' % urllib.parse.urlencode(params) if params else '' + 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. @@ -366,6 +417,16 @@ def build_key_only_query(self, params_list): @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 @@ -382,15 +443,16 @@ def list(self, fallback_to_auth=False, **kwargs): try: query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % {'url': url, 'query': query} - return self._list( - url_query, - self.collection_key) + 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: - return self._list( - url_query, - self.collection_key, - endpoint_filter={'interface': plugin.AUTH_INTERFACE}) + 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 @@ -421,22 +483,28 @@ def find(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) query = self._build_query(kwargs) - rl = self._list( - '%(url)s%(query)s' % { - 'url': url, - 'query': query, - }, + url_query = '%(url)s%(query)s' % { + 'url': url, + 'query': query + } + elements = self._list( + url_query, self.collection_key) - num = len(rl) - if num == 0: + 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 num > 1: + elif len(elements) > 1: raise ksc_exceptions.NoUniqueMatch else: - return rl[0] + return (base_response if self.client.include_metadata + else elements[0]) class Resource(object): @@ -478,14 +546,14 @@ def human_id(self): return None def _add_details(self, info): - for (k, v) in six.iteritems(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; + # In that case we can't help but not set this attribute; # it'll be available in a dict representation though pass self._info[k] = v @@ -494,7 +562,7 @@ def _add_details(self, info): pass def __getattr__(self, k): - """Checking attrbiute existence.""" + """Checking attribute existence.""" if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): @@ -519,8 +587,6 @@ def get(self): new = self.manager.get(self.id) if new: self._add_details(new._info) - self._add_details( - {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): """Define equality for resources.""" diff --git a/keystoneclient/client.py b/keystoneclient/client.py index 5da9794dc..b2dcf0628 100644 --- a/keystoneclient/client.py +++ b/keystoneclient/client.py @@ -10,25 +10,10 @@ # 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. diff --git a/keystoneclient/common/cms.py b/keystoneclient/common/cms.py index 16e32c6bd..2ee8b52ae 100644 --- a/keystoneclient/common/cms.py +++ b/keystoneclient/common/cms.py @@ -26,10 +26,9 @@ import zlib from debtcollector import removals -import six from keystoneclient import exceptions -from keystoneclient.i18n import _, _LE +from keystoneclient.i18n import _ subprocess = None @@ -38,13 +37,15 @@ PKIZ_PREFIX = 'PKIZ_' PKIZ_CMS_FORM = 'DER' PKI_ASN1_FORM = 'PEM' -DEFAULT_TOKEN_DIGEST_ALGORITHM = 'sha256' +# 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/apps/cms.html#EXIT_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 @@ -114,7 +115,7 @@ def _process_communicate_handle_oserror(process, data, files): retcode, err = _check_files_accessible(files) if process.stderr: msg = process.stderr.read() - if isinstance(msg, six.binary_type): + if isinstance(msg, bytes): msg = msg.decode('utf-8') if err: err = (_('Hit OSError in ' @@ -131,7 +132,7 @@ def _process_communicate_handle_oserror(process, data, files): else: retcode = process.poll() if err is not None: - if isinstance(err, six.binary_type): + if isinstance(err, bytes): err = err.decode('utf-8') return output, err, retcode @@ -160,8 +161,8 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name, properly. """ _ensure_subprocess() - if isinstance(formatted, six.string_types): - data = bytearray(formatted, _encoding_for_form(inform)) + if isinstance(formatted, str): + data = bytes(formatted, _encoding_for_form(inform)) else: data = formatted process = subprocess.Popen(['openssl', 'cms', '-verify', @@ -180,21 +181,31 @@ def cms_verify(formatted, 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 with not exist file, return code 2 - # and error msg will be returned. + # 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 - # http://www.openssl.org/docs/apps/cms.html#EXIT_CODES + # 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 @@ -344,8 +355,8 @@ def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, """ _ensure_subprocess() - if isinstance(data_to_sign, six.string_types): - data = bytearray(data_to_sign, encoding='utf-8') + if isinstance(data_to_sign, str): + data = bytes(data_to_sign, encoding='utf-8') else: data = data_to_sign process = subprocess.Popen(['openssl', 'cms', '-sign', @@ -365,11 +376,11 @@ def cms_sign_data(data_to_sign, 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(_LE('Signing error: Unable to load certificate - ' - 'ensure you have configured PKI with ' - '"keystone-manage pki_setup"')) + LOG.error('Signing error: Unable to load certificate - ' + 'ensure you have configured PKI with ' + '"keystone-manage pki_setup"') else: - LOG.error(_LE('Signing error: %s'), err) + LOG.error('Signing error: %s', err) raise subprocess.CalledProcessError(retcode, 'openssl') if outform == PKI_ASN1_FORM: return output.decode('utf-8') @@ -425,7 +436,7 @@ def cms_hash_token(token_id, mode='md5'): return None if is_asn1_token(token_id) or is_pkiz(token_id): hasher = hashlib.new(mode) - if isinstance(token_id, six.text_type): + if isinstance(token_id, str): token_id = token_id.encode('utf-8') hasher.update(token_id) return hasher.hexdigest() diff --git a/keystoneclient/contrib/auth/v3/oidc.py b/keystoneclient/contrib/auth/v3/oidc.py index 957c50e4b..3884293cc 100644 --- a/keystoneclient/contrib/auth/v3/oidc.py +++ b/keystoneclient/contrib/auth/v3/oidc.py @@ -11,7 +11,6 @@ # under the License. from oslo_config import cfg -from positional import positional from keystoneclient import access from keystoneclient.auth.identity.v3 import federated @@ -42,7 +41,6 @@ def get_options(cls): ]) return options - @positional(4) def __init__(self, auth_url, identity_provider, protocol, username, password, client_id, client_secret, access_token_endpoint, scope='profile', diff --git a/keystoneclient/contrib/auth/v3/saml2.py b/keystoneclient/contrib/auth/v3/saml2.py index 8a07b7f3f..acf3f0513 100644 --- a/keystoneclient/contrib/auth/v3/saml2.py +++ b/keystoneclient/contrib/auth/v3/saml2.py @@ -11,11 +11,11 @@ # under the License. import datetime +import urllib.parse 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 @@ -327,7 +327,7 @@ def _send_service_provider_saml2_authn_response(self, session): authenticated user. This function directs the HTTP request to SP managed URL, for instance: ``https://:/Shibboleth.sso/ SAML2/ECP``. - Upon success the there's a session created and access to the protected + 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/ diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py index f7fb8a14b..f7cefa1bf 100644 --- a/keystoneclient/contrib/ec2/utils.py +++ b/keystoneclient/contrib/ec2/utils.py @@ -20,9 +20,7 @@ import hashlib import hmac import re - -import six -from six.moves import urllib +import urllib.parse from keystoneclient.i18n import _ @@ -106,9 +104,9 @@ def generate(self, credentials): @staticmethod def _get_utf8_value(value): """Get the UTF8-encoded version of a value.""" - if not isinstance(value, (six.binary_type, six.text_type)): + if not isinstance(value, (str, bytes)): value = str(value) - if isinstance(value, six.text_type): + if isinstance(value, str): return value.encode('utf-8') else: return value @@ -121,9 +119,7 @@ def _calc_signature_0(self, params): 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: + for key in sorted(params, key=str.lower): self.hmac.update(key.encode('utf-8')) val = self._get_utf8_value(params[key]) self.hmac.update(val) @@ -218,14 +214,14 @@ def canonical_header_str(): # - the Authorization header (SignedHeaders key) # - the X-Amz-SignedHeaders query parameter headers_lower = dict((k.lower().strip(), v.strip()) - for (k, v) in six.iteritems(headers)) + 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('Boto/2.[0-9].[0-2]', user_agent) + strip_port = re.match(r'Boto/2\.[0-9]\.[0-2]', user_agent) header_list = [] sh_str = auth_param('SignedHeaders') diff --git a/keystoneclient/contrib/revoke/__init__.py b/keystoneclient/contrib/revoke/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/keystoneclient/contrib/revoke/model.py b/keystoneclient/contrib/revoke/model.py deleted file mode 100644 index 914a1f4c2..000000000 --- a/keystoneclient/contrib/revoke/model.py +++ /dev/null @@ -1,318 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_utils import timeutils - -from keystoneclient import utils - - -# The set of attributes common between the RevokeEvent -# and the dictionaries created from the token Data. -_NAMES = ['trust_id', - 'consumer_id', - 'access_token_id', - 'expires_at', - 'domain_id', - 'project_id', - 'user_id', - 'role_id'] - - -# Additional arguments for creating a RevokeEvent -_EVENT_ARGS = ['issued_before', 'revoked_at'] - -# Values that will be in the token data but not in the event. -# These will compared with event values that have different names. -# For example: both trustor_id and trustee_id are compared against user_id -_TOKEN_KEYS = ['identity_domain_id', - 'assignment_domain_id', - 'issued_at', - 'trustor_id', - 'trustee_id'] - - -REVOKE_KEYS = _NAMES + _EVENT_ARGS - - -def blank_token_data(issued_at): - token_data = dict() - for name in _NAMES: - token_data[name] = None - for name in _TOKEN_KEYS: - token_data[name] = None - # required field - token_data['issued_at'] = issued_at - return token_data - - -class RevokeEvent(object): - def __init__(self, **kwargs): - for k in REVOKE_KEYS: - v = kwargs.get(k, None) - setattr(self, k, v) - if self.revoked_at is None: - self.revoked_at = timeutils.utcnow() - if self.issued_before is None: - self.issued_before = self.revoked_at - - def to_dict(self): - keys = ['user_id', - 'role_id', - 'domain_id', - 'project_id'] - event = dict((key, self.__dict__[key]) for key in keys - if self.__dict__[key] is not None) - if self.trust_id is not None: - event['OS-TRUST:trust_id'] = self.trust_id - if self.consumer_id is not None: - event['OS-OAUTH1:consumer_id'] = self.consumer_id - if self.consumer_id is not None: - event['OS-OAUTH1:access_token_id'] = self.access_token_id - if self.expires_at is not None: - event['expires_at'] = utils.isotime(self.expires_at, - subsecond=True) - if self.issued_before is not None: - event['issued_before'] = utils.isotime(self.issued_before, - subsecond=True) - return event - - def key_for_name(self, name): - return "%s=%s" % (name, getattr(self, name) or '*') - - -def attr_keys(event): - return map(event.key_for_name, _NAMES) - - -class RevokeTree(object): - """Fast Revocation Checking Tree Structure. - - The Tree is an index to quickly match tokens against events. - Each node is a hashtable of key=value combinations from revocation events. - The - - """ - - def __init__(self, revoke_events=None): - self.revoke_map = dict() - self.add_events(revoke_events) - - def add_event(self, event): - """Update the tree based on a revocation event. - - Creates any necessary internal nodes in the tree corresponding to the - fields of the revocation event. The leaf node will always be set to - the latest 'issued_before' for events that are otherwise identical. - - :param: Event to add to the tree - - :returns: the event that was passed in. - - """ - revoke_map = self.revoke_map - for key in attr_keys(event): - revoke_map = revoke_map.setdefault(key, {}) - revoke_map['issued_before'] = max( - event.issued_before, revoke_map.get( - 'issued_before', event.issued_before)) - return event - - def remove_event(self, event): - """Update the tree based on the removal of a Revocation Event. - - Removes empty nodes from the tree from the leaf back to the root. - - If multiple events trace the same path, but have different - 'issued_before' values, only the last is ever stored in the tree. - So only an exact match on 'issued_before' ever triggers a removal - - :param: Event to remove from the tree - - """ - stack = [] - revoke_map = self.revoke_map - for name in _NAMES: - key = event.key_for_name(name) - nxt = revoke_map.get(key) - if nxt is None: - break - stack.append((revoke_map, key, nxt)) - revoke_map = nxt - else: - if event.issued_before == revoke_map['issued_before']: - revoke_map.pop('issued_before') - for parent, key, child in reversed(stack): - if not any(child): - del parent[key] - - def add_events(self, revoke_events): - return map(self.add_event, revoke_events or []) - - def is_revoked(self, token_data): - """Check if a token is revoked. - - Compare the values for each level of the tree with the values from - the token, accounting for attributes that have alternative - keys, and for wildcard matches. - if there is a match, continue down the tree. - if there is no match, exit early. - - token_data is a map based on a flattened view of token. - The required fields are: - - 'expires_at','user_id', 'project_id', 'identity_domain_id', - 'assignment_domain_id', 'trust_id', 'trustor_id', 'trustee_id' - 'consumer_id', 'access_token_id' - - """ - # Alternative names to be checked in token for every field in - # revoke tree. - alternatives = { - 'user_id': ['user_id', 'trustor_id', 'trustee_id'], - 'domain_id': ['identity_domain_id', 'assignment_domain_id'], - } - # Contains current forest (collection of trees) to be checked. - partial_matches = [self.revoke_map] - # We iterate over every layer of our revoke tree (except the last one). - for name in _NAMES: - # bundle is the set of partial matches for the next level down - # the tree - bundle = [] - wildcard = '%s=*' % (name,) - # For every tree in current forest. - for tree in partial_matches: - # If there is wildcard node on current level we take it. - bundle.append(tree.get(wildcard)) - if name == 'role_id': - # Roles are very special since a token has a list of them. - # If the revocation event matches any one of them, - # revoke the token. - for role_id in token_data.get('roles', []): - bundle.append(tree.get('role_id=%s' % role_id)) - else: - # For other fields we try to get any branch that concur - # with any alternative field in the token. - for alt_name in alternatives.get(name, [name]): - bundle.append( - tree.get('%s=%s' % (name, token_data[alt_name]))) - # tree.get returns `None` if there is no match, so `bundle.append` - # adds a 'None' entry. This call remoes the `None` entries. - partial_matches = [x for x in bundle if x is not None] - if not partial_matches: - # If we end up with no branches to follow means that the token - # is definitely not in the revoke tree and all further - # iterations will be for nothing. - return False - - # The last (leaf) level is checked in a special way because we verify - # issued_at field differently. - for leaf in partial_matches: - try: - if leaf['issued_before'] > token_data['issued_at']: - return True - except KeyError: # nosec(cjschaef): 'issued_before' or - # 'issued_at' key doesn't exist, try next leaf - continue - # If we made it out of the loop then no element in revocation tree - # corresponds to our token and it is good. - return False - - -def build_token_values_v2(access, default_domain_id): - token_data = access['token'] - token_values = { - 'expires_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['expires'])), - 'issued_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['issued_at'])), - 'user_id': access.get('user', {}).get('id') - } - - project = token_data.get('tenant') - if project is not None: - token_values['project_id'] = project['id'] - else: - token_values['project_id'] = None - - token_values['identity_domain_id'] = default_domain_id - token_values['assignment_domain_id'] = default_domain_id - - trust = token_data.get('trust') - if trust is None: - token_values['trust_id'] = None - token_values['trustor_id'] = None - token_values['trustee_id'] = None - else: - token_values['trust_id'] = trust['id'] - token_values['trustor_id'] = trust['trustor_id'] - token_values['trustee_id'] = trust['trustee_id'] - - token_values['consumer_id'] = None - token_values['access_token_id'] = None - - role_list = [] - # Roles are by ID in metadata and by name in the user section - roles = access.get('metadata', {}).get('roles', []) - for role in roles: - role_list.append(role) - token_values['roles'] = role_list - return token_values - - -def build_token_values(token_data): - token_values = { - 'expires_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['expires_at'])), - 'issued_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['issued_at']))} - - user = token_data.get('user') - if user is not None: - token_values['user_id'] = user['id'] - token_values['identity_domain_id'] = user['domain']['id'] - else: - token_values['user_id'] = None - token_values['identity_domain_id'] = None - - project = token_data.get('project', token_data.get('tenant')) - if project is not None: - token_values['project_id'] = project['id'] - token_values['assignment_domain_id'] = project['domain']['id'] - else: - token_values['project_id'] = None - token_values['assignment_domain_id'] = None - - role_list = [] - roles = token_data.get('roles') - if roles is not None: - for role in roles: - role_list.append(role['id']) - token_values['roles'] = role_list - - trust = token_data.get('OS-TRUST:trust') - if trust is None: - token_values['trust_id'] = None - token_values['trustor_id'] = None - token_values['trustee_id'] = None - else: - token_values['trust_id'] = trust['id'] - token_values['trustor_id'] = trust['trustor_user']['id'] - token_values['trustee_id'] = trust['trustee_user']['id'] - - oauth1 = token_data.get('OS-OAUTH1') - if oauth1 is None: - token_values['consumer_id'] = None - token_values['access_token_id'] = None - else: - token_values['consumer_id'] = oauth1['consumer_id'] - token_values['access_token_id'] = oauth1['access_token_id'] - return token_values diff --git a/keystoneclient/discover.py b/keystoneclient/discover.py index 85b0875ad..16174162d 100644 --- a/keystoneclient/discover.py +++ b/keystoneclient/discover.py @@ -10,13 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -import logging import warnings from debtcollector import removals from keystoneauth1 import plugin -from positional import positional -import six from keystoneclient import _discover from keystoneclient import exceptions @@ -26,9 +23,6 @@ from keystoneclient.v3 import client as v3_client -_logger = logging.getLogger(__name__) - - _CLIENT_VERSIONS = {2: v2_client.Client, 3: v3_client.Client} @@ -151,7 +145,6 @@ class Discover(_discover.Discover): """ - @positional(2) def __init__(self, session=None, authenticated=None, **kwargs): if not session: warnings.warn( @@ -231,7 +224,7 @@ def raw_version_data(self, unstable=False, **kwargs): :returns: The endpoints returned from the server that match the criteria. - :rtype: list + :rtype: List Example:: @@ -239,8 +232,8 @@ def raw_version_data(self, unstable=False, **kwargs): >>> 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'}], + 'links': [{'href': 'http://127.0.0.1:5000/v3/', + 'rel': 'self'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v3+json'}, @@ -249,11 +242,11 @@ def raw_version_data(self, unstable=False, **kwargs): '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'}], + 'links': [{'href': 'http://127.0.0.1:5000/v2.0/', + 'rel': 'self'}, + {'href': '...', + 'rel': 'describedby', + 'type': 'application/pdf'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v2.0+json'}, @@ -300,7 +293,7 @@ def _create_client(self, version_data, **kwargs): raise exceptions.DiscoveryFailure(msg) # kwargs should take priority over stored kwargs. - for k, v in six.iteritems(self._client_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 diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py index 2e3270a88..034e5c977 100644 --- a/keystoneclient/exceptions.py +++ b/keystoneclient/exceptions.py @@ -376,6 +376,7 @@ def __init__(self, output): msg = _('Unable to sign or verify data.') super(CMSError, self).__init__(msg) + EmptyCatalog = _exc.EmptyCatalog """The service catalog is empty. @@ -398,6 +399,7 @@ def __init__(self, output): class MethodNotImplemented(ClientException): """Method not implemented by the keystoneclient API.""" + MissingAuthPlugin = _exc.MissingAuthPlugin """An authenticated request is required but no plugin available. @@ -415,7 +417,7 @@ class MethodNotImplemented(ClientException): class UnsupportedParameters(ClientException): """A parameter that was provided or returned is not supported. - :param list(str) names: Names of the unsupported parameters. + :param List(str) names: Names of the unsupported parameters. .. py:attribute:: names diff --git a/keystoneclient/fixture/__init__.py b/keystoneclient/fixture/__init__.py index 768f96912..92034a0da 100644 --- a/keystoneclient/fixture/__init__.py +++ b/keystoneclient/fixture/__init__.py @@ -28,6 +28,8 @@ """ +# flake8: noqa: F405 + import warnings from keystoneclient.fixture.discovery import * # noqa diff --git a/keystoneclient/generic/client.py b/keystoneclient/generic/client.py index 6d048026c..7c82c195f 100644 --- a/keystoneclient/generic/client.py +++ b/keystoneclient/generic/client.py @@ -14,17 +14,23 @@ # under the License. import logging +import urllib.parse as urlparse -from six.moves.urllib import parse as urlparse +from debtcollector import removals from keystoneclient import exceptions from keystoneclient import httpclient -from keystoneclient.i18n import _, _LE +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. @@ -125,7 +131,7 @@ def _check_keystone_versions(self, url): else: raise exceptions.from_response(resp, "GET", url) except Exception: - _logger.exception(_LE('Failed to detect available versions.')) + _logger.exception('Failed to detect available versions.') def discover_extensions(self, url=None): """Discover Keystone extensions supported. @@ -169,7 +175,7 @@ def _check_keystone_extensions(self, url): raise exceptions.from_response( resp, "GET", "%sextensions" % url) except Exception: - _logger.exception(_LE('Failed to check keystone extensions.')) + _logger.exception('Failed to check keystone extensions.') @staticmethod def _get_version_info(version, root_url): diff --git a/keystoneclient/httpclient.py b/keystoneclient/httpclient.py index e7c2f2444..8c38d1596 100644 --- a/keystoneclient/httpclient.py +++ b/keystoneclient/httpclient.py @@ -17,6 +17,7 @@ # under the License. """OpenStack Client interface. Handles the REST calls and responses.""" +import importlib.metadata import logging import warnings @@ -24,8 +25,7 @@ from debtcollector import renames from keystoneauth1 import adapter from oslo_serialization import jsonutils -import pkg_resources -from positional import positional +import packaging.version import requests try: @@ -34,9 +34,10 @@ # 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') + keyring_v = packaging.version.Version( + importlib.metadata.version('keyring') + ) + keyring_min = packaging.version.Version('5.5.1') # This is a list of versions, e.g., pkg_resources.parse_version('3.3') keyring_bad = [] @@ -44,7 +45,7 @@ import keyring else: keyring = None -except (ImportError, pkg_resources.DistributionNotFound): +except (ImportError, importlib.metadata.PackageNotFoundError): keyring = None pickle = None @@ -54,7 +55,7 @@ from keystoneclient.auth import base from keystoneclient import baseclient from keystoneclient import exceptions -from keystoneclient.i18n import _, _LW +from keystoneclient.i18n import _ from keystoneclient import session as client_session @@ -131,7 +132,7 @@ def user_id(self): # return None pass - # there is a case that we explicity allow (tested by our unit tests) + # 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. @@ -222,7 +223,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): :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) + default: admin (v2), public (v3). (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) @@ -241,7 +242,6 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): removal_version='2.0.0') @renames.renamed_kwarg('tenant_id', 'project_id', version='1.7.0', removal_version='2.0.0') - @positional(enforcement=positional.WARN) 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, @@ -250,7 +250,7 @@ def __init__(self, username=None, tenant_id=None, tenant_name=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, + interface='default', endpoint_override=None, auth=None, user_agent=USER_AGENT, connect_retries=None, **kwargs): # set baseline defaults self.user_id = None @@ -374,12 +374,21 @@ def __init__(self, username=None, tenant_id=None, tenant_name=None, 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) + + # NOTE(frickler): If we know we have v3, use the public interface as + # default, otherwise keep the historic default of admin + if interface == 'default': + if version == (3, 0): + interface = 'public' + else: + interface = 'admin' + + # 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__ self._adapter = _KeystoneAdapter(session, service_type='identity', service_name=service_name, @@ -391,9 +400,14 @@ def __init__(self, username=None, tenant_id=None, tenant_name=None, 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(_LW('Failed to load keyring modules.')) + _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 @@ -442,7 +456,10 @@ def auth_token(self): @property def service_catalog(self): """Return this client's service catalog.""" - return self.auth_ref.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.""" @@ -480,7 +497,6 @@ def tenant_name(self): return self.project_name - @positional(enforcement=positional.WARN) 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, @@ -630,8 +646,7 @@ def get_auth_ref_from_keyring(self, **kwargs): auth_ref = None except Exception as e: auth_ref = None - _logger.warning( - _LW('Unable to retrieve token from keyring %s'), e) + _logger.warning('Unable to retrieve token from keyring %s', e) return (keyring_key, auth_ref) def store_auth_ref_into_keyring(self, keyring_key): @@ -643,8 +658,7 @@ def store_auth_ref_into_keyring(self, keyring_key): pickle.dumps(self.auth_ref)) # nosec # (cjschaef): see bug 1534288 except Exception as e: - _logger.warning( - _LW("Failed to store token into keyring %s"), e) + _logger.warning("Failed to store token into keyring %s", e) def _process_management_url(self, region_name): try: @@ -692,7 +706,6 @@ def management_url(self, value): # permanently setting _endpoint would better match that behaviour. self._endpoint = value - @positional(enforcement=positional.WARN) def get_raw_token_from_identity_service(self, auth_url, username=None, password=None, tenant_name=None, tenant_id=None, token=None, diff --git a/keystoneclient/i18n.py b/keystoneclient/i18n.py index fc9a52b3d..f3726d199 100644 --- a/keystoneclient/i18n.py +++ b/keystoneclient/i18n.py @@ -14,7 +14,7 @@ """oslo.i18n integration module. -See http://docs.openstack.org/developer/oslo.i18n/usage.html . +See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ @@ -25,13 +25,3 @@ # The primary translation function using the well-known name "_" _ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical diff --git a/keystoneclient/service_catalog.py b/keystoneclient/service_catalog.py index de4a6a710..afbefc0ae 100644 --- a/keystoneclient/service_catalog.py +++ b/keystoneclient/service_catalog.py @@ -19,15 +19,11 @@ import abc import warnings -from positional import positional -import six - from keystoneclient import exceptions from keystoneclient.i18n import _ -@six.add_metaclass(abc.ABCMeta) -class ServiceCatalog(object): +class ServiceCatalog(object, metaclass=abc.ABCMeta): """Helper methods for dealing with a Keystone Service Catalog. .. warning:: @@ -209,7 +205,6 @@ def _get_service_endpoints(self, attr, filter_value, service_type, return endpoints @abc.abstractmethod - @positional(enforcement=positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): @@ -233,7 +228,6 @@ def get_urls(self, attr=None, filter_value=None, """ raise NotImplementedError() # pragma: no cover - @positional(3, enforcement=positional.WARN) def url_for(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): @@ -327,7 +321,7 @@ def is_valid(cls, resource_dict): def _normalize_endpoint_type(self, endpoint_type): if endpoint_type and 'URL' not in endpoint_type: - endpoint_type = endpoint_type + 'URL' + endpoint_type += 'URL' return endpoint_type @@ -348,7 +342,6 @@ def get_token(self): pass return token - @positional(enforcement=positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): @@ -415,7 +408,6 @@ def get_token(self): pass return token - @positional(enforcement=positional.WARN) def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='public', region_name=None, service_name=None): diff --git a/keystoneclient/session.py b/keystoneclient/session.py index 522a53366..4944d45f6 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -17,6 +17,7 @@ import os import socket import time +import urllib.parse import warnings from debtcollector import removals @@ -25,18 +26,20 @@ from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils -from positional import positional import requests -import six -from six.moves import urllib from keystoneclient import exceptions -from keystoneclient.i18n import _, _LI, _LW +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__) @@ -129,7 +132,6 @@ class Session(object): """This property is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release.""" - @positional(2, enforcement=positional.WARN) def __init__(self, auth=None, session=None, original_ip=None, verify=True, cert=None, timeout=None, user_agent=None, redirect=_DEFAULT_REDIRECT_LIMIT): @@ -164,15 +166,16 @@ def __init__(self, auth=None, session=None, original_ip=None, verify=True, def _process_header(header): """Redact the secure headers to be logged.""" secure_headers = ('authorization', 'x-auth-token', - 'x-subject-token',) + 'x-subject-token', 'x-service-token') if header[0].lower() in secure_headers: - token_hasher = hashlib.sha1() + # 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 - @positional() def _http_log_request(self, url, method=None, data=None, headers=None, logger=_logger): if not logger.isEnabledFor(logging.DEBUG): @@ -187,7 +190,7 @@ def _http_log_request(self, url, method=None, data=None, # 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): + elif isinstance(self.verify, str): string_parts.append('--cacert "%s"' % self.verify) if method: @@ -196,11 +199,16 @@ def _http_log_request(self, url, method=None, data=None, string_parts.append(url) if headers: - for header in six.iteritems(headers): + for header in headers.items(): string_parts.append('-H "%s: %s"' % self._process_header(header)) if data: + if isinstance(data, bytes): + try: + data = data.decode("ascii") + except UnicodeDecodeError: + data = "" string_parts.append("-d '%s'" % data) try: logger.debug(' '.join(string_parts)) @@ -216,21 +224,36 @@ def _http_log_response(self, response, logger): if not logger.isEnabledFor(logging.DEBUG): return - text = _remove_service_catalog(response.text) + # NOTE(samueldmq): If the response does not provide enough info about + # the content type to decide whether it is useful and safe to log it + # or not, just do not log the body. Trying to# read the response body + # anyways may result on reading a long stream of bytes and getting an + # unexpected MemoryError. See bug 1616105 for further details. + content_type = response.headers.get('content-type', None) + + # NOTE(lamt): Per [1], the Content-Type header can be of the form + # Content-Type := type "/" subtype *[";" parameter] + # [1] https://www.w3.org/Protocols/rfc1341/4_Content-Type.html + for log_type in _LOG_CONTENT_TYPES: + if content_type is not None and content_type.startswith(log_type): + text = _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 six.iteritems(response.headers): + for header in response.headers.items(): string_parts.append('%s: %s' % self._process_header(header)) - if text: - string_parts.append('\nRESP BODY: %s\n' % - strutils.mask_password(text)) + string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text)) logger.debug(' '.join(string_parts)) - @positional(enforcement=positional.WARN) + # 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, @@ -450,7 +473,7 @@ def _send_request(self, url, method, redirect, log, logger, if connect_retries <= 0: raise - logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), + logger.info('Failure: %(e)s. Retrying in %(delay).1fs.', {'e': e, 'delay': connect_retry_delay}) time.sleep(connect_retry_delay) @@ -477,8 +500,8 @@ def _send_request(self, url, method, redirect, log, logger, try: location = resp.headers['location'] except KeyError: - logger.warning(_LW("Failed to redirect request to %s as new " - "location was not provided."), resp.url) + 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. @@ -780,7 +803,7 @@ def get_project_id(self, auth=None): auth = self._auth_required(auth, msg) return auth.get_project_id(self) - @positional.classmethod() + @classmethod def get_conf_options(cls, deprecated_opts=None): """Get oslo_config options that are needed for a :py:class:`.Session`. @@ -829,7 +852,7 @@ def get_conf_options(cls, deprecated_opts=None): help='Timeout value for http requests'), ] - @positional.classmethod() + @classmethod def register_conf_options(cls, conf, group, deprecated_opts=None): """Register the oslo_config options that are needed for a session. diff --git a/keystoneclient/tests/functional/base.py b/keystoneclient/tests/functional/base.py index 743cb5019..76355099f 100644 --- a/keystoneclient/tests/functional/base.py +++ b/keystoneclient/tests/functional/base.py @@ -10,10 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack import config as occ +import openstack.exceptions import testtools from keystoneclient import client -import os_client_config IDENTITY_CLIENT = 'identity' OPENSTACK_CLOUDS = ('functional_admin', 'devstack-admin', 'envvars') @@ -22,8 +23,8 @@ 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. + The client is instantiated 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 @@ -33,12 +34,14 @@ def get_client(version): """ for cloud in OPENSTACK_CLOUDS: try: - cloud_config = os_client_config.get_config( + cloud_config = occ.OpenStackConfig().get_one( 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: + endpoint = cloud_config.get_session_endpoint(IDENTITY_CLIENT) + return client.Client( + version=version, + session=cloud_config.get_session(), + endpoint=endpoint) + except openstack.exceptions.ConfigException: pass raise Exception("Could not find any cloud definition for clouds named" @@ -83,7 +86,3 @@ def user_id(self): class V3ClientTestCase(ClientTestCase): version = '3' - - -class V2ClientTestCase(ClientTestCase): - version = '2.0' diff --git a/keystoneclient/tests/functional/hooks/post_test_hook.sh b/keystoneclient/tests/functional/hooks/post_test_hook.sh deleted file mode 100755 index 951c321c5..000000000 --- a/keystoneclient/tests/functional/hooks/post_test_hook.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -xe - -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 is executed inside post_test_hook function in devstack gate. - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -export KEYSTONECLIENT_DIR="$BASE/new/python-keystoneclient" - -# Get admin credentials -cd $BASE/new/devstack -source openrc admin admin - -# Go to the keystoneclient dir -cd $KEYSTONECLIENT_DIR - -sudo chown -R jenkins:stack $KEYSTONECLIENT_DIR - -# Run tests -echo "Running keystoneclient functional test suite" -set +e -# Preserve env for OS_ credentials -sudo -E -H -u jenkins tox -efunctional -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE diff --git a/keystoneclient/tests/functional/test_access.py b/keystoneclient/tests/functional/test_access.py deleted file mode 100644 index 84733a2ba..000000000 --- a/keystoneclient/tests/functional/test_access.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os - -from keystoneclient.auth.identity import v2 -from keystoneclient import session -from tempest.lib import base - - -class TestV2AccessInfo(base.BaseTestCase): - - def setUp(self): - super(TestV2AccessInfo, self).setUp() - - self.session = session.Session() - - def test_access_audit_id(self): - unscoped_plugin = v2.Password(auth_url=os.environ.get('OS_AUTH_URL'), - username=os.environ.get('OS_USERNAME'), - password=os.environ.get('OS_PASSWORD')) - - unscoped_auth_ref = unscoped_plugin.get_access(self.session) - - self.assertIsNotNone(unscoped_auth_ref.audit_id) - self.assertIsNone(unscoped_auth_ref.audit_chain_id) - - scoped_plugin = v2.Token(auth_url=os.environ.get('OS_AUTH_URL'), - token=unscoped_auth_ref.auth_token, - tenant_name=os.environ.get('OS_TENANT_NAME')) - - scoped_auth_ref = scoped_plugin.get_access(self.session) - - self.assertIsNotNone(scoped_auth_ref.audit_id) - self.assertIsNotNone(scoped_auth_ref.audit_chain_id) - - self.assertEqual(unscoped_auth_ref.audit_id, - scoped_auth_ref.audit_chain_id) diff --git a/keystoneclient/tests/functional/test_base.py b/keystoneclient/tests/functional/test_base.py index d5f051663..091fc77d4 100644 --- a/keystoneclient/tests/functional/test_base.py +++ b/keystoneclient/tests/functional/test_base.py @@ -19,10 +19,3 @@ class V3ClientVersionTestCase(base.V3ClientTestCase): def test_version(self): self.assertIsInstance(self.client, keystoneclient.v3.client.Client) - - -class V2ClientVersionTestCase(base.V2ClientTestCase): - - def test_version(self): - self.assertIsInstance(self.client, - keystoneclient.v2_0.client.Client) diff --git a/keystoneclient/tests/functional/v2_0/__init__.py b/keystoneclient/tests/functional/v2_0/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/keystoneclient/tests/functional/v3/client_fixtures.py b/keystoneclient/tests/functional/v3/client_fixtures.py index 4b54b88f7..edffc9b8c 100644 --- a/keystoneclient/tests/functional/v3/client_fixtures.py +++ b/keystoneclient/tests/functional/v3/client_fixtures.py @@ -71,9 +71,10 @@ def setUp(self): class Project(Base): - def __init__(self, client, domain_id=None, parent=None): + 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() @@ -81,7 +82,8 @@ def setUp(self): self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id, 'enabled': True, - 'parent': self.parent} + 'parent': self.parent, + 'tags': self.tags} self.entity = self.client.projects.create(**self.ref) self.addCleanup(self.client.projects.delete, self.entity) @@ -114,8 +116,8 @@ def setUp(self): self.ref = {'prior_role': self.prior_role, 'implied_role': self.implied_role} - self.entity = self.client.roles.create_implied(**self.ref) - self.addCleanup(self.client.roles.delete_implied, self.prior_role, + self.entity = self.client.inference_rules.create(**self.ref) + self.addCleanup(self.client.inference_rules.delete, self.prior_role, self.implied_role) @@ -178,6 +180,18 @@ def setUp(self): 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): @@ -219,3 +233,20 @@ def setUp(self): 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) diff --git a/keystoneclient/tests/functional/v3/test_credentials.py b/keystoneclient/tests/functional/v3/test_credentials.py index d428f1084..a5d00b1c0 100644 --- a/keystoneclient/tests/functional/v3/test_credentials.py +++ b/keystoneclient/tests/functional/v3/test_credentials.py @@ -20,6 +20,11 @@ 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) @@ -46,7 +51,7 @@ def check_credential(self, credential, credential_ref=None): self.assertIsNotNone(credential.project_id) def test_create_credential_of_cert_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, @@ -58,7 +63,7 @@ def test_create_credential_of_cert_type(self): self.check_credential(credential, credential_ref) def test_create_credential_of_ec2_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) # project is mandatory attribute if the credential type is ec2 @@ -70,7 +75,7 @@ def test_create_credential_of_ec2_type(self): self.client.credentials.create, **credential_ref) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) credential_ref = {'user': user.id, @@ -84,7 +89,7 @@ def test_create_credential_of_ec2_type(self): self.check_credential(credential, credential_ref) def test_create_credential_of_totp_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, @@ -96,9 +101,9 @@ def test_create_credential_of_totp_type(self): self.check_credential(credential, credential_ref) def test_get_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: @@ -111,14 +116,14 @@ def test_get_credential(self): self.check_credential(credential_ret, credential.ref) def test_list_credentials(self): - user = fixtures.User(self.client, self.project_domain_id) + 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.project_domain_id) + 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) @@ -139,12 +144,12 @@ def test_list_credentials(self): self.assertIn(totp_credential.entity, credentials) def test_update_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - new_user = fixtures.User(self.client, self.project_domain_id) + new_user = fixtures.User(self.client, self.test_domain.id) self.useFixture(new_user) - new_project = fixtures.Project(self.client, self.project_domain_id) + new_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(new_project) credential = fixtures.Credential(self.client, user=user.id, @@ -166,9 +171,9 @@ def test_update_credential(self): self.check_credential(credential_ret, credential.ref) def test_delete_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: diff --git a/keystoneclient/tests/functional/v3/test_domain_configs.py b/keystoneclient/tests/functional/v3/test_domain_configs.py new file mode 100644 index 000000000..f3ca71a22 --- /dev/null +++ b/keystoneclient/tests/functional/v3/test_domain_configs.py @@ -0,0 +1,106 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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) diff --git a/keystoneclient/tests/functional/v3/test_endpoint_filters.py b/keystoneclient/tests/functional/v3/test_endpoint_filters.py new file mode 100644 index 000000000..d8956bed2 --- /dev/null +++ b/keystoneclient/tests/functional/v3/test_endpoint_filters.py @@ -0,0 +1,86 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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) diff --git a/keystoneclient/tests/functional/v3/test_endpoint_groups.py b/keystoneclient/tests/functional/v3/test_endpoint_groups.py new file mode 100644 index 000000000..52fcf724e --- /dev/null +++ b/keystoneclient/tests/functional/v3/test_endpoint_groups.py @@ -0,0 +1,123 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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) diff --git a/keystoneclient/tests/functional/v3/test_implied_roles.py b/keystoneclient/tests/functional/v3/test_implied_roles.py index b2f743c78..4a8b446ea 100644 --- a/keystoneclient/tests/functional/v3/test_implied_roles.py +++ b/keystoneclient/tests/functional/v3/test_implied_roles.py @@ -28,11 +28,7 @@ "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", +inference_rules = {"test_admin": "test_policy_manager", "test_id_manager": "test_project_observer", "test_resource_manager": "test_project_observer", "test_role_manager": "test_project_observer", @@ -48,11 +44,12 @@ def setUp(self): super(TestImpliedRoles, self).setUp() def test_implied_roles(self): - initial_rule_count = len(self.client.roles.list_role_inferences()) + initial_rule_count = ( + len(self.client.inference_rules.list_inference_roles())) self.create_roles() self.create_rules() - rule_count = len(self.client.roles.list_role_inferences()) + rule_count = len(self.client.inference_rules.list_inference_roles()) self.assertEqual(initial_rule_count + len(inference_rules), rule_count) diff --git a/keystoneclient/tests/functional/v3/test_projects.py b/keystoneclient/tests/functional/v3/test_projects.py index d06aaa88b..a7f082db1 100644 --- a/keystoneclient/tests/functional/v3/test_projects.py +++ b/keystoneclient/tests/functional/v3/test_projects.py @@ -18,7 +18,7 @@ from keystoneclient.tests.functional.v3 import client_fixtures as fixtures -class ProjectsTestCase(base.V3ClientTestCase): +class ProjectsTestMixin(object): def check_project(self, project, project_ref=None): self.assertIsNotNone(project.id) @@ -43,13 +43,25 @@ def check_project(self, project, project_ref=None): 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.project_domain_id, + 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, - 'parent': self.project_id} + 'parent': self.test_project.id} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) @@ -58,7 +70,7 @@ def test_create_subproject(self): def test_create_project(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, - 'domain': self.project_domain_id, + 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex} @@ -67,31 +79,25 @@ def test_create_project(self): self.check_project(project, project_ref) def test_get_project(self): - project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(project) - - project_ret = self.client.projects.get(project.id) - self.check_project(project_ret, project.ref) + 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.project_id, + self.test_project.id, subtree_as_list=True, subtree_as_ids=True) self.assertRaises(exceptions.ValidationError, self.client.projects.get, - self.project_id, + self.test_project.id, parents_as_list=True, parents_as_ids=True) def test_get_hierarchy_as_list(self): - parent_project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(parent_project) - - project = fixtures.Project(self.client, self.project_domain_id, - parent=parent_project.id) + project = fixtures.Project(self.client, self.test_domain.id, + parent=self.test_project.id) self.useFixture(project) - child_project = fixtures.Project(self.client, self.project_domain_id, + child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) @@ -101,8 +107,9 @@ def test_get_hierarchy_as_list(self): role = fixtures.Role(self.client) self.useFixture(role) self.client.roles.grant(role.id, user=self.user_id, - project=parent_project.id) - self.client.roles.grant(role.id, user=self.user_id, project=project.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) @@ -111,20 +118,19 @@ def test_get_hierarchy_as_list(self): parents_as_list=True) self.check_project(project_ret, project.ref) - self.assertItemsEqual([{'project': parent_project.entity.to_dict()}], - project_ret.parents) - self.assertItemsEqual([{'project': child_project.entity.to_dict()}], - project_ret.subtree) + self.assertCountEqual( + [{'project': self.test_project.entity.to_dict()}], + project_ret.parents) + self.assertCountEqual( + [{'project': child_project.entity.to_dict()}], + project_ret.subtree) def test_get_hierarchy_as_ids(self): - parent_project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(parent_project) - - project = fixtures.Project(self.client, self.project_domain_id, - parent=parent_project.id) + project = fixtures.Project(self.client, self.test_domain.id, + parent=self.test_project.id) self.useFixture(project) - child_project = fixtures.Project(self.client, self.project_domain_id, + child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) @@ -132,14 +138,14 @@ def test_get_hierarchy_as_ids(self): subtree_as_ids=True, parents_as_ids=True) - self.assertItemsEqual([parent_project.id], project_ret.parents) - self.assertItemsEqual([child_project.id], project_ret.subtree) + self.assertCountEqual([self.test_project.id], project_ret.parents) + self.assertCountEqual([child_project.id], project_ret.subtree) def test_list_projects(self): - project_one = fixtures.Project(self.client, self.project_domain_id) + project_one = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_one) - project_two = fixtures.Project(self.client, self.project_domain_id) + project_two = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_two) projects = self.client.projects.list() @@ -151,8 +157,32 @@ def test_list_projects(self): 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.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex @@ -167,22 +197,273 @@ def test_update_project(self): self.check_project(project_ret, project.ref) def test_update_project_domain_not_allowed(self): - project = fixtures.Project(self.client) - self.useFixture(project) - domain = fixtures.Domain(self.client) self.useFixture(domain) # Cannot update domain after project is created. self.assertRaises(http.BadRequest, self.client.projects.update, - project.id, domain=domain.id) + self.test_project.id, domain=domain.id) def test_delete_project(self): project = self.client.projects.create(name=uuid.uuid4().hex, - domain=self.project_domain_id, + 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) diff --git a/keystoneclient/tests/functional/v3/test_roles.py b/keystoneclient/tests/functional/v3/test_roles.py index d2bc7a25b..6dba6ffa3 100644 --- a/keystoneclient/tests/functional/v3/test_roles.py +++ b/keystoneclient/tests/functional/v3/test_roles.py @@ -168,7 +168,7 @@ def test_user_domain_grant_and_revoke(self): 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.assertCountEqual(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, @@ -188,7 +188,7 @@ def test_user_project_grant_and_revoke(self): 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.assertCountEqual(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, @@ -208,7 +208,7 @@ def test_group_domain_grant_and_revoke(self): 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.assertCountEqual(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, @@ -228,7 +228,7 @@ def test_group_project_grant_and_revoke(self): 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.assertCountEqual(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, diff --git a/keystoneclient/tests/functional/v3/test_users.py b/keystoneclient/tests/functional/v3/test_users.py index b39c7f9e0..780ddbacf 100644 --- a/keystoneclient/tests/functional/v3/test_users.py +++ b/keystoneclient/tests/functional/v3/test_users.py @@ -76,6 +76,29 @@ def test_list_users(self): 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) diff --git a/keystoneclient/tests/hacking/__init__.py b/keystoneclient/tests/hacking/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/keystoneclient/tests/hacking/checks.py b/keystoneclient/tests/hacking/checks.py deleted file mode 100644 index 42826982a..000000000 --- a/keystoneclient/tests/hacking/checks.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""python-keystoneclient's pep8 extensions. - -In order to make the review process faster and easier for core devs we are -adding some python-keystoneclient specific pep8 checks. This will catch common -errors so that core devs don't have to. - -""" - - -import re - - -def check_oslo_namespace_imports(logical_line, blank_before, filename): - oslo_namespace_imports = re.compile( - r"(((from)|(import))\s+oslo\.)|(from\s+oslo\s+import\s+)") - - if re.match(oslo_namespace_imports, logical_line): - msg = ("K333: '%s' must be used instead of '%s'.") % ( - logical_line.replace('oslo.', 'oslo_'), - logical_line) - yield(0, msg) - - -def factory(register): - register(check_oslo_namespace_imports) diff --git a/keystoneclient/tests/unit/apiclient/test_exceptions.py b/keystoneclient/tests/unit/apiclient/test_exceptions.py index 4a803c7e7..65cf08016 100644 --- a/keystoneclient/tests/unit/apiclient/test_exceptions.py +++ b/keystoneclient/tests/unit/apiclient/test_exceptions.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - from keystoneclient import exceptions from keystoneclient.tests.unit import utils @@ -23,7 +21,7 @@ class FakeResponse(object): json_data = {} def __init__(self, **kwargs): - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): setattr(self, key, value) def json(self): @@ -40,7 +38,7 @@ def assert_exception(self, ex_cls, method, url, status_code, json_data): method, url) self.assertIsInstance(ex, ex_cls) - self.assertEqual(ex.message, json_data["error"]["message"]) + 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) diff --git a/keystoneclient/tests/unit/auth/test_auth.py b/keystoneclient/tests/unit/auth/test_auth.py index f2884350c..3a56fb2aa 100644 --- a/keystoneclient/tests/unit/auth/test_auth.py +++ b/keystoneclient/tests/unit/auth/test_auth.py @@ -10,29 +10,13 @@ # 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) + pass 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) + pass diff --git a/keystoneclient/tests/unit/auth/test_cli.py b/keystoneclient/tests/unit/auth/test_cli.py index b6fefa7c7..b2a2f6ae8 100644 --- a/keystoneclient/tests/unit/auth/test_cli.py +++ b/keystoneclient/tests/unit/auth/test_cli.py @@ -11,10 +11,10 @@ # under the License. import argparse +from unittest import mock import uuid import fixtures -import mock from oslo_config import cfg from keystoneclient.auth import base diff --git a/keystoneclient/tests/unit/auth/test_conf.py b/keystoneclient/tests/unit/auth/test_conf.py index 47bf75900..bea37a4f1 100644 --- a/keystoneclient/tests/unit/auth/test_conf.py +++ b/keystoneclient/tests/unit/auth/test_conf.py @@ -10,17 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock 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 @@ -39,58 +36,10 @@ def setUp(self): 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) + pass 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) + pass def test_loading_invalid_plugin(self): auth_plugin = uuid.uuid4().hex @@ -155,15 +104,7 @@ def test_diff_section(self, m): 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) + pass def test_get_common(self): opts = conf.get_common_conf_options() @@ -172,7 +113,4 @@ def test_get_common(self): 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) + pass diff --git a/keystoneclient/tests/unit/auth/test_default_cli.py b/keystoneclient/tests/unit/auth/test_default_cli.py index cb4603dc4..8afd2cde7 100644 --- a/keystoneclient/tests/unit/auth/test_default_cli.py +++ b/keystoneclient/tests/unit/auth/test_default_cli.py @@ -11,9 +11,9 @@ # under the License. import argparse +from unittest import mock import uuid -import mock from keystoneclient.auth.identity.generic import cli from keystoneclient import exceptions diff --git a/keystoneclient/tests/unit/auth/test_identity_common.py b/keystoneclient/tests/unit/auth/test_identity_common.py index 579367245..3e8cc2b81 100644 --- a/keystoneclient/tests/unit/auth/test_identity_common.py +++ b/keystoneclient/tests/unit/auth/test_identity_common.py @@ -12,13 +12,12 @@ import abc import datetime +from unittest import mock 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 @@ -28,8 +27,7 @@ from keystoneclient.tests.unit import utils -@six.add_metaclass(abc.ABCMeta) -class CommonIdentityTests(object): +class CommonIdentityTests(object, metaclass=abc.ABCMeta): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' @@ -79,7 +77,8 @@ def stub_auth_data(self, **kwargs): self.stub_auth(json=token) - @abc.abstractproperty + @property + @abc.abstractmethod def version(self): """The API version being tested.""" @@ -461,7 +460,7 @@ def test_setting_headers(self): self.assertEqual(text, resp.text) - for k, v in six.iteritems(self.auth.headers): + for k, v in self.auth.headers.items(): self.assertRequestHeaderEqual(k, v) with self.deprecations.expect_deprecations_here(): diff --git a/keystoneclient/tests/unit/auth/test_identity_v2.py b/keystoneclient/tests/unit/auth/test_identity_v2.py index 8ef87c430..84f4f5194 100644 --- a/keystoneclient/tests/unit/auth/test_identity_v2.py +++ b/keystoneclient/tests/unit/auth/test_identity_v2.py @@ -12,9 +12,9 @@ import argparse import copy +from unittest import mock import uuid -import mock from keystoneclient.auth.identity import v2 from keystoneclient import exceptions @@ -84,7 +84,7 @@ def setUp(self): self.TEST_RESPONSE_DICT = { "access": { "token": { - "expires": "2020-01-01T00:00:10.000123Z", + "expires": "2999-01-01T00:00:10.000123Z", "id": self.TEST_TOKEN, "tenant": { "id": self.TEST_TENANT_ID diff --git a/keystoneclient/tests/unit/auth/test_identity_v3.py b/keystoneclient/tests/unit/auth/test_identity_v3.py index 534e99747..fe7815ed2 100644 --- a/keystoneclient/tests/unit/auth/test_identity_v3.py +++ b/keystoneclient/tests/unit/auth/test_identity_v3.py @@ -12,10 +12,10 @@ import argparse import copy +from unittest import mock import uuid from keystoneauth1 import fixture -import mock from keystoneclient import access from keystoneclient.auth.identity import v3 @@ -129,7 +129,7 @@ def setUp(self): "password" ], - "expires_at": "2020-01-01T00:00:10.000123Z", + "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": self.TEST_DOMAIN_ID, diff --git a/keystoneclient/tests/unit/auth/test_loading.py b/keystoneclient/tests/unit/auth/test_loading.py index f8ef3b75a..3c2689dd9 100644 --- a/keystoneclient/tests/unit/auth/test_loading.py +++ b/keystoneclient/tests/unit/auth/test_loading.py @@ -12,7 +12,6 @@ import uuid -import six from keystoneclient.tests.unit.auth import utils @@ -39,7 +38,7 @@ def _getter(opt): self.assertEqual(set(vals), set(called_opts)) - for k, v in six.iteritems(vals): + for k, v in vals.items(): # replace - to _ because it's the dest used to create kwargs self.assertEqual(v, p[k.replace('-', '_')]) diff --git a/keystoneclient/tests/unit/auth/test_password.py b/keystoneclient/tests/unit/auth/test_password.py index 020eb124c..93d2fb8ca 100644 --- a/keystoneclient/tests/unit/auth/test_password.py +++ b/keystoneclient/tests/unit/auth/test_password.py @@ -11,9 +11,9 @@ # under the License. import argparse +from unittest import mock import uuid -import mock from keystoneclient.auth.identity.generic import password from keystoneclient.auth.identity import v2 diff --git a/keystoneclient/tests/unit/auth/utils.py b/keystoneclient/tests/unit/auth/utils.py index 6c8be8cdb..1e346e35c 100644 --- a/keystoneclient/tests/unit/auth/utils.py +++ b/keystoneclient/tests/unit/auth/utils.py @@ -11,12 +11,11 @@ # under the License. import functools +from unittest import mock import uuid from keystoneauth1 import fixture -import mock from oslo_config import cfg -import six from keystoneclient import access from keystoneclient.auth import base @@ -88,7 +87,7 @@ class TestCase(utils.TestCase): 'a_bool': a_bool} def assertTestVals(self, plugin, vals=TEST_VALS): - for k, v in six.iteritems(vals): + for k, v in vals.items(): self.assertEqual(v, plugin[k]) diff --git a/keystoneclient/tests/unit/client_fixtures.py b/keystoneclient/tests/unit/client_fixtures.py index b03f428d8..c6c291984 100644 --- a/keystoneclient/tests/unit/client_fixtures.py +++ b/keystoneclient/tests/unit/client_fixtures.py @@ -23,7 +23,6 @@ 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 @@ -204,7 +203,7 @@ def new_client(self): def _hash_signed_token_safe(signed_text, **kwargs): - if isinstance(signed_text, six.text_type): + if isinstance(signed_text, str): signed_text = signed_text.encode('utf-8') return utils.hash_signed_token(signed_text, **kwargs) @@ -299,7 +298,7 @@ def setUp(self): self.v3_UUID_TOKEN_UNKNOWN_BIND = '7ed9781b62cd4880b8d8c6788ab1d1e2' revoked_token = self.REVOKED_TOKEN - if isinstance(revoked_token, six.text_type): + if isinstance(revoked_token, str): 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, @@ -310,7 +309,7 @@ def setUp(self): 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): + if isinstance(revoked_v3_token, str): 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') @@ -322,12 +321,12 @@ def setUp(self): self.REVOKED_v3_TOKEN_LIST) revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ - if isinstance(revoked_token_pkiz, six.text_type): + if isinstance(revoked_token_pkiz, str): 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): + if isinstance(revoked_v3_token_pkiz, str): 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) @@ -399,7 +398,7 @@ def setUp(self): 'access': { 'token': { 'id': self.UUID_TOKEN_DEFAULT, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', @@ -420,7 +419,7 @@ def setUp(self): 'access': { 'token': { 'id': self.VALID_DIABLO_TOKEN, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', 'tenantId': 'tenant_id1', }, 'user': { @@ -437,7 +436,7 @@ def setUp(self): 'access': { 'token': { 'id': self.UUID_TOKEN_UNSCOPED, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', @@ -453,7 +452,7 @@ def setUp(self): 'access': { 'token': { 'id': 'valid-token', - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', @@ -474,7 +473,7 @@ def setUp(self): 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'id': self.UUID_TOKEN_BIND, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', @@ -496,7 +495,7 @@ def setUp(self): 'token': { 'bind': {'FOO': 'BAR'}, 'id': self.UUID_TOKEN_UNKNOWN_BIND, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', @@ -515,7 +514,7 @@ def setUp(self): }, self.v3_UUID_TOKEN_DEFAULT: { 'token': { - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', @@ -542,7 +541,7 @@ def setUp(self): }, self.v3_UUID_TOKEN_UNSCOPED: { 'token': { - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', @@ -556,7 +555,7 @@ def setUp(self): }, self.v3_UUID_TOKEN_DOMAIN_SCOPED: { 'token': { - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', @@ -581,7 +580,7 @@ def setUp(self): 'access': { 'token': { 'id': self.SIGNED_TOKEN_SCOPED_KEY, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', @@ -599,7 +598,7 @@ def setUp(self): 'access': { 'token': { 'id': self.SIGNED_TOKEN_UNSCOPED_KEY, - 'expires': '2020-01-01T00:00:10.000123Z', + 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', @@ -613,7 +612,7 @@ def setUp(self): }, self.SIGNED_v3_TOKEN_SCOPED_KEY: { 'token': { - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', @@ -642,7 +641,7 @@ def setUp(self): 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'methods': ['password'], - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'user': { 'id': 'user_id1', 'name': 'user_name1', @@ -669,7 +668,7 @@ def setUp(self): self.v3_UUID_TOKEN_UNKNOWN_BIND: { 'token': { 'bind': {'FOO': 'BAR'}, - 'expires_at': '2020-01-01T00:00:10.000123Z', + 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', @@ -703,73 +702,12 @@ def setUp(self): self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY]) self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in - six.iteritems(self.TOKEN_RESPONSES)]) + self.TOKEN_RESPONSES.items()]) EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) -class HackingCode(fixtures.Fixture): - """A fixture to house the various code examples. - - Examples contains various keystoneclient hacking style checks. - """ - - oslo_namespace_imports = { - 'code': """ - import oslo.utils - import oslo_utils - import oslo.utils.encodeutils - import oslo_utils.encodeutils - from oslo import utils - from oslo.utils import encodeutils - from oslo_utils import encodeutils - - import oslo.serialization - import oslo_serialization - import oslo.serialization.jsonutils - import oslo_serialization.jsonutils - from oslo import serialization - from oslo.serialization import jsonutils - from oslo_serialization import jsonutils - - import oslo.config - import oslo_config - import oslo.config.cfg - import oslo_config.cfg - from oslo import config - from oslo.config import cfg - from oslo_config import cfg - - import oslo.i18n - import oslo_i18n - import oslo.i18n.log - import oslo_i18n.log - from oslo import i18n - from oslo.i18n import log - from oslo_i18n import log - """, - 'expected_errors': [ - (1, 0, 'K333'), - (3, 0, 'K333'), - (5, 0, 'K333'), - (6, 0, 'K333'), - (9, 0, 'K333'), - (11, 0, 'K333'), - (13, 0, 'K333'), - (14, 0, 'K333'), - (17, 0, 'K333'), - (19, 0, 'K333'), - (21, 0, 'K333'), - (22, 0, 'K333'), - (25, 0, 'K333'), - (27, 0, 'K333'), - (29, 0, 'K333'), - (30, 0, 'K333'), - ], - } - - class Deprecations(fixtures.Fixture): def setUp(self): super(Deprecations, self).setUp() diff --git a/keystoneclient/tests/unit/generic/test_client.py b/keystoneclient/tests/unit/generic/test_client.py index a3690fb0c..5c27b6eb9 100644 --- a/keystoneclient/tests/unit/generic/test_client.py +++ b/keystoneclient/tests/unit/generic/test_client.py @@ -22,7 +22,8 @@ BASE_URL = "%s:5000/" % BASE_HOST V2_URL = "%sv2.0" % BASE_URL -EXTENSION_NAMESPACE = "http://docs.openstack.org/identity/api/ext/OS-FAKE/v1.0" +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"} @@ -58,6 +59,7 @@ 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) diff --git a/keystoneclient/tests/unit/test_base.py b/keystoneclient/tests/unit/test_base.py index f6ca651af..ca57dc469 100644 --- a/keystoneclient/tests/unit/test_base.py +++ b/keystoneclient/tests/unit/test_base.py @@ -11,14 +11,29 @@ # 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 -from oslotest import mockpatch +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): @@ -44,7 +59,7 @@ def test_resource_lazy_getattr(self): session_ = session.Session(auth=auth) self.client = client.Client(session=session_) - self.useFixture(mockpatch.PatchObject( + self.useFixture(fixtures.MockPatchObject( self.client._adapter, 'get', side_effect=AttributeError, autospec=True)) @@ -127,7 +142,7 @@ def test_api(self): self.assertEqual(self.mgr.api, self.client) def test_get(self): - get_mock = self.useFixture(mockpatch.PatchObject( + get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._get(self.url, "hello") @@ -135,7 +150,7 @@ def test_get(self): self.assertEqual(rsrc.hi, 1) def test_post(self): - post_mock = self.useFixture(mockpatch.PatchObject( + post_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'post', autospec=True, return_value=(None, self.body)) ).mock @@ -150,7 +165,7 @@ def test_post(self): self.assertEqual(rsrc["hi"], 1) def test_put(self): - put_mock = self.useFixture(mockpatch.PatchObject( + put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock @@ -165,7 +180,7 @@ def test_put(self): self.assertEqual(rsrc.hello["hi"], 1) def test_patch(self): - patch_mock = self.useFixture(mockpatch.PatchObject( + patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock @@ -181,12 +196,12 @@ def test_patch(self): self.assertEqual(rsrc.hello["hi"], 1) def test_update(self): - patch_mock = self.useFixture(mockpatch.PatchObject( + patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock - put_mock = self.useFixture(mockpatch.PatchObject( + put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock @@ -202,3 +217,209 @@ def test_update(self): 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.assertIsInstance(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?') diff --git a/keystoneclient/tests/unit/test_cms.py b/keystoneclient/tests/unit/test_cms.py index 11078aeec..2671bcb49 100644 --- a/keystoneclient/tests/unit/test_cms.py +++ b/keystoneclient/tests/unit/test_cms.py @@ -13,8 +13,8 @@ import errno import os import subprocess +from unittest import mock -import mock import testresources from testtools import matchers diff --git a/keystoneclient/tests/unit/test_discovery.py b/keystoneclient/tests/unit/test_discovery.py index cc7fb0fc7..57c7002cc 100644 --- a/keystoneclient/tests/unit/test_discovery.py +++ b/keystoneclient/tests/unit/test_discovery.py @@ -15,7 +15,6 @@ from keystoneauth1 import fixture from oslo_serialization import jsonutils -import six from testtools import matchers from keystoneclient import _discover @@ -87,7 +86,7 @@ V2_AUTH_RESPONSE = jsonutils.dumps({ "access": { "token": { - "expires": "2020-01-01T00:00:10.000123Z", + "expires": "2999-01-01T00:00:10.000123Z", "id": 'fakeToken', "tenant": { "id": '1' @@ -105,7 +104,7 @@ V3_MEDIA_TYPES = V3_VERSION.media_types V3_VERSION.updated_str = UPDATED -V3_TOKEN = six.u('3e2813b7ba0b4006840c3825860b86ed'), +V3_TOKEN = ('3e2813b7ba0b4006840c3825860b86ed',) V3_AUTH_RESPONSE = jsonutils.dumps({ "token": { "methods": [ @@ -113,7 +112,7 @@ "password" ], - "expires_at": "2020-01-01T00:00:10.000123Z", + "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": '1', @@ -243,7 +242,7 @@ def test_available_versions_basics(self): 'cinder': jsonutils.dumps(CINDER_EXAMPLES), 'glance': jsonutils.dumps(GLANCE_EXAMPLES)} - for path, text in six.iteritems(examples): + for path, text in examples.items(): url = "%s%s" % (BASE_URL, path) self.requests_mock.get(url, status_code=300, text=text) diff --git a/keystoneclient/tests/unit/test_ec2utils.py b/keystoneclient/tests/unit/test_ec2utils.py index 5102f6b31..12cf57531 100644 --- a/keystoneclient/tests/unit/test_ec2utils.py +++ b/keystoneclient/tests/unit/test_ec2utils.py @@ -12,8 +12,6 @@ # 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 @@ -265,3 +263,42 @@ def test_generate_v4_port_nostrip(self): 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) diff --git a/keystoneclient/tests/unit/test_fixtures.py b/keystoneclient/tests/unit/test_fixtures.py index 345ae457c..d7fd26e66 100644 --- a/keystoneclient/tests/unit/test_fixtures.py +++ b/keystoneclient/tests/unit/test_fixtures.py @@ -12,7 +12,6 @@ import uuid -import six from keystoneclient import fixture from keystoneclient.tests.unit import utils @@ -246,7 +245,7 @@ def test_catalog(self): # the endpoint content below easier. self.assertTrue(endpoint.pop('id')) - for interface, url in six.iteritems(endpoints): + for interface, url in endpoints.items(): endpoint = {'interface': interface, 'url': url, 'region': region, 'region_id': region} self.assertIn(endpoint, service['endpoints']) diff --git a/keystoneclient/tests/unit/test_hacking_checks.py b/keystoneclient/tests/unit/test_hacking_checks.py deleted file mode 100644 index f1e81c999..000000000 --- a/keystoneclient/tests/unit/test_hacking_checks.py +++ /dev/null @@ -1,50 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import textwrap - -import mock -import pep8 -import testtools - -from keystoneclient.tests.hacking import checks -from keystoneclient.tests.unit import client_fixtures - - -class TestCheckOsloNamespaceImports(testtools.TestCase): - def setUp(self): - super(TestCheckOsloNamespaceImports, self).setUp() - self.useFixture(client_fixtures.Deprecations()) - - # We are patching pep8 so that only the check under test is actually - # installed. - @mock.patch('pep8._checks', - {'physical_line': {}, 'logical_line': {}, 'tree': {}}) - def run_check(self, code): - pep8.register_check(checks.check_oslo_namespace_imports) - - lines = textwrap.dedent(code).strip().splitlines(True) - - checker = pep8.Checker(lines=lines) - checker.check_all() - checker.report._deferred_print.sort() - return checker.report._deferred_print - - def assert_has_errors(self, code, expected_errors=None): - actual_errors = [e[:3] for e in self.run_check(code)] - self.assertEqual(expected_errors or [], actual_errors) - - def test(self): - code_ex = self.useFixture(client_fixtures.HackingCode()) - code = code_ex.oslo_namespace_imports['code'] - errors = code_ex.oslo_namespace_imports['expected_errors'] - self.assert_has_errors(code, expected_errors=errors) diff --git a/keystoneclient/tests/unit/test_http.py b/keystoneclient/tests/unit/test_http.py index 56f116c5a..4a8053fab 100644 --- a/keystoneclient/tests/unit/test_http.py +++ b/keystoneclient/tests/unit/test_http.py @@ -12,9 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +import io import logging -import six from testtools import matchers from keystoneclient import exceptions @@ -97,7 +97,7 @@ def test_get_error_with_json_resp(self): cl.get('/hi') except exceptions.BadRequest as exc: exc_raised = True - self.assertEqual(exc.message, "Error message string") + self.assertEqual(exc.message, "Error message string (HTTP 400)") self.assertTrue(exc_raised, 'Exception not raised.') def test_post(self): @@ -133,19 +133,6 @@ def test_forwarded_for(self): 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): @@ -153,7 +140,7 @@ class BasicRequestTests(utils.TestCase): def setUp(self): super(BasicRequestTests, self).setUp() - self.logger_message = six.moves.cStringIO() + self.logger_message = io.StringIO() handler = logging.StreamHandler(self.logger_message) handler.setLevel(logging.DEBUG) @@ -166,22 +153,24 @@ def setUp(self): self.addCleanup(self.logger.setLevel, level) def request(self, method='GET', response='Test Response', status_code=200, - url=None, **kwargs): + url=None, headers={}, **kwargs): if not url: url = self.url self.requests_mock.register_uri(method, url, text=response, - status_code=status_code) + status_code=status_code, + headers=headers) with self.deprecations.expect_deprecations_here(): - return httpclient.request(url, method, **kwargs) + 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) + self.request(method=method, status_code=status, response=response, + headers={'Content-Type': 'application/json'}) self.assertEqual(self.requests_mock.last_request.method, method) @@ -200,16 +189,17 @@ def test_headers(self): self.request(headers=headers) - for k, v in six.iteritems(headers): + for k, v in headers.items(): self.assertRequestHeaderEqual(k, v) - for header in six.iteritems(headers): + 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) + 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)) diff --git a/keystoneclient/tests/unit/test_https.py b/keystoneclient/tests/unit/test_https.py index 4e8d260cf..315a17aaa 100644 --- a/keystoneclient/tests/unit/test_https.py +++ b/keystoneclient/tests/unit/test_https.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock import requests +from unittest import mock from keystoneclient import httpclient from keystoneclient.tests.unit import utils diff --git a/keystoneclient/tests/unit/test_keyring.py b/keystoneclient/tests/unit/test_keyring.py index 7d30d980c..0a5bf7150 100644 --- a/keystoneclient/tests/unit/test_keyring.py +++ b/keystoneclient/tests/unit/test_keyring.py @@ -11,8 +11,8 @@ # under the License. import datetime +from unittest import mock -import mock from oslo_utils import timeutils from keystoneclient import access diff --git a/keystoneclient/tests/unit/test_session.py b/keystoneclient/tests/unit/test_session.py index 8fb364ac0..71a8e2743 100644 --- a/keystoneclient/tests/unit/test_session.py +++ b/keystoneclient/tests/unit/test_session.py @@ -1,3 +1,5 @@ +# -*- 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 @@ -11,16 +13,16 @@ # under the License. import argparse +from io import StringIO import itertools import logging +from unittest import mock 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 @@ -149,12 +151,14 @@ def test_session_debug_output(self): in order to redact secure headers while debug is true. """ session = client_session.Session(verify=False) - headers = {'HEADERA': 'HEADERVALB'} + 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, } - body = 'BODYRESPONSE' - data = 'BODYDATA' + '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) @@ -167,13 +171,13 @@ def test_session_debug_output(self): self.assertIn(body, self.logger.output) self.assertIn("'%s'" % data, self.logger.output) - for k, v in six.iteritems(headers): + 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 six.iteritems(security_headers): + 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) @@ -181,51 +185,70 @@ def test_session_debug_output(self): def test_logs_failed_output(self): """Test that output is logged even for failed requests.""" session = client_session.Session() - body = uuid.uuid4().hex + body = {uuid.uuid4().hex: uuid.uuid4().hex} - self.stub_url('GET', text=body, status_code=400) + 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'unicode_data' + data = 'αβγδ' 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(unicode(self.TEST_URL), data=data) - - self.assertIn("Replaced characters that could not be decoded" - " in log output", self.logger.output) - - # Our data payload should have changed to - # include the replacement char - self.assertIn(u"-d 'my data\ufffd'", self.logger.output) - def test_logging_cacerts(self): path_to_certs = '/path/to/certs' session = client_session.Session(verify=path_to_certs) @@ -284,11 +307,12 @@ def _ssl_error(request, context): # 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) + self.assertRaisesRegex( + exceptions.SSLError, + msg, + session.get, + self.TEST_URL, + ) def test_mask_password_in_http_log_response(self): session = client_session.Session() @@ -315,7 +339,8 @@ def fake_debug(msg): "auth_username": "verybadusername", "auth_method": "CHAP"}}} body_json = jsonutils.dumps(body) - response = mock.Mock(text=body_json, status_code=200, headers={}) + 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) @@ -762,28 +787,29 @@ def test_logger_object_passed(self): logger.setLevel(logging.DEBUG) logger.propagate = False - io = six.StringIO() + io = StringIO() handler = logging.StreamHandler(io) logger.addHandler(handler) auth = AuthPlugin() sess = client_session.Session(auth=auth) - response = uuid.uuid4().hex + response = {uuid.uuid4().hex: uuid.uuid4().hex} self.stub_url('GET', - text=response, - headers={'Content-Type': 'text/html'}) + json=response, + headers={'Content-Type': 'application/json'}) resp = sess.get(self.TEST_URL, logger=logger) - self.assertEqual(response, resp.text) + self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) - self.assertIn(response, output) + self.assertIn(list(response.keys())[0], output) + self.assertIn(list(response.values())[0], output) - self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(response, self.logger.output) + self.assertNotIn(list(response.keys())[0], self.logger.output) + self.assertNotIn(list(response.values())[0], self.logger.output) class AdapterTest(utils.TestCase): @@ -957,7 +983,7 @@ def test_logger_object_passed(self): logger.setLevel(logging.DEBUG) logger.propagate = False - io = six.StringIO() + io = StringIO() handler = logging.StreamHandler(io) logger.addHandler(handler) @@ -965,21 +991,22 @@ def test_logger_object_passed(self): sess = client_session.Session(auth=auth) adpt = adapter.Adapter(sess, auth=auth, logger=logger) - response = uuid.uuid4().hex + response = {uuid.uuid4().hex: uuid.uuid4().hex} - self.stub_url('GET', text=response, - headers={'Content-Type': 'text/html'}) + self.stub_url('GET', json=response, + headers={'Content-Type': 'application/json'}) resp = adpt.get(self.TEST_URL, logger=logger) - self.assertEqual(response, resp.text) + self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) - self.assertIn(response, output) + self.assertIn(list(response.keys())[0], output) + self.assertIn(list(response.values())[0], output) - self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(response, self.logger.output) + self.assertNotIn(list(response.keys())[0], self.logger.output) + self.assertNotIn(list(response.values())[0], self.logger.output) class ConfLoadingTests(utils.TestCase): diff --git a/keystoneclient/tests/unit/test_utils.py b/keystoneclient/tests/unit/test_utils.py index 01443314c..2aa5e92be 100644 --- a/keystoneclient/tests/unit/test_utils.py +++ b/keystoneclient/tests/unit/test_utils.py @@ -11,7 +11,6 @@ # under the License. from keystoneauth1 import exceptions as ksa_exceptions -import six import testresources from testtools import matchers @@ -33,7 +32,7 @@ class FakeManager(object): resources = { '1234': {'name': 'entity_one'}, '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0': {'name': 'entity_two'}, - '\xe3\x82\xbdtest': {'name': u'\u30bdtest'}, + '\xe3\x82\xbdtest': {'name': '\u30bdtest'}, '5678': {'name': '9876'} } @@ -112,8 +111,7 @@ class HashSignedTokenTestCase(test_utils.TestCase, 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 = 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)) @@ -123,8 +121,7 @@ def test_default_md5(self): def test_sha256(self): """Can also hash with sha256.""" token = self.examples.SIGNED_TOKEN_SCOPED - if six.PY3: - token = token.encode('utf-8') + 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)) diff --git a/keystoneclient/tests/unit/utils.py b/keystoneclient/tests/unit/utils.py index 378f912f0..4463213b1 100644 --- a/keystoneclient/tests/unit/utils.py +++ b/keystoneclient/tests/unit/utils.py @@ -12,6 +12,7 @@ import logging import sys +import urllib.parse as urlparse import uuid import fixtures @@ -19,8 +20,6 @@ import requests import requests_mock from requests_mock.contrib import fixture -import six -from six.moves.urllib import parse as urlparse import testscenarios import testtools @@ -97,7 +96,7 @@ def assertQueryStringContains(self, **kwargs): parts = urlparse.urlparse(self.requests_mock.last_request.url) qs = urlparse.parse_qs(parts.query, keep_blank_values=True) - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): self.assertIn(k, qs) self.assertIn(v, qs[k]) diff --git a/keystoneclient/tests/unit/v2_0/client_fixtures.py b/keystoneclient/tests/unit/v2_0/client_fixtures.py index 019b9445d..d3d8bcac3 100644 --- a/keystoneclient/tests/unit/v2_0/client_fixtures.py +++ b/keystoneclient/tests/unit/v2_0/client_fixtures.py @@ -9,8 +9,6 @@ # 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 diff --git a/keystoneclient/tests/unit/v2_0/test_auth.py b/keystoneclient/tests/unit/v2_0/test_auth.py index f9512f62e..b73352471 100644 --- a/keystoneclient/tests/unit/v2_0/test_auth.py +++ b/keystoneclient/tests/unit/v2_0/test_auth.py @@ -28,7 +28,7 @@ def setUp(self): self.TEST_RESPONSE_DICT = { "access": { "token": { - "expires": "2020-01-01T00:00:10.000123Z", + "expires": "2999-01-01T00:00:10.000123Z", "id": self.TEST_TOKEN, "tenant": { "id": self.TEST_TENANT_ID @@ -61,7 +61,7 @@ def test_authenticate_success_expired(self): # Build a new response TEST_TOKEN = "abcdef" - resp_b['access']['token']['expires'] = '2020-01-01T00:00:10.000123Z' + 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 @@ -149,7 +149,7 @@ def test_authenticate_success_password_unscoped(self): auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) - self.assertFalse('serviceCatalog' in cs.service_catalog.catalog) + self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): @@ -222,7 +222,7 @@ def test_authenticate_success_token_unscoped(self): auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) - self.assertFalse('serviceCatalog' in cs.service_catalog.catalog) + self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): diff --git a/keystoneclient/tests/unit/v2_0/test_client.py b/keystoneclient/tests/unit/v2_0/test_client.py index 05cc07535..7fe9b1813 100644 --- a/keystoneclient/tests/unit/v2_0/test_client.py +++ b/keystoneclient/tests/unit/v2_0/test_client.py @@ -10,12 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. -import json import uuid +from oslo_serialization import jsonutils + from keystoneauth1 import fixture -import six +from keystoneauth1 import session as auth_session from keystoneclient.auth import token_endpoint from keystoneclient import exceptions from keystoneclient import session @@ -75,10 +76,10 @@ def test_auth_ref_load(self): password='password', project_name='exampleproject', auth_url=self.TEST_URL) - cache = json.dumps(cl.auth_ref) + 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=json.loads(cache)) + 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) @@ -100,11 +101,11 @@ def test_auth_ref_load_with_overridden_arguments(self): password='password', project_name='exampleproject', auth_url=self.TEST_URL) - cache = json.dumps(cl.auth_ref) + 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=json.loads(cache), + 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(): @@ -206,8 +207,15 @@ def test_client_params(self): cl = client.Client(session=sess, **opts) - for k, v in six.iteritems(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) diff --git a/keystoneclient/tests/unit/v2_0/test_discovery.py b/keystoneclient/tests/unit/v2_0/test_discovery.py index 5afe59ab1..a3700e0e1 100644 --- a/keystoneclient/tests/unit/v2_0/test_discovery.py +++ b/keystoneclient/tests/unit/v2_0/test_discovery.py @@ -29,11 +29,11 @@ def setUp(self): "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", }, {"rel": "describedby", diff --git a/keystoneclient/tests/unit/v2_0/test_extensions.py b/keystoneclient/tests/unit/v2_0/test_extensions.py index 662d380e7..3927bc07c 100644 --- a/keystoneclient/tests/unit/v2_0/test_extensions.py +++ b/keystoneclient/tests/unit/v2_0/test_extensions.py @@ -22,7 +22,7 @@ def setUp(self): "values": [ { 'name': 'OpenStack Keystone User CRUD', - 'namespace': 'http://docs.openstack.org/' + 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-KSCRUD/v1.0', 'updated': '2013-07-07T12:00:0-00:00', 'alias': 'OS-KSCRUD', @@ -36,7 +36,7 @@ def setUp(self): }, { 'name': 'OpenStack EC2 API', - 'namespace': 'http://docs.openstack.org/' + 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-EC2/v1.0', 'updated': '2013-09-07T12:00:0-00:00', 'alias': 'OS-EC2', diff --git a/keystoneclient/tests/unit/v3/client_fixtures.py b/keystoneclient/tests/unit/v3/client_fixtures.py index 8e86208e9..e22e7da1c 100644 --- a/keystoneclient/tests/unit/v3/client_fixtures.py +++ b/keystoneclient/tests/unit/v3/client_fixtures.py @@ -9,8 +9,6 @@ # 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 diff --git a/keystoneclient/tests/unit/v3/saml2_fixtures.py b/keystoneclient/tests/unit/v3/saml2_fixtures.py index 3cf2e772a..17c1395a8 100644 --- a/keystoneclient/tests/unit/v3/saml2_fixtures.py +++ b/keystoneclient/tests/unit/v3/saml2_fixtures.py @@ -10,9 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import six - -SP_SOAP_RESPONSE = six.b(""" -""") +""" -SAML2_ASSERTION = six.b(""" +SAML2_ASSERTION = b""" VALUE= -""") +""" UNSCOPED_TOKEN_HEADER = 'UNSCOPED_TOKEN' diff --git a/keystoneclient/tests/unit/v3/test_access_rules.py b/keystoneclient/tests/unit/v3/test_access_rules.py new file mode 100644 index 000000000..d3e22f8dd --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_access_rules.py @@ -0,0 +1,41 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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) diff --git a/keystoneclient/tests/unit/v3/test_application_credentials.py b/keystoneclient/tests/unit/v3/test_application_credentials.py new file mode 100644 index 000000000..6e4bba3e6 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_application_credentials.py @@ -0,0 +1,137 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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) diff --git a/keystoneclient/tests/unit/v3/test_auth.py b/keystoneclient/tests/unit/v3/test_auth.py index f71990576..d3c44adc7 100644 --- a/keystoneclient/tests/unit/v3/test_auth.py +++ b/keystoneclient/tests/unit/v3/test_auth.py @@ -28,7 +28,7 @@ def setUp(self): "password" ], - "expires_at": "2020-01-01T00:00:10.000123Z", + "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": self.TEST_DOMAIN_ID, @@ -222,7 +222,7 @@ def test_authenticate_success_password_unscoped(self): auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) - self.assertFalse('catalog' in cs.service_catalog.catalog) + self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): @@ -232,7 +232,7 @@ def test_auth_url_token_authentication(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, - base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + base_url=self.TEST_PUBLIC_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): @@ -325,7 +325,7 @@ def test_authenticate_success_token_unscoped(self): auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) - self.assertFalse('catalog' in cs.service_catalog.catalog) + self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): @@ -335,7 +335,7 @@ def test_allow_override_of_auth_token(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, - base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + base_url=self.TEST_PUBLIC_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): diff --git a/keystoneclient/tests/unit/v3/test_auth_manager.py b/keystoneclient/tests/unit/v3/test_auth_manager.py index 18579607a..dec8b0c05 100644 --- a/keystoneclient/tests/unit/v3/test_auth_manager.py +++ b/keystoneclient/tests/unit/v3/test_auth_manager.py @@ -62,3 +62,17 @@ def test_get_domains(self): for d in domains: self.assertIsInstance(d, auth.Domain) + + def test_get_systems(self): + body = {'system': [{ + 'all': True, + }]} + + self.stub_url('GET', ['auth', 'system'], json=body) + + systems = self.client.auth.systems() + system = systems[0] + + self.assertEqual(1, len(systems)) + self.assertIsInstance(system, auth.System) + self.assertTrue(system.all) diff --git a/keystoneclient/tests/unit/v3/test_auth_oidc.py b/keystoneclient/tests/unit/v3/test_auth_oidc.py index b0140dd07..278800bca 100644 --- a/keystoneclient/tests/unit/v3/test_auth_oidc.py +++ b/keystoneclient/tests/unit/v3/test_auth_oidc.py @@ -10,10 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import urllib.parse import uuid from oslo_config import fixture as config -from six.moves import urllib + import testtools from keystoneclient.auth import conf diff --git a/keystoneclient/tests/unit/v3/test_auth_saml2.py b/keystoneclient/tests/unit/v3/test_auth_saml2.py index b74983af9..8c2f67daa 100644 --- a/keystoneclient/tests/unit/v3/test_auth_saml2.py +++ b/keystoneclient/tests/unit/v3/test_auth_saml2.py @@ -11,12 +11,12 @@ # under the License. import os +import urllib.parse 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 @@ -106,28 +106,7 @@ def setUp(self): 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) + pass def test_initial_sp_call(self): """Test initial call, expect SOAP message.""" @@ -465,31 +444,7 @@ def setUp(self): 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) + pass def test_get_adfs_security_token(self): """Test ADFSUnscopedToken._get_adfs_security_token().""" diff --git a/keystoneclient/tests/unit/v3/test_client.py b/keystoneclient/tests/unit/v3/test_client.py index c401ba680..82088fdfc 100644 --- a/keystoneclient/tests/unit/v3/test_client.py +++ b/keystoneclient/tests/unit/v3/test_client.py @@ -11,11 +11,11 @@ # under the License. import copy -import json import uuid -import six +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 @@ -90,10 +90,10 @@ def test_auth_ref_load(self): password='password', project_id=token.project_id, auth_url=self.TEST_URL) - cache = json.dumps(c.auth_ref) + 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=json.loads(cache)) + 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) @@ -124,10 +124,10 @@ def test_auth_ref_load_with_overridden_arguments(self): password='password', project_id=project_id, auth_url=self.TEST_URL) - cache = json.dumps(c.auth_ref) + 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=json.loads(cache), + 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) @@ -256,8 +256,15 @@ def test_client_params(self): cl = client.Client(session=sess, **opts) - for k, v in six.iteritems(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) diff --git a/keystoneclient/tests/unit/v3/test_discover.py b/keystoneclient/tests/unit/v3/test_discover.py index 898d46b0d..f54b2f9c4 100644 --- a/keystoneclient/tests/unit/v3/test_discover.py +++ b/keystoneclient/tests/unit/v3/test_discover.py @@ -27,12 +27,12 @@ def setUp(self): "href": "http://127.0.0.1:5000/v3.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "identity-dev-guide-3.pdf", }, ]}, @@ -44,12 +44,12 @@ def setUp(self): "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", } ]}], diff --git a/keystoneclient/tests/unit/v3/test_domain_configs.py b/keystoneclient/tests/unit/v3/test_domain_configs.py new file mode 100644 index 000000000..2a7df0927 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_domain_configs.py @@ -0,0 +1,96 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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) diff --git a/keystoneclient/tests/unit/v3/test_endpoint_filter.py b/keystoneclient/tests/unit/v3/test_endpoint_filter.py index 2eed70586..62e89cb35 100644 --- a/keystoneclient/tests/unit/v3/test_endpoint_filter.py +++ b/keystoneclient/tests/unit/v3/test_endpoint_filter.py @@ -36,6 +36,13 @@ def new_endpoint_ref(self, **kwargs): 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). @@ -147,3 +154,140 @@ def test_list_projects_for_endpoint(self): 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) diff --git a/keystoneclient/tests/unit/v3/test_endpoint_groups.py b/keystoneclient/tests/unit/v3/test_endpoint_groups.py new file mode 100644 index 000000000..364fd53c2 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_endpoint_groups.py @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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 diff --git a/keystoneclient/tests/unit/v3/test_federation.py b/keystoneclient/tests/unit/v3/test_federation.py index a760ed7d8..08391c7c1 100644 --- a/keystoneclient/tests/unit/v3/test_federation.py +++ b/keystoneclient/tests/unit/v3/test_federation.py @@ -11,6 +11,7 @@ # under the License. import copy +import fixtures import uuid from keystoneauth1 import exceptions @@ -18,7 +19,6 @@ 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 @@ -422,7 +422,7 @@ def _mock_k2k_flow_urls(self): self.requests_mock.register_uri( 'POST', self.REQUEST_ECP_URL, - content=six.b(k2k_fixtures.ECP_ENVELOPE), + content=k2k_fixtures.ECP_ENVELOPE.encode(), headers={'Content-Type': 'application/vnd.paos+xml'}, status_code=200) @@ -432,7 +432,7 @@ def _mock_k2k_flow_urls(self): self.requests_mock.register_uri( 'POST', self.SP_URL, - content=six.b(k2k_fixtures.TOKEN_BASED_ECP), + content=k2k_fixtures.TOKEN_BASED_ECP.encode(), headers={'Content-Type': 'application/vnd.paos+xml'}, status_code=302) @@ -582,3 +582,244 @@ def test_create(self): 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') diff --git a/keystoneclient/tests/unit/v3/test_limits.py b/keystoneclient/tests/unit/v3/test_limits.py new file mode 100644 index 000000000..0dca67d9b --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_limits.py @@ -0,0 +1,77 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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) diff --git a/keystoneclient/tests/unit/v3/test_oauth1.py b/keystoneclient/tests/unit/v3/test_oauth1.py index f939244dd..a15d94b83 100644 --- a/keystoneclient/tests/unit/v3/test_oauth1.py +++ b/keystoneclient/tests/unit/v3/test_oauth1.py @@ -11,11 +11,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from unittest import mock + +import fixtures +from urllib import parse as urlparse import uuid -import mock -import six -from six.moves.urllib import parse as urlparse from testtools import matchers from keystoneclient import session @@ -104,7 +105,7 @@ def _validate_oauth_headers(self, auth_header, oauth_client): self.assertEqual('HMAC-SHA1', parameters['oauth_signature_method']) self.assertEqual('1.0', parameters['oauth_version']) - self.assertIsInstance(parameters['oauth_nonce'], six.string_types) + self.assertIsInstance(parameters['oauth_nonce'], str) self.assertEqual(oauth_client.client_key, parameters['oauth_consumer_key']) if oauth_client.resource_owner_key: @@ -277,6 +278,53 @@ def test_oauth_authenticate_success(self): 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): diff --git a/keystoneclient/tests/unit/v3/test_projects.py b/keystoneclient/tests/unit/v3/test_projects.py index 48477ed4c..99186f17f 100644 --- a/keystoneclient/tests/unit/v3/test_projects.py +++ b/keystoneclient/tests/unit/v3/test_projects.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures import uuid from keystoneauth1 import exceptions as ksa_exceptions @@ -54,8 +55,7 @@ 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) + 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)) @@ -63,6 +63,18 @@ def test_list_projects_for_domain(self): 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 @@ -312,3 +324,146 @@ def test_update_with_parent_project(self): # 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') diff --git a/keystoneclient/tests/unit/v3/test_registered_limits.py b/keystoneclient/tests/unit/v3/test_registered_limits.py new file mode 100644 index 000000000..1f612f8bb --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_registered_limits.py @@ -0,0 +1,76 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES 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) diff --git a/keystoneclient/tests/unit/v3/test_role_assignments.py b/keystoneclient/tests/unit/v3/test_role_assignments.py index b24799cbc..39b4b2355 100644 --- a/keystoneclient/tests/unit/v3/test_role_assignments.py +++ b/keystoneclient/tests/unit/v3/test_role_assignments.py @@ -23,6 +23,32 @@ def setUp(self): 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 @@ -65,7 +91,9 @@ def setUp(self): self.TEST_ALL_RESPONSE_LIST = (self.TEST_USER_PROJECT_LIST + self.TEST_GROUP_PROJECT_LIST + - self.TEST_USER_DOMAIN_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)) @@ -150,6 +178,50 @@ def test_domain_assignments_list(self): 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', @@ -193,7 +265,7 @@ def test_include_names_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, - '?include_names'], + '?include_names=True'], entity=ref_list) returned_list = self.manager.list(include_names=True) diff --git a/keystoneclient/tests/unit/v3/test_roles.py b/keystoneclient/tests/unit/v3/test_roles.py index 7dfd7f2b3..0e531e736 100644 --- a/keystoneclient/tests/unit/v3/test_roles.py +++ b/keystoneclient/tests/unit/v3/test_roles.py @@ -599,70 +599,226 @@ def test_user_group_role_revoke_fails(self): group=group_id, user=user_id) - def test_implied_role_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=200) - self.manager.check_implied(prior_role_id, implied_role_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_implied_role_get(self): + def test_check(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex - self.stub_url('GET', + self.stub_url('HEAD', ['roles', prior_role_id, 'implies', implied_role_id], - json={'role': {}}, status_code=204) - self.manager.get_implied(prior_role_id, implied_role_id) + result = self.manager.check(prior_role_id, implied_role_id) + self.assertTrue(result) - def test_implied_role_create(self): - prior_role_id = uuid.uuid4().hex - implied_role_id = uuid.uuid4().hex - test_json = { + 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": { - "prior_role": { - "id": prior_role_id, - "links": {}, - "name": "prior role name" + "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_role_id, - "links": {}, - "name": "implied role name" + "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.stub_url('PUT', - ['roles', prior_role_id, 'implies', implied_role_id], - json=test_json, - status_code=200) + ['roles', prior_id, 'implies', implied_id], + json=mock_response, + status_code=201) - returned_rule = self.manager.create_implied( - prior_role_id, implied_role_id) + manager_result = self.manager.create(prior_id, implied_id) - self.assertEqual(test_json['role_inference']['implies'], - returned_rule.implies) - self.assertEqual(test_json['role_inference']['prior_role'], - returned_rule.prior_role) + 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_implied_role_delete(self): + 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=200) + status_code=204) - self.manager.delete_implied(prior_role_id, implied_role_id) + 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 + } + }] + } - def test_list_role_inferences(self, **kwargs): self.stub_url('GET', - ['role_inferences', ''], - json={'role_inferences': {}}, - status_code=204) + ['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) - self.manager.list_role_inferences() + 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") diff --git a/keystoneclient/tests/unit/v3/test_simple_cert.py b/keystoneclient/tests/unit/v3/test_simple_cert.py index 1c4a245d2..a059f08d7 100644 --- a/keystoneclient/tests/unit/v3/test_simple_cert.py +++ b/keystoneclient/tests/unit/v3/test_simple_cert.py @@ -11,10 +11,12 @@ # 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): @@ -36,5 +38,36 @@ def test_get_certificates(self): 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) diff --git a/keystoneclient/tests/unit/v3/test_tokens.py b/keystoneclient/tests/unit/v3/test_tokens.py index 0208f53b7..1a8fd6831 100644 --- a/keystoneclient/tests/unit/v3/test_tokens.py +++ b/keystoneclient/tests/unit/v3/test_tokens.py @@ -64,11 +64,6 @@ def test_get_revoked_audit_id_only(self): self.assertQueryStringIs('audit_id_only') self.assertEqual(sample_revoked_response, resp) - def test_get_revoked_audit_id_only_positional_exc(self): - # When get_revoked(True) an exception is raised because this must be - # called with named parameter. - self.assertRaises(TypeError, self.client.tokens.get_revoked, True) - def test_validate_token_with_token_id(self): # Can validate a token passing a string token ID. token_id = uuid.uuid4().hex @@ -145,6 +140,19 @@ def test_validate_token_nocatalog(self): 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) diff --git a/keystoneclient/tests/unit/v3/test_trusts.py b/keystoneclient/tests/unit/v3/test_trusts.py index 72fb5b764..1c74ac9b9 100644 --- a/keystoneclient/tests/unit/v3/test_trusts.py +++ b/keystoneclient/tests/unit/v3/test_trusts.py @@ -64,6 +64,22 @@ def test_create_roles(self): 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 diff --git a/keystoneclient/tests/unit/v3/test_users.py b/keystoneclient/tests/unit/v3/test_users.py index e0a34461e..a7f03f9ce 100644 --- a/keystoneclient/tests/unit/v3/test_users.py +++ b/keystoneclient/tests/unit/v3/test_users.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock import uuid from keystoneclient import exceptions diff --git a/keystoneclient/tests/unit/v3/utils.py b/keystoneclient/tests/unit/v3/utils.py index 2c9c86d30..cb3839a9c 100644 --- a/keystoneclient/tests/unit/v3/utils.py +++ b/keystoneclient/tests/unit/v3/utils.py @@ -10,13 +10,16 @@ # License for the specific language governing permissions and limitations # under the License. +import requests import uuid -import six -from six.moves.urllib import parse as urlparse +from 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): @@ -45,6 +48,7 @@ class UnauthenticatedTestCase(utils.TestCase): class TestCase(UnauthenticatedTestCase): TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v3" + TEST_PUBLIC_IDENTITY_ENDPOINT = "http://127.0.0.1:5000/v3" TEST_SERVICE_CATALOG = [{ "endpoints": [{ @@ -222,6 +226,8 @@ 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') @@ -274,7 +280,7 @@ def _get_expected_path(self, expected_path=None): 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``. + """Test ``entities.list(id=x)`` being rewritten as ``GET /v3/entities/x``. # noqa This tests an edge case of each manager's list() implementation, to ensure that it "does the right thing" when users call ``.list()`` @@ -301,7 +307,7 @@ def test_list(self, ref_list=None, expected_path=None, qs_args = self.requests_mock.last_request.qs qs_args_expected = expected_query or filter_kwargs - for key, value in six.iteritems(qs_args_expected): + 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 @@ -344,6 +350,8 @@ def test_find(self, ref=None): 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) @@ -372,3 +380,17 @@ def test_delete(self, ref=None): 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 diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index 4685111f4..1c31f2bd3 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -12,34 +12,26 @@ import getpass import hashlib -import logging import sys from keystoneauth1 import exceptions as ksa_exceptions from oslo_utils import timeutils -# NOTE(stevemar): do not remove positional. We need this to stay for a while -# since versions of auth_token require it here. -from positional import positional # noqa -import six from keystoneclient import exceptions as ksc_exceptions -logger = logging.getLogger(__name__) - - 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 + # 'name_or_id' as a bytes instead pass # finally try to find entity by name try: - if isinstance(name_or_id, six.binary_type): + if isinstance(name_or_id, bytes): name_or_id = name_or_id.decode('utf-8', 'strict') return manager.find(name=name_or_id) except ksa_exceptions.NotFound: @@ -53,30 +45,6 @@ def find_resource(manager, name_or_id): raise ksc_exceptions.CommandError(msg) -def unauthenticated(f): - """Add 'unauthenticated' attribute to decorated function. - - Usage:: - - @unauthenticated - def mymethod(f): - ... - """ - f.unauthenticated = True - return f - - -def isunauthenticated(f): - """Check if function requires authentication. - - Checks to see if the function is marked as not requiring authentication - with the @unauthenticated decorator. - - Returns True if decorator is set to True, False otherwise. - """ - return getattr(f, 'unauthenticated', False) - - def hash_signed_token(signed_text, mode='md5'): hash_ = hashlib.new(mode) hash_.update(signed_text) @@ -145,7 +113,7 @@ def isotime(at=None, subsecond=False): if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' - st += ('Z' if tz == 'UTC' else tz) + st += ('Z' if (tz == 'UTC' or tz == 'UTC+00:00') else tz) return st diff --git a/keystoneclient/v2_0/tenants.py b/keystoneclient/v2_0/tenants.py index d375da611..91731c45e 100644 --- a/keystoneclient/v2_0/tenants.py +++ b/keystoneclient/v2_0/tenants.py @@ -15,8 +15,7 @@ # under the License. from keystoneauth1 import plugin -import six -from six.moves import urllib +import urllib.parse from keystoneclient import base from keystoneclient import exceptions @@ -92,7 +91,7 @@ def create(self, tenant_name, description=None, enabled=True, **kwargs): "enabled": enabled}} # Allow Extras Passthru and ensure we don't clobber primary arguments. - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if k not in params['tenant']: params['tenant'][k] = v @@ -142,7 +141,7 @@ def update(self, tenant_id, tenant_name=None, description=None, body['tenant']['description'] = description # Allow Extras Passthru and ensure we don't clobber primary arguments. - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if k not in body['tenant']: body['tenant'][k] = v diff --git a/keystoneclient/v2_0/tokens.py b/keystoneclient/v2_0/tokens.py index 8e647966c..14c054eeb 100644 --- a/keystoneclient/v2_0/tokens.py +++ b/keystoneclient/v2_0/tokens.py @@ -12,7 +12,6 @@ from keystoneauth1 import exceptions from keystoneauth1 import plugin -from positional import positional from keystoneclient import access from keystoneclient import base @@ -40,7 +39,6 @@ def tenant(self): class TokenManager(base.Manager): resource_class = Token - @positional(enforcement=positional.WARN) def authenticate(self, username=None, tenant_id=None, tenant_name=None, password=None, token=None, return_raw=False): if token: diff --git a/keystoneclient/v2_0/users.py b/keystoneclient/v2_0/users.py index f663626ac..706dafcdc 100644 --- a/keystoneclient/v2_0/users.py +++ b/keystoneclient/v2_0/users.py @@ -14,9 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. -from six.moves import urllib - from keystoneclient import base +import urllib.parse class User(base.Resource): diff --git a/keystoneclient/v3/access_rules.py b/keystoneclient/v3/access_rules.py new file mode 100644 index 000000000..78fd0159e --- /dev/null +++ b/keystoneclient/v3/access_rules.py @@ -0,0 +1,118 @@ +# 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.')) diff --git a/keystoneclient/v3/application_credentials.py b/keystoneclient/v3/application_credentials.py new file mode 100644 index 000000000..3e9286279 --- /dev/null +++ b/keystoneclient/v3/application_credentials.py @@ -0,0 +1,173 @@ +# 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. + +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, str): + 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``. # noqa + + :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.')) diff --git a/keystoneclient/v3/auth.py b/keystoneclient/v3/auth.py index 0009a3aef..4d85e24ea 100644 --- a/keystoneclient/v3/auth.py +++ b/keystoneclient/v3/auth.py @@ -16,21 +16,24 @@ from keystoneclient import base from keystoneclient.v3 import domains from keystoneclient.v3 import projects +from keystoneclient.v3 import system Domain = domains.Domain Project = projects.Project +System = system.System class AuthManager(base.Manager): """Retrieve auth context specific information. - The information returned by the auth routes is entirely dependant on the + 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' + _SYSTEM_URL = '/auth/system' def projects(self): """List projects that the specified token can be rescoped to. @@ -67,3 +70,23 @@ def domains(self): 'domains', obj_class=Domain, endpoint_filter=endpoint_filter) + + def systems(self): + """List Systems that the specified token can be rescoped to. + + At the moment this is either empty or "all". + + :returns: a list of systems. + :rtype: list of :class:`keystoneclient.v3.systems.System`. + + """ + try: + return self._list(self._SYSTEM_URL, + 'system', + obj_class=System) + except exceptions.EndpointNotFound: + endpoint_filter = {'interface': plugin.AUTH_INTERFACE} + return self._list(self._SYSTEM_URL, + 'system', + obj_class=System, + endpoint_filter=endpoint_filter) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 619de6071..e99b06549 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -22,6 +22,8 @@ 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 @@ -30,13 +32,17 @@ 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 @@ -116,6 +122,10 @@ class Client(httpclient.HTTPClient): :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` @@ -125,6 +135,11 @@ class Client(httpclient.HTTPClient): :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.\ @@ -146,6 +161,10 @@ class Client(httpclient.HTTPClient): :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` @@ -158,6 +177,10 @@ class Client(httpclient.HTTPClient): :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` @@ -201,24 +224,37 @@ def __init__(self, **kwargs): '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) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index 586a74a62..26d5a8745 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -15,12 +15,18 @@ 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.""" + """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' @@ -40,6 +46,23 @@ def _build_base_url(self, project=None, endpoint=None): 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): @@ -59,7 +82,7 @@ def delete_endpoint_from_project(self, project, endpoint): return super(EndpointFilterManager, self)._delete(url=base_url) def check_endpoint_in_project(self, project, endpoint): - """Check if project-endpoint association exist.""" + """Check if project-endpoint association exists.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) @@ -88,3 +111,53 @@ def list_projects_for_endpoint(self, endpoint): 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) diff --git a/keystoneclient/v3/contrib/endpoint_policy.py b/keystoneclient/v3/contrib/endpoint_policy.py index 24148c1ac..c65b1fbc7 100644 --- a/keystoneclient/v3/contrib/endpoint_policy.py +++ b/keystoneclient/v3/contrib/endpoint_policy.py @@ -39,17 +39,17 @@ def _act_on_policy_association_for_endpoint( def create_policy_association_for_endpoint(self, policy, endpoint): """Create an association between a policy and an endpoint.""" - self._act_on_policy_association_for_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.""" - self._act_on_policy_association_for_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.""" - self._act_on_policy_association_for_endpoint( + return self._act_on_policy_association_for_endpoint( policy, endpoint, self._delete) def _act_on_policy_association_for_service(self, policy, service, action): @@ -67,17 +67,17 @@ def _act_on_policy_association_for_service(self, policy, service, action): def create_policy_association_for_service(self, policy, service): """Create an association between a policy and a service.""" - self._act_on_policy_association_for_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.""" - self._act_on_policy_association_for_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.""" - self._act_on_policy_association_for_service( + return self._act_on_policy_association_for_service( policy, service, self._delete) def _act_on_policy_association_for_region_and_service( @@ -99,19 +99,19 @@ def _act_on_policy_association_for_region_and_service( def create_policy_association_for_region_and_service( self, policy, region, service): """Create an association between a policy and a service in a region.""" - self._act_on_policy_association_for_region_and_service( + 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.""" - self._act_on_policy_association_for_region_and_service( + 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.""" - self._act_on_policy_association_for_region_and_service( + return self._act_on_policy_association_for_region_and_service( policy, region, service, self._delete) def get_policy_for_endpoint(self, endpoint): @@ -130,9 +130,10 @@ def get_policy_for_endpoint(self, endpoint): 'endpoint_id': endpoint_id, 'ext_name': self.OS_EP_POLICY_EXT} - _resp, body = self.client.get(url) - return policies.Policy( - self, body[policies.PolicyManager.key], loaded=True) + 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. diff --git a/keystoneclient/v3/contrib/federation/base.py b/keystoneclient/v3/contrib/federation/base.py index 98567a232..c09bc2267 100644 --- a/keystoneclient/v3/contrib/federation/base.py +++ b/keystoneclient/v3/contrib/federation/base.py @@ -14,18 +14,17 @@ from keystoneauth1 import exceptions from keystoneauth1 import plugin -import six from keystoneclient import base -@six.add_metaclass(abc.ABCMeta) -class EntityManager(base.Manager): +class EntityManager(base.Manager, metaclass=abc.ABCMeta): """Manager class for listing federated accessible objects.""" resource_class = None - @abc.abstractproperty + @property + @abc.abstractmethod def object_type(self): raise exceptions.MethodNotImplemented diff --git a/keystoneclient/v3/contrib/federation/identity_providers.py b/keystoneclient/v3/contrib/federation/identity_providers.py index 85c4a73ff..221ec11f4 100644 --- a/keystoneclient/v3/contrib/federation/identity_providers.py +++ b/keystoneclient/v3/contrib/federation/identity_providers.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -40,7 +38,6 @@ def _build_url_and_put(self, **kwargs): return self._update(url, body=body, response_key=self.key, method='PUT') - @positional.method(0) def create(self, id, **kwargs): """Create Identity Provider object. @@ -48,8 +45,8 @@ def create(self, id, **kwargs): PUT /OS-FEDERATION/identity_providers/$identity_provider :param id: unique id of the identity provider. - :param kwargs: optional attributes: description (str), enabled - (boolean) and remote_ids (list). + :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` @@ -79,7 +76,7 @@ def list(self, **kwargs): GET /OS-FEDERATION/identity_providers :returns: a list of IdentityProvider resource objects. - :rtype: list + :rtype: List """ return super(IdentityProviderManager, self).list(**kwargs) diff --git a/keystoneclient/v3/contrib/federation/mappings.py b/keystoneclient/v3/contrib/federation/mappings.py index 24a9c7f42..a0e54ae6f 100644 --- a/keystoneclient/v3/contrib/federation/mappings.py +++ b/keystoneclient/v3/contrib/federation/mappings.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -41,7 +39,6 @@ def _build_url_and_put(self, **kwargs): response_key=self.key, method='PUT') - @positional.method(0) def create(self, mapping_id, **kwargs): """Create federation mapping. diff --git a/keystoneclient/v3/contrib/federation/protocols.py b/keystoneclient/v3/contrib/federation/protocols.py index 34daf0f7d..4fad689fe 100644 --- a/keystoneclient/v3/contrib/federation/protocols.py +++ b/keystoneclient/v3/contrib/federation/protocols.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -57,7 +55,6 @@ def _build_url_and_put(self, request_body=None, **kwargs): response_key=self.key, method='PUT') - @positional.method(3) def create(self, protocol_id, identity_provider, mapping, **kwargs): """Create federation protocol object and tie to the Identity Provider. diff --git a/keystoneclient/v3/contrib/federation/saml.py b/keystoneclient/v3/contrib/federation/saml.py index 9be657a57..435e45dc6 100644 --- a/keystoneclient/v3/contrib/federation/saml.py +++ b/keystoneclient/v3/contrib/federation/saml.py @@ -37,7 +37,7 @@ def create_saml_assertion(self, service_provider, token_id): headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(SAML2_ENDPOINT, json=body, headers=headers) - return resp.text + 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. @@ -56,7 +56,7 @@ def create_ecp_assertion(self, service_provider, token_id): headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(ECP_ENDPOINT, json=body, headers=headers) - return resp.text + return self._prepare_return_value(resp, resp.text) def _create_common_request(self, service_provider, token_id): headers = {'Content-Type': 'application/json'} diff --git a/keystoneclient/v3/contrib/federation/service_providers.py b/keystoneclient/v3/contrib/federation/service_providers.py index f731c394e..fed1257d1 100644 --- a/keystoneclient/v3/contrib/federation/service_providers.py +++ b/keystoneclient/v3/contrib/federation/service_providers.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -42,7 +40,6 @@ def _build_url_and_put(self, **kwargs): return self._update(url, body=body, response_key=self.key, method='PUT') - @positional.method(0) def create(self, id, **kwargs): """Create Service Provider object. diff --git a/keystoneclient/v3/contrib/oauth1/access_tokens.py b/keystoneclient/v3/contrib/oauth1/access_tokens.py index 37f194153..a64c5dddc 100644 --- a/keystoneclient/v3/contrib/oauth1/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth1/access_tokens.py @@ -11,8 +11,6 @@ # 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 @@ -48,4 +46,5 @@ def create(self, consumer_key, consumer_secret, request_key, http_method='POST') resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) - return self.resource_class(self, token) + return self._prepare_return_value(resp, + self.resource_class(self, token)) diff --git a/keystoneclient/v3/contrib/oauth1/request_tokens.py b/keystoneclient/v3/contrib/oauth1/request_tokens.py index 59f06bcba..7494e8349 100644 --- a/keystoneclient/v3/contrib/oauth1/request_tokens.py +++ b/keystoneclient/v3/contrib/oauth1/request_tokens.py @@ -11,10 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals +import urllib.parse as urlparse from keystoneauth1 import plugin -from six.moves.urllib import parse as urlparse from keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils @@ -70,4 +69,5 @@ def create(self, consumer_key, consumer_secret, project): headers=headers) resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) - return self.resource_class(self, token) + return self._prepare_return_value(resp, + self.resource_class(self, token)) diff --git a/keystoneclient/v3/contrib/oauth1/utils.py b/keystoneclient/v3/contrib/oauth1/utils.py index 3c5c9d48f..40a0632af 100644 --- a/keystoneclient/v3/contrib/oauth1/utils.py +++ b/keystoneclient/v3/contrib/oauth1/utils.py @@ -11,8 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import six -from six.moves.urllib import parse as urlparse +import urllib.parse as urlparse OAUTH_PATH = '/OS-OAUTH1' @@ -25,8 +24,7 @@ def get_oauth_token_from_body(body): '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') + body = body.decode('utf-8') credentials = urlparse.parse_qs(body) key = credentials['oauth_token'][0] diff --git a/keystoneclient/v3/contrib/simple_cert.py b/keystoneclient/v3/contrib/simple_cert.py index 8168e67a3..6b58b27b3 100644 --- a/keystoneclient/v3/contrib/simple_cert.py +++ b/keystoneclient/v3/contrib/simple_cert.py @@ -11,12 +11,15 @@ # 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. @@ -27,7 +30,7 @@ def get_ca_certificates(self): """ resp, body = self._client.get('/OS-SIMPLE-CERT/ca', authenticated=False) - return resp.text + return self.mgr._prepare_return_value(resp, resp.text) def get_certificates(self): """Get signing certificates. @@ -38,4 +41,4 @@ def get_certificates(self): """ resp, body = self._client.get('/OS-SIMPLE-CERT/certificates', authenticated=False) - return resp.text + return self.mgr._prepare_return_value(resp, resp.text) diff --git a/keystoneclient/v3/contrib/trusts.py b/keystoneclient/v3/contrib/trusts.py index e23618890..a8ef57909 100644 --- a/keystoneclient/v3/contrib/trusts.py +++ b/keystoneclient/v3/contrib/trusts.py @@ -39,13 +39,14 @@ class TrustManager(base.CrudManager): base_url = '/OS-TRUST' def create(self, trustee_user, trustor_user, role_names=None, - project=None, impersonation=False, expires_at=None, - remaining_uses=None, **kwargs): + 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 @@ -55,9 +56,13 @@ def create(self, trustee_user, trustor_user, role_names=None, """ # Convert role_names list into list-of-dict API format + roles = [] if role_names: - roles = [{'name': n} for n in role_names] - else: + 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 diff --git a/keystoneclient/v3/credentials.py b/keystoneclient/v3/credentials.py index 80eb38b39..70e067001 100644 --- a/keystoneclient/v3/credentials.py +++ b/keystoneclient/v3/credentials.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -41,7 +39,6 @@ class CredentialManager(base.CrudManager): collection_key = 'credentials' key = 'credential' - @positional(1, enforcement=positional.WARN) def create(self, user, type, blob, project=None, **kwargs): """Create a credential. @@ -95,7 +92,6 @@ def list(self, **kwargs): """ return super(CredentialManager, self).list(**kwargs) - @positional(2, enforcement=positional.WARN) def update(self, credential, user, type=None, blob=None, project=None, **kwargs): """Update a credential. diff --git a/keystoneclient/v3/domain_configs.py b/keystoneclient/v3/domain_configs.py new file mode 100644 index 000000000..4c011bce8 --- /dev/null +++ b/keystoneclient/v3/domain_configs.py @@ -0,0 +1,130 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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')) diff --git a/keystoneclient/v3/domains.py b/keystoneclient/v3/domains.py index a790558f9..0f542b8b0 100644 --- a/keystoneclient/v3/domains.py +++ b/keystoneclient/v3/domains.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -40,7 +38,6 @@ class DomainManager(base.CrudManager): collection_key = 'domains' key = 'domain' - @positional(1, enforcement=positional.WARN) def create(self, name, description=None, enabled=True, **kwargs): """Create a domain. @@ -89,7 +86,6 @@ def list(self, **kwargs): kwargs['enabled'] = 0 return super(DomainManager, self).list(**kwargs) - @positional(enforcement=positional.WARN) def update(self, domain, name=None, description=None, enabled=None, **kwargs): """Update a domain. @@ -114,7 +110,7 @@ def update(self, domain, name=None, **kwargs) def delete(self, domain): - """"Delete a domain. + """Delete a domain. :param domain: the domain to be deleted on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` diff --git a/keystoneclient/v3/endpoint_groups.py b/keystoneclient/v3/endpoint_groups.py new file mode 100644 index 000000000..f8b47c4d6 --- /dev/null +++ b/keystoneclient/v3/endpoint_groups.py @@ -0,0 +1,136 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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)) diff --git a/keystoneclient/v3/endpoints.py b/keystoneclient/v3/endpoints.py index b960c3e86..0452394a5 100644 --- a/keystoneclient/v3/endpoints.py +++ b/keystoneclient/v3/endpoints.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ @@ -51,10 +49,9 @@ class EndpointManager(base.CrudManager): def _validate_interface(self, interface): if interface is not None and interface not in VALID_INTERFACES: msg = _('"interface" must be one of: %s') - msg = msg % ', '.join(VALID_INTERFACES) + msg %= ', '.join(VALID_INTERFACES) raise exceptions.ValidationError(msg) - @positional(1, enforcement=positional.WARN) def create(self, service, url, interface=None, region=None, enabled=True, **kwargs): """Create an endpoint. @@ -97,7 +94,6 @@ def get(self, endpoint): return super(EndpointManager, self).get( endpoint_id=base.getid(endpoint)) - @positional(enforcement=positional.WARN) def list(self, service=None, interface=None, region=None, enabled=None, region_id=None, **kwargs): """List endpoints. @@ -128,7 +124,6 @@ def list(self, service=None, interface=None, region=None, enabled=None, enabled=enabled, **kwargs) - @positional(enforcement=positional.WARN) def update(self, endpoint, service=None, url=None, interface=None, region=None, enabled=None, **kwargs): """Update an endpoint. diff --git a/keystoneclient/v3/groups.py b/keystoneclient/v3/groups.py index 2eec3244c..843ad0028 100644 --- a/keystoneclient/v3/groups.py +++ b/keystoneclient/v3/groups.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -29,7 +27,6 @@ class Group(base.Resource): """ - @positional(enforcement=positional.WARN) def update(self, name=None, description=None): kwargs = { 'name': name if name is not None else self.name, @@ -54,7 +51,6 @@ class GroupManager(base.CrudManager): collection_key = 'groups' key = 'group' - @positional(1, enforcement=positional.WARN) def create(self, name, domain=None, description=None, **kwargs): """Create a group. @@ -75,7 +71,6 @@ def create(self, name, domain=None, description=None, **kwargs): description=description, **kwargs) - @positional(enforcement=positional.WARN) def list(self, user=None, domain=None, **kwargs): """List groups. @@ -111,7 +106,6 @@ def get(self, group): return super(GroupManager, self).get( group_id=base.getid(group)) - @positional(enforcement=positional.WARN) def update(self, group, name=None, description=None, **kwargs): """Update a group. diff --git a/keystoneclient/v3/limits.py b/keystoneclient/v3/limits.py new file mode 100644 index 000000000..52b1b886c --- /dev/null +++ b/keystoneclient/v3/limits.py @@ -0,0 +1,150 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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)) diff --git a/keystoneclient/v3/policies.py b/keystoneclient/v3/policies.py index a9be680d5..32de94e91 100644 --- a/keystoneclient/v3/policies.py +++ b/keystoneclient/v3/policies.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -29,7 +27,6 @@ class Policy(base.Resource): """ - @positional(enforcement=positional.WARN) def update(self, blob=None, type=None): kwargs = { 'blob': blob if blob is not None else self.blob, @@ -52,7 +49,6 @@ class PolicyManager(base.CrudManager): collection_key = 'policies' key = 'policy' - @positional(1, enforcement=positional.WARN) def create(self, blob, type='application/json', **kwargs): """Create a policy. @@ -95,7 +91,6 @@ def list(self, **kwargs): """ return super(PolicyManager, self).list(**kwargs) - @positional(enforcement=positional.WARN) def update(self, policy, blob=None, type=None, **kwargs): """Update a policy. @@ -117,7 +112,7 @@ def update(self, policy, blob=None, type=None, **kwargs): **kwargs) def delete(self, policy): - """"Delete a policy. + """Delete a policy. :param policy: the policy to be deleted on the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` diff --git a/keystoneclient/v3/projects.py b/keystoneclient/v3/projects.py index 81dcf8d91..4ba94bf03 100644 --- a/keystoneclient/v3/projects.py +++ b/keystoneclient/v3/projects.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional +import urllib.parse from keystoneclient import base from keystoneclient import exceptions @@ -37,7 +37,6 @@ class Project(base.Resource): """ - @positional(enforcement=positional.WARN) def update(self, name=None, description=None, enabled=None): kwargs = { 'name': name if name is not None else self.name, @@ -55,6 +54,24 @@ def update(self, name=None, description=None, enabled=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.""" @@ -63,7 +80,6 @@ class ProjectManager(base.CrudManager): collection_key = 'projects' key = 'project' - @positional(3, enforcement=positional.WARN) def create(self, name, domain, description=None, enabled=True, parent=None, **kwargs): """Create a project. @@ -96,8 +112,7 @@ def create(self, name, domain, description=None, enabled=enabled, **kwargs) - @positional(enforcement=positional.WARN) - def list(self, domain=None, user=None, **kwargs): + def list(self, domain=None, user=None, parent=None, **kwargs): """List projects. :param domain: the domain of the projects to be filtered on. @@ -105,19 +120,42 @@ def list(self, domain=None, user=None, **kwargs): :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 - return super(ProjectManager, self).list( + 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 = 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: @@ -132,7 +170,6 @@ def _check_not_subtree_as_ids_and_subtree_as_list(self, subtree_as_ids, 'parameters, not both') raise exceptions.ValidationError(msg) - @positional() def get(self, project, subtree_as_list=False, parents_as_list=False, subtree_as_ids=False, parents_as_ids=False): """Retrieve a project. @@ -180,9 +217,15 @@ def get(self, project, subtree_as_list=False, parents_as_list=False, 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) - return self._get(url + query, self.key) + p = self._get(url + query, self.key) + p.tags = getattr(p, 'tags', []) + return p + + def find(self, **kwargs): + p = super(ProjectManager, self).find(**kwargs) + p.tags = getattr(p, 'tags', []) + return p - @positional(enforcement=positional.WARN) def update(self, project, name=None, domain=None, description=None, enabled=None, **kwargs): """Update a project. @@ -220,3 +263,73 @@ def delete(self, project): """ return super(ProjectManager, self).delete( project_id=base.getid(project)) + + 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) + 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) diff --git a/keystoneclient/v3/regions.py b/keystoneclient/v3/regions.py index 7783b3fc9..0538a6656 100644 --- a/keystoneclient/v3/regions.py +++ b/keystoneclient/v3/regions.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from debtcollector import removals from keystoneclient import base @@ -34,6 +35,11 @@ class RegionManager(base.CrudManager): 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. @@ -81,6 +87,11 @@ def list(self, **kwargs): 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. diff --git a/keystoneclient/v3/registered_limits.py b/keystoneclient/v3/registered_limits.py new file mode 100644 index 000000000..088aadc9e --- /dev/null +++ b/keystoneclient/v3/registered_limits.py @@ -0,0 +1,160 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 + ) diff --git a/keystoneclient/v3/role_assignments.py b/keystoneclient/v3/role_assignments.py index 5360a9488..ce1e550a0 100644 --- a/keystoneclient/v3/role_assignments.py +++ b/keystoneclient/v3/role_assignments.py @@ -46,9 +46,25 @@ def _check_not_domain_and_project(self, domain, project): msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) - def list(self, user=None, group=None, project=None, domain=None, role=None, - effective=False, os_inherit_extension_inherited_to=None, - include_subtree=False, include_names=False): + 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 @@ -64,6 +80,8 @@ def list(self, user=None, group=None, project=None, domain=None, role=None, (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) @@ -76,6 +94,9 @@ def list(self, user=None, group=None, project=None, domain=None, role=None, """ 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: @@ -86,6 +107,8 @@ def list(self, user=None, group=None, project=None, domain=None, role=None, 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: diff --git a/keystoneclient/v3/roles.py b/keystoneclient/v3/roles.py index c08385645..3364bd88b 100644 --- a/keystoneclient/v3/roles.py +++ b/keystoneclient/v3/roles.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional +from debtcollector import removals from keystoneclient import base from keystoneclient import exceptions @@ -35,7 +35,7 @@ class Role(base.Resource): class InferenceRule(base.Resource): - """Represents an Rule that states one ROle implies another. + """Represents a rule that states one role implies another. Attributes: * prior_role: this role implies the other @@ -52,8 +52,9 @@ class RoleManager(base.CrudManager): resource_class = Role collection_key = 'roles' key = 'role' + deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager' - def _role_grants_base_url(self, user, group, domain, project, + 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 @@ -65,6 +66,18 @@ def _role_grants_base_url(self, user, group, domain, project, 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 @@ -78,13 +91,26 @@ def _role_grants_base_url(self, user, group, domain, project, return base_url % params - def _require_domain_xor_project(self, domain, project): - 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 a domain or project') - raise exceptions.ValidationError(msg) + 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: @@ -94,7 +120,6 @@ def _require_user_xor_group(self, user, group): msg = _('Must specify either a user or group') raise exceptions.ValidationError(msg) - @positional(1, enforcement=positional.WARN) def create(self, name, domain=None, **kwargs): """Create a role. @@ -118,92 +143,6 @@ def create(self, name, domain=None, **kwargs): domain_id=domain_id, **kwargs) - 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_implied(self, prior_role, implied_role, **kwargs): - """Create an inference rule. - - :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` - :param kwargs: any other attribute provided will be passed to the - server. - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - resp, body = self.client.put("/roles" + url_tail, **kwargs) - return self.resource_class(self, body['role_inference']) - - def delete_implied(self, prior_role, implied_role, **kwargs): - """Delete an inference rule. - - :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` - :param kwargs: any other attribute provided will be passed to the - server. - - :returns: Response object with 204 status. - :rtype: :class:`requests.models.Response` - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - return super(RoleManager, self).delete(tail=url_tail, **kwargs) - - def get_implied(self, prior_role, implied_role, **kwargs): - """Retrieve an inference rule. - - :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` - :param kwargs: any other attribute provided will be passed to the - server. - - :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) - return super(RoleManager, self).get(tail=url_tail, **kwargs) - - def check_implied(self, prior_role, implied_role, **kwargs): - """Check if an inference rule 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` - :param kwargs: any other attribute provided will be passed to the - server. - - :returns: response object with 200 status returned from server. - :rtype: :class:`requests.models.Response` - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - return super(RoleManager, self).head(tail=url_tail, **kwargs) - - def list_role_inferences(self, **kwargs): - """List role inferences. - - :param kwargs: attributes provided will be passed to the server. - - :returns: a list of roles inferences. - :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule` - - """ - resp, body = self.client.get('/role_inferences/', **kwargs) - obj_class = InferenceRule - return [obj_class(self, res, loaded=True) - for res in body['role_inferences']] - def get(self, role): """Retrieve a role. @@ -216,8 +155,7 @@ def get(self, role): """ return super(RoleManager, self).get(role_id=base.getid(role)) - @positional(enforcement=positional.WARN) - def list(self, user=None, group=None, domain=None, + def list(self, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """List roles and role grants. @@ -230,12 +168,12 @@ def list(self, user=None, group=None, domain=None, 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 and domain - are mutually exclusive. + 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 and domain - are mutually exclusive. + 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 @@ -253,16 +191,17 @@ def list(self, user=None, group=None, domain=None, kwargs['tail'] = '/inherited_to_projects' if user or group: self._require_user_xor_group(user, group) - self._require_domain_xor_project(domain, project) + self._enforce_mutually_exclusive_group(system, domain, project) base_url = self._role_grants_base_url( - user, group, domain, project, os_inherit_extension_inherited) + 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) - @positional(enforcement=positional.WARN) def update(self, role, name=None, **kwargs): """Update a role. @@ -296,9 +235,8 @@ def delete(self, role): return super(RoleManager, self).delete( role_id=base.getid(role)) - @positional(enforcement=positional.WARN) - def grant(self, role, user=None, group=None, domain=None, project=None, - os_inherit_extension_inherited=False, **kwargs): + 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. @@ -311,13 +249,16 @@ def grant(self, role, user=None, group=None, domain=None, project=None, 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 and domain - are mutually exclusive. + 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 and domain - are mutually exclusive. + 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 @@ -331,21 +272,21 @@ def grant(self, role, user=None, group=None, domain=None, project=None, :rtype: :class:`keystoneclient.v3.roles.Role` """ - self._require_domain_xor_project(domain, project) + 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, domain, project, os_inherit_extension_inherited) + user, group, system, domain, project, + os_inherit_extension_inherited) return super(RoleManager, self).put(base_url=base_url, role_id=base.getid(role), **kwargs) - @positional(enforcement=positional.WARN) - def check(self, role, user=None, group=None, domain=None, project=None, - os_inherit_extension_inherited=False, **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 @@ -356,13 +297,16 @@ def check(self, role, user=None, group=None, domain=None, project=None, 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 and domain - are mutually exclusive. + 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 and domain - are mutually exclusive. + 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 @@ -380,23 +324,23 @@ def check(self, role, user=None, group=None, domain=None, project=None, :rtype: :class:`requests.models.Response` """ - self._require_domain_xor_project(domain, project) + 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, domain, project, os_inherit_extension_inherited) + 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) - @positional(enforcement=positional.WARN) - def revoke(self, role, user=None, group=None, domain=None, project=None, - os_inherit_extension_inherited=False, **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 @@ -407,13 +351,16 @@ def revoke(self, role, user=None, group=None, domain=None, project=None, 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 and domain - are mutually exclusive. + 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 and domain - are mutually exclusive. + 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 @@ -427,16 +374,198 @@ def revoke(self, role, user=None, group=None, domain=None, project=None, :rtype: list of :class:`keystoneclient.v3.roles.Role` """ - self._require_domain_xor_project(domain, project) + 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, domain, project, os_inherit_extension_inherited) + 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')) diff --git a/keystoneclient/v3/services.py b/keystoneclient/v3/services.py index d38e2d407..631940e80 100644 --- a/keystoneclient/v3/services.py +++ b/keystoneclient/v3/services.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import base @@ -41,7 +39,6 @@ class ServiceManager(base.CrudManager): collection_key = 'services' key = 'service' - @positional(1, enforcement=positional.WARN) def create(self, name, type=None, enabled=True, description=None, **kwargs): """Create a service. @@ -78,7 +75,6 @@ def get(self, service): return super(ServiceManager, self).get( service_id=base.getid(service)) - @positional(enforcement=positional.WARN) def list(self, name=None, type=None, **kwargs): """List services. @@ -96,7 +92,6 @@ def list(self, name=None, type=None, **kwargs): type=type_arg, **kwargs) - @positional(enforcement=positional.WARN) def update(self, service, name=None, type=None, enabled=None, description=None, **kwargs): """Update a service. diff --git a/keystoneclient/v3/system.py b/keystoneclient/v3/system.py new file mode 100644 index 000000000..8d3edafdd --- /dev/null +++ b/keystoneclient/v3/system.py @@ -0,0 +1,26 @@ +# Copyright 2021 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 System(base.Resource): + """Represents the deployment system, with all the services in it. + + Attributes: + * all: boolean + """ + + pass diff --git a/keystoneclient/v3/tokens.py b/keystoneclient/v3/tokens.py index 380ab8f3a..7e0cb07a8 100644 --- a/keystoneclient/v3/tokens.py +++ b/keystoneclient/v3/tokens.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from positional import positional - from keystoneclient import access from keystoneclient import base @@ -40,7 +38,6 @@ def revoke_token(self, token): headers = {'X-Subject-Token': token_id} return self._client.delete('/auth/tokens', headers=headers) - @positional.method(0) def get_revoked(self, audit_id_only=False): """Get revoked tokens list. @@ -60,38 +57,65 @@ def get_revoked(self, audit_id_only=False): resp, body = self._client.get(path) return body - @positional.method(1) - def get_token_data(self, token, include_catalog=True): + 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: - url += '?nocatalog' + 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 - @positional.method(1) - def validate(self, token, include_catalog=True): + 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) + 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) diff --git a/keystoneclient/v3/users.py b/keystoneclient/v3/users.py index 31cad3954..90cd51c45 100644 --- a/keystoneclient/v3/users.py +++ b/keystoneclient/v3/users.py @@ -15,7 +15,6 @@ # under the License. from debtcollector import renames -from positional import positional from keystoneclient import base from keystoneclient import exceptions @@ -47,7 +46,6 @@ def _require_user_and_group(self, user, group): @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') - @positional(1, enforcement=positional.WARN) def create(self, name, domain=None, project=None, password=None, email=None, description=None, enabled=True, default_project=None, **kwargs): @@ -96,7 +94,6 @@ def create(self, name, domain=None, project=None, password=None, @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') - @positional(enforcement=positional.WARN) def list(self, project=None, domain=None, group=None, default_project=None, **kwargs): """List users. @@ -153,7 +150,6 @@ def get(self, user): @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') - @positional(enforcement=positional.WARN) def update(self, user, name=None, domain=None, project=None, password=None, email=None, description=None, enabled=None, default_project=None, **kwargs): diff --git a/playbooks/run-ds-tox.yaml b/playbooks/run-ds-tox.yaml new file mode 100644 index 000000000..b414b747c --- /dev/null +++ b/playbooks/run-ds-tox.yaml @@ -0,0 +1,5 @@ +- hosts: all + roles: + - run-devstack + - ensure-tox + - tox diff --git a/playbooks/tox-post.yaml b/playbooks/tox-post.yaml new file mode 100644 index 000000000..7f0cb1982 --- /dev/null +++ b/playbooks/tox-post.yaml @@ -0,0 +1,4 @@ +- hosts: all + roles: + - fetch-tox-output + - fetch-subunit-output diff --git a/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml b/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml new file mode 100644 index 000000000..6a3f6cadd --- /dev/null +++ b/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml @@ -0,0 +1,5 @@ +--- +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. diff --git a/releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml b/releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml new file mode 100644 index 000000000..623d96de2 --- /dev/null +++ b/releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml @@ -0,0 +1,6 @@ +--- +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. diff --git a/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml b/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml new file mode 100644 index 000000000..114d95bce --- /dev/null +++ b/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml @@ -0,0 +1,7 @@ +--- +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. diff --git a/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml b/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml new file mode 100644 index 000000000..c67357c49 --- /dev/null +++ b/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml @@ -0,0 +1,8 @@ +--- +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. diff --git a/releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml b/releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml new file mode 100644 index 000000000..e6ae2b08d --- /dev/null +++ b/releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml @@ -0,0 +1,7 @@ +--- +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``. diff --git a/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml b/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml new file mode 100644 index 000000000..2699a7f5b --- /dev/null +++ b/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml @@ -0,0 +1,6 @@ +--- +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'])``. diff --git a/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml b/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml new file mode 100644 index 000000000..9c7dc2bf1 --- /dev/null +++ b/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for creating access rules as an attribute of application + credentials as well as for retrieving and deleting them. diff --git a/releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml b/releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml new file mode 100644 index 000000000..6af51e4f1 --- /dev/null +++ b/releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml @@ -0,0 +1,5 @@ +--- +deprecations: + - | + The region resource in Keystone never support or contain "enabled" property. + Thus the property is deprecated and will be removed in future versions. diff --git a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml new file mode 100644 index 000000000..e9c1c9c3d --- /dev/null +++ b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - > + [`bug 1616105 `_] + Only log the response body when the ``Content-Type`` header is set to + ``application/json``. This avoids logging large binary objects (such as + images). Other ``Content-Type`` will not be logged. Additional + ``Content-Type`` strings can be added as required. diff --git a/releasenotes/notes/bug-1641674-4862454115265e76.yaml b/releasenotes/notes/bug-1641674-4862454115265e76.yaml new file mode 100644 index 000000000..19c8ecc34 --- /dev/null +++ b/releasenotes/notes/bug-1641674-4862454115265e76.yaml @@ -0,0 +1,8 @@ +--- +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. diff --git a/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml b/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml new file mode 100644 index 000000000..5d066e906 --- /dev/null +++ b/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The ``X-Service-Token`` header value is now properly masked, and is + displayed as a hash value, in the log. diff --git a/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml b/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml index 82a723dc6..fd08a910b 100644 --- a/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml +++ b/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml @@ -3,10 +3,10 @@ deprecations: - > [`blueprint deprecate-to-ksa `_] Several modules related to authentication in keystoneclient have been - deprecated in favor of [`keystoneauth `_] + 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 `_]. + [`documented `_]. diff --git a/releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml b/releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml new file mode 100644 index 000000000..b97748489 --- /dev/null +++ b/releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml @@ -0,0 +1,6 @@ +--- +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. \ No newline at end of file diff --git a/releasenotes/notes/drop-python-3-6-and-3-7-ef1e107897dde8f4.yaml b/releasenotes/notes/drop-python-3-6-and-3-7-ef1e107897dde8f4.yaml new file mode 100644 index 000000000..db420d739 --- /dev/null +++ b/releasenotes/notes/drop-python-3-6-and-3-7-ef1e107897dde8f4.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Python 3.6 & 3.7 support has been dropped. The minimum version of Python now + supported is Python 3.8. diff --git a/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml b/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml new file mode 100644 index 000000000..988dca5e3 --- /dev/null +++ b/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Now keystone client supports to list projects which belongs to the given + parent project. diff --git a/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml b/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml new file mode 100644 index 000000000..c0c868cbe --- /dev/null +++ b/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml @@ -0,0 +1,8 @@ +--- +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. diff --git a/releasenotes/notes/remove-client-HTTPClient-b69cc6fb7d07fadc.yaml b/releasenotes/notes/remove-client-HTTPClient-b69cc6fb7d07fadc.yaml new file mode 100644 index 000000000..e02103073 --- /dev/null +++ b/releasenotes/notes/remove-client-HTTPClient-b69cc6fb7d07fadc.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + The deprecated ``keystoneclient.client.HTTPClient`` class has been removed. diff --git a/releasenotes/notes/remove-py38-2e39854190447827.yaml b/releasenotes/notes/remove-py38-2e39854190447827.yaml new file mode 100644 index 000000000..040316360 --- /dev/null +++ b/releasenotes/notes/remove-py38-2e39854190447827.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Python 3.8 has been removed. Now the minimum python version + supported is 3.9 . diff --git a/releasenotes/notes/remove-py39-a294c2d7335b646e.yaml b/releasenotes/notes/remove-py39-a294c2d7335b646e.yaml new file mode 100644 index 000000000..eaf3014b9 --- /dev/null +++ b/releasenotes/notes/remove-py39-a294c2d7335b646e.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Python 3.9 has been removed. Now Python 3.10 is the minimum + version supported. diff --git a/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml b/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml new file mode 100644 index 000000000..61b9d17ac --- /dev/null +++ b/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml @@ -0,0 +1,6 @@ +--- +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. diff --git a/releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml b/releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml new file mode 100644 index 000000000..8ef4701db --- /dev/null +++ b/releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml @@ -0,0 +1,13 @@ +--- +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. \ No newline at end of file diff --git a/releasenotes/notes/switch-default-interface-v3-dcd7167196ace531.yaml b/releasenotes/notes/switch-default-interface-v3-dcd7167196ace531.yaml new file mode 100644 index 000000000..90709c097 --- /dev/null +++ b/releasenotes/notes/switch-default-interface-v3-dcd7167196ace531.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + For sessions using the v3 Identity API, the default interface has been + switched from ``admin`` to ``public``. This allows deployments to get rid + of the admin endpoint, which functionally is no longer necessary with the + v3 API. diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst new file mode 100644 index 000000000..2c9a36fae --- /dev/null +++ b/releasenotes/source/2023.1.rst @@ -0,0 +1,6 @@ +=========================== +2023.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: unmaintained/2023.1 diff --git a/releasenotes/source/2023.2.rst b/releasenotes/source/2023.2.rst new file mode 100644 index 000000000..a4838d7d0 --- /dev/null +++ b/releasenotes/source/2023.2.rst @@ -0,0 +1,6 @@ +=========================== +2023.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2023.2 diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst new file mode 100644 index 000000000..6896656be --- /dev/null +++ b/releasenotes/source/2024.1.rst @@ -0,0 +1,6 @@ +=========================== +2024.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: unmaintained/2024.1 diff --git a/releasenotes/source/2024.2.rst b/releasenotes/source/2024.2.rst new file mode 100644 index 000000000..aaebcbc8c --- /dev/null +++ b/releasenotes/source/2024.2.rst @@ -0,0 +1,6 @@ +=========================== +2024.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2024.2 diff --git a/releasenotes/source/2025.1.rst b/releasenotes/source/2025.1.rst new file mode 100644 index 000000000..3add0e53a --- /dev/null +++ b/releasenotes/source/2025.1.rst @@ -0,0 +1,6 @@ +=========================== +2025.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.1 diff --git a/releasenotes/source/2025.2.rst b/releasenotes/source/2025.2.rst new file mode 100644 index 000000000..4dae18d86 --- /dev/null +++ b/releasenotes/source/2025.2.rst @@ -0,0 +1,6 @@ +=========================== +2025.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.2 diff --git a/releasenotes/source/2026.1.rst b/releasenotes/source/2026.1.rst new file mode 100644 index 000000000..3d2861580 --- /dev/null +++ b/releasenotes/source/2026.1.rst @@ -0,0 +1,6 @@ +=========================== +2026.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2026.1 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index e316bc56a..f2ae6a44e 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -1,4 +1,3 @@ -# -*- 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 @@ -38,7 +37,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'oslosphinx', + 'openstackdocstheme', 'reno.sphinxext', ] @@ -55,20 +54,13 @@ master_doc = 'index' # General information about the project. -project = u'keystoneclient Release Notes' -copyright = u'2015, Keystone Developers' +copyright = '2015, Keystone Developers' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -import pbr.version -keystone_version = pbr.version.VersionInfo('keystoneclient') +# Release notes are version independent. # The full version, including alpha/beta/rc tags. -release = keystone_version.version_string_with_vcs() +release = '' # The short X.Y version. -version = keystone_version.canonical_version_string() +version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -100,7 +92,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -113,7 +105,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +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 @@ -149,10 +141,6 @@ # directly to the root of the documentation. # html_extra_path = [] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True @@ -196,24 +184,13 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'keystoneclientReleaseNotes.tex', - u'keystoneclient Release Notes Documentation', - u'Keystone Developers', 'manual'), + 'keystoneclient Release Notes Documentation', + 'Keystone Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -243,8 +220,8 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'keystoneclientreleasenotes', - u'keystoneclient Release Notes Documentation', - [u'Keystone Developers'], 1) + 'keystoneclient Release Notes Documentation', + ['Keystone Developers'], 1) ] # If true, show URL addresses after external links. @@ -258,8 +235,8 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'keystoneclientReleaseNotes', - u'keystoneclient Release Notes Documentation', - u'Keystone Developers', 'keystoneclientReleaseNotes', + 'keystoneclient Release Notes Documentation', + 'Keystone Developers', 'keystoneclientReleaseNotes', 'Python bindings for the OpenStack Identity service.', 'Miscellaneous'), ] @@ -278,3 +255,8 @@ # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] + +# -- Options for openstackdocstheme ------------------------------------------- +openstackdocs_repo_name = 'openstack/python-keystoneclient' +openstackdocs_bug_project = 'python-keystoneclient' +openstackdocs_bug_tag = '' diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index e28892023..d84852efd 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,5 +6,24 @@ :maxdepth: 1 unreleased + 2026.1 + 2025.2 + 2025.1 + 2024.2 + 2024.1 + 2023.2 + 2023.1 + zed + yoga + xena + wallaby + victoria + ussuri + train + stein + rocky + queens + pike + ocata newton mitaka diff --git a/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po new file mode 100644 index 000000000..aede2504e --- /dev/null +++ b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po @@ -0,0 +1,57 @@ +# 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" diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst new file mode 100644 index 000000000..9515f6cf0 --- /dev/null +++ b/releasenotes/source/ocata.rst @@ -0,0 +1,6 @@ +============================ + Ocata Series Release Notes +============================ + +.. release-notes:: + :branch: origin/stable/ocata diff --git a/releasenotes/source/pike.rst b/releasenotes/source/pike.rst new file mode 100644 index 000000000..e43bfc0ce --- /dev/null +++ b/releasenotes/source/pike.rst @@ -0,0 +1,6 @@ +=================================== + Pike Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/pike diff --git a/releasenotes/source/queens.rst b/releasenotes/source/queens.rst new file mode 100644 index 000000000..36ac6160c --- /dev/null +++ b/releasenotes/source/queens.rst @@ -0,0 +1,6 @@ +=================================== + Queens Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/queens diff --git a/releasenotes/source/rocky.rst b/releasenotes/source/rocky.rst new file mode 100644 index 000000000..40dd517b7 --- /dev/null +++ b/releasenotes/source/rocky.rst @@ -0,0 +1,6 @@ +=================================== + Rocky Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/rocky diff --git a/releasenotes/source/stein.rst b/releasenotes/source/stein.rst new file mode 100644 index 000000000..efaceb667 --- /dev/null +++ b/releasenotes/source/stein.rst @@ -0,0 +1,6 @@ +=================================== + Stein Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/stein diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst new file mode 100644 index 000000000..7fa1088ac --- /dev/null +++ b/releasenotes/source/train.rst @@ -0,0 +1,6 @@ +=================================== + Train Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/train diff --git a/releasenotes/source/ussuri.rst b/releasenotes/source/ussuri.rst new file mode 100644 index 000000000..e21e50e0c --- /dev/null +++ b/releasenotes/source/ussuri.rst @@ -0,0 +1,6 @@ +=========================== +Ussuri Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/ussuri diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst new file mode 100644 index 000000000..8ce933419 --- /dev/null +++ b/releasenotes/source/victoria.rst @@ -0,0 +1,6 @@ +============================= +Victoria Series Release Notes +============================= + +.. release-notes:: + :branch: unmaintained/victoria diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst new file mode 100644 index 000000000..bcf35c5f8 --- /dev/null +++ b/releasenotes/source/wallaby.rst @@ -0,0 +1,6 @@ +============================ +Wallaby Series Release Notes +============================ + +.. release-notes:: + :branch: unmaintained/wallaby diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst new file mode 100644 index 000000000..d19eda488 --- /dev/null +++ b/releasenotes/source/xena.rst @@ -0,0 +1,6 @@ +========================= +Xena Series Release Notes +========================= + +.. release-notes:: + :branch: unmaintained/xena diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst new file mode 100644 index 000000000..43cafdea8 --- /dev/null +++ b/releasenotes/source/yoga.rst @@ -0,0 +1,6 @@ +========================= +Yoga Series Release Notes +========================= + +.. release-notes:: + :branch: unmaintained/yoga diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst new file mode 100644 index 000000000..6cc2b1554 --- /dev/null +++ b/releasenotes/source/zed.rst @@ -0,0 +1,6 @@ +======================== +Zed Series Release Notes +======================== + +.. release-notes:: + :branch: unmaintained/zed diff --git a/requirements.txt b/requirements.txt index 2362edf4c..1b69b9833 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,14 @@ -# 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>=1.6 # Apache-2.0 +# Requirements lower bounds listed here are our best effort to keep them up to +# date but we do not test them so no guarantee of having them all correct. If +# you find any incorrect lower bounds, let us know or propose a fix. +pbr>=2.0.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 -keystoneauth1>=2.14.0 # Apache-2.0 -oslo.config>=3.14.0 # Apache-2.0 -oslo.i18n>=2.1.0 # Apache-2.0 -oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.17.0 # Apache-2.0 -positional>=1.1.1 # Apache-2.0 -requests>=2.10.0 # Apache-2.0 -six>=1.9.0 # MIT -stevedore>=1.17.1 # 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.18.0 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 +requests>=2.14.2 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 +packaging>=20.4 # BSD diff --git a/setup.cfg b/setup.cfg index f9075500e..037fe1c57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,12 @@ [metadata] name = python-keystoneclient summary = Client Library for OpenStack Identity -description-file = +description_file = README.rst author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = http://docs.openstack.org/developer/python-keystoneclient +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/python-keystoneclient/latest/ +python_requires = >=3.10 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -13,11 +14,10 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 [files] packages = @@ -36,35 +36,3 @@ keystoneclient.auth.plugin = v3unscopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2UnscopedToken v3scopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2ScopedToken v3unscopedadfs = keystoneclient.contrib.auth.v3.saml2:ADFSUnscopedToken - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 - -[pbr] -warnerrors = True -autodoc_tree_index_modules = True -autodoc_tree_excludes = - setup.py - keystoneclient/tests/ - -[upload_sphinx] -upload-dir = doc/build/html - -[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 - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py index 782bb21f0..cd35c3c35 100644 --- a/setup.py +++ b/setup.py @@ -13,17 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index 75a2eeedf..238690c65 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,26 +1,18 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -hacking<0.11,>=0.10.0 -flake8-docstrings==0.2.1.post1 # MIT +hacking>=6.1.0,<6.2.0 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD keyring>=5.5.1 # MIT/PSF -lxml>=2.3 # BSD -mock>=2.0 # BSD -oauthlib>=0.6 # BSD -oslosphinx>=4.7.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -reno>=1.8.0 # Apache2 -requests-mock>=1.1 # Apache-2.0 -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD -tempest>=12.1.0 # Apache-2.0 -testrepository>=0.0.18 # Apache-2.0/BSD -testresources>=0.2.4 # Apache-2.0/BSD +lxml>=4.5.0 # BSD +oauthlib>=0.6.2 # BSD +openstacksdk>=0.10.0 # Apache-2.0 +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>=1.4.0 # MIT +testtools>=2.2.0 # MIT # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 83ba18543..e69fc3559 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,22 @@ [tox] -minversion = 1.6 +minversion = 3.18.0 skipsdist = True -envlist = py34,py27,pep8,releasenotes +envlist = py3,pep8,releasenotes +ignore_basepython_conflict = True [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - OS_STDOUT_NOCAPTURE=False +setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt +deps = + -c{env:TOX_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 - python setup.py testr --slowest --testr-args='{posargs}' -whitelist_externals = find + stestr run --slowest {posargs} +allowlist_externals = find +basepython = python3 [testenv:pep8] commands = @@ -30,13 +32,21 @@ commands = bandit -r keystoneclient -x tests -n5 commands = {posargs} [testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' +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 = OS_TEST_PATH=./keystoneclient/tests/functional +setenv = {[testenv]setenv} + OS_TEST_PATH=./keystoneclient/tests/functional passenv = OS_* [flake8] @@ -45,22 +55,40 @@ passenv = OS_* # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package +# D107: Missing docstring in __init__ # D203: 1 blank line required before class docstring (deprecated in pep257) -ignore = D100,D101,D102,D103,D104,D203 +# D401 First line should be in imperative mood; try rephrasing +# W504 line break after binary operator +ignore = D100,D101,D102,D103,D104,D107,D203,D401,W504 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [testenv:docs] -commands= - python setup.py build_sphinx +commands = sphinx-build -W -b html doc/source doc/build/html +deps = + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt + -r{toxinidir}/requirements.txt + +[testenv:pdf-docs] +deps = {[testenv:docs]deps} +allowlist_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 = + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt [hacking] import_exceptions = keystoneclient.i18n -local-check-factory = keystoneclient.tests.hacking.checks.factory [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if @@ -69,3 +97,4 @@ local-check-factory = keystoneclient.tests.hacking.checks.factory # separately, outside of the requirements files. deps = bindep commands = bindep test +