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
+