Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Id80e2fd6d0f5f1873bd0b11403d19e86de400530
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = keystoneclient
|
||||
omit = keystoneclient/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,26 +0,0 @@
|
||||
.coverage
|
||||
.testrepository
|
||||
subunit.log
|
||||
.venv
|
||||
*,cover
|
||||
cover
|
||||
*.pyc
|
||||
.idea
|
||||
*.sw?
|
||||
*.egg
|
||||
*~
|
||||
.tox
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
build
|
||||
dist
|
||||
python_keystoneclient.egg-info
|
||||
keystoneclient/versioninfo
|
||||
doc/source/api
|
||||
# Development environment files
|
||||
.project
|
||||
.pydevproject
|
||||
# Temporary files created during test, but not removed
|
||||
examples/pki/certs/tmp*
|
||||
# Files created by releasenotes build
|
||||
releasenotes/build
|
@@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-keystoneclient.git
|
6
.mailmap
6
.mailmap
@@ -1,6 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
||||
<mr.alex.meade@gmail.com> <hatboy112@yahoo.com>
|
||||
<mr.alex.meade@gmail.com> <alex.meade@rackspace.com>
|
||||
<morgan.fainberg@gmail.com> <m@metacloud.com>
|
@@ -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
|
@@ -1,18 +0,0 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps documented at:
|
||||
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works
|
||||
and your OpenStack accounts are set up, you can skip to the
|
||||
development workflow section of this documentation to learn how
|
||||
changes to OpenStack should be submitted for review via the
|
||||
Gerrit tool:
|
||||
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/python-keystoneclient
|
24
HACKING.rst
24
HACKING.rst
@@ -1,24 +0,0 @@
|
||||
Keystone Style Commandments
|
||||
===========================
|
||||
|
||||
- Step 1: Read the OpenStack Style Commandments
|
||||
https://docs.openstack.org/hacking/latest/
|
||||
- Step 2: Read on
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
When dealing with exceptions from underlying libraries, translate those
|
||||
exceptions to an instance or subclass of ClientException.
|
||||
|
||||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
python-keystoneclient uses testtools and testr for its unittest suite
|
||||
and its test runner. Basic workflow around our use of tox and testr can
|
||||
be found at https://wiki.openstack.org/testr. If you'd like to learn more
|
||||
in depth:
|
||||
|
||||
https://testtools.readthedocs.org/
|
||||
https://testrepository.readthedocs.org/
|
209
LICENSE
209
LICENSE
@@ -1,209 +0,0 @@
|
||||
Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1)
|
||||
Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1)
|
||||
Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7)
|
||||
All rights reserved.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
--- License for python-keystoneclient versions prior to 2.1 ---
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of this project nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
14
README
Normal file
14
README
Normal file
@@ -0,0 +1,14 @@
|
||||
This project is no longer maintained.
|
||||
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
For ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
64
README.rst
64
README.rst
@@ -1,64 +0,0 @@
|
||||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
|
||||
.. image:: https://governance.openstack.org/badges/python-keystoneclient.svg
|
||||
:target: https://governance.openstack.org/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/
|
||||
: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
|
||||
`OpenStackClient`_.
|
||||
|
||||
* `PyPi`_ - package installation
|
||||
* `Online Documentation`_
|
||||
* `Launchpad project`_ - release management
|
||||
* `Blueprints`_ - feature specifications
|
||||
* `Bugs`_ - issue tracking
|
||||
* `Source`_
|
||||
* `Specs`_
|
||||
* `How to Contribute`_
|
||||
|
||||
.. _PyPi: https://pypi.python.org/pypi/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: https://docs.openstack.org/infra/manual/developers.html
|
||||
.. _Specs: https://specs.openstack.org/openstack/keystone-specs/
|
||||
|
||||
.. contents:: Contents:
|
||||
:local:
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
By way of a quick-start::
|
||||
|
||||
>>> from keystoneauth1.identity import v3
|
||||
>>> from keystoneauth1 import session
|
||||
>>> from keystoneclient.v3 import client
|
||||
>>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin",
|
||||
... password="password", project_name="admin",
|
||||
... user_domain_id="default", project_domain_id="default")
|
||||
>>> sess = session.Session(auth=auth)
|
||||
>>> keystone = client.Client(session=sess)
|
||||
>>> keystone.projects.list()
|
||||
[...]
|
||||
>>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True)
|
||||
>>> project.delete()
|
23
bindep.txt
23
bindep.txt
@@ -1,23 +0,0 @@
|
||||
# This is a cross-platform list tracking distribution packages needed by tests;
|
||||
# see https://docs.openstack.org/infra/bindep/ for additional information.
|
||||
|
||||
gettext
|
||||
libssl-dev
|
||||
|
||||
dbus-devel [platform:rpm]
|
||||
dbus-glib-devel [platform:rpm]
|
||||
libdbus-1-dev [platform:dpkg]
|
||||
libdbus-glib-1-dev [platform:dpkg]
|
||||
libffi-dev [platform:dpkg]
|
||||
libffi-devel [platform:rpm]
|
||||
libsasl2-dev [platform:dpkg]
|
||||
libxml2-dev [platform:dpkg]
|
||||
libxslt1-dev [platform:dpkg]
|
||||
python-all-dev [platform:dpkg]
|
||||
python3-all-dev [platform:dpkg]
|
||||
|
||||
cyrus-sasl-devel [platform:rpm]
|
||||
libxml2-devel [platform:rpm]
|
||||
python-devel [platform:rpm]
|
||||
python3-devel [platform:fedora]
|
||||
python34-devel [platform:centos]
|
1
doc/.gitignore
vendored
1
doc/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
build/
|
90
doc/Makefile
90
doc/Makefile
@@ -1,90 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXSOURCE = source
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE)
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-keystoneclient.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-keystoneclient.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
@@ -1,232 +0,0 @@
|
||||
# python-keystoneclient documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Dec 6 14:19:25 2009.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing
|
||||
# dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..', '..')))
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.intersphinx',
|
||||
'openstackdocstheme',
|
||||
]
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
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
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['keystoneclient.']
|
||||
|
||||
# Grouping the document tree for man pages.
|
||||
# List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual'
|
||||
|
||||
#man_pages = []
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
#html_theme_path = ["."]
|
||||
#html_theme = '_theme'
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = ['static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'python-keystoneclientdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual])
|
||||
# .
|
||||
latex_documents = [
|
||||
('index', 'python-keystoneclient.tex',
|
||||
'python-keystoneclient Documentation',
|
||||
'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss',
|
||||
'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
keystoneauth_url = 'https://docs.openstack.org/keystoneauth/latest/'
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None),
|
||||
'keystoneauth1': (keystoneauth_url, None),
|
||||
}
|
||||
|
||||
# -- Options for openstackdocstheme -------------------------------------------
|
||||
repository_name = 'openstack/python-keystoneclient'
|
||||
bug_project = 'python-keystoneclient'
|
||||
bug_tag = ''
|
@@ -1 +0,0 @@
|
||||
.. include:: ../../ChangeLog
|
@@ -1,54 +0,0 @@
|
||||
Python bindings to the OpenStack Identity API (Keystone)
|
||||
========================================================
|
||||
|
||||
This is a client for OpenStack Identity API. There's a Python API for
|
||||
:doc:`Identity API v3 <using-api-v3>` and :doc:`v2 <using-api-v2>` (the
|
||||
:mod:`keystoneclient` modules).
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
using-api-v3
|
||||
using-sessions
|
||||
using-api-v2
|
||||
api/modules
|
||||
|
||||
Related Identity Projects
|
||||
=========================
|
||||
|
||||
In addition to creating the Python client library, the Keystone team also
|
||||
provides `Identity Service`_, as well as `WSGI Middleware`_.
|
||||
|
||||
.. _`Identity Service`: https://docs.openstack.org/keystone/latest/
|
||||
.. _`WSGI Middleware`: https://docs.openstack.org/keystonemiddleware/latest/
|
||||
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
history
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Code is hosted `on GitHub`_. Submit bugs to the Keystone project on
|
||||
`Launchpad`_. Submit code to the ``openstack/python-keystoneclient`` project
|
||||
using `Gerrit`_.
|
||||
|
||||
.. _on GitHub: https://github.com/openstack/python-keystoneclient
|
||||
.. _Launchpad: https://launchpad.net/python-keystoneclient
|
||||
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Run tests with ``tox``.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
@@ -1,125 +0,0 @@
|
||||
=======================
|
||||
Using the V2 client API
|
||||
=======================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The main concepts in the Identity v2 API are:
|
||||
|
||||
* tenants
|
||||
* users
|
||||
* roles
|
||||
* services
|
||||
* endpoints
|
||||
|
||||
The V2 client API lets you query and make changes through
|
||||
managers. For example, to manipulate tenants, you interact with a
|
||||
``keystoneclient.v2_0.tenants.TenantManager`` object.
|
||||
|
||||
You obtain access to managers via attributes of the
|
||||
``keystoneclient.v2_0.client.Client`` object. For example, the ``tenants``
|
||||
attribute of the ``Client`` class is a tenant manager::
|
||||
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> keystone.tenants.list() # List tenants
|
||||
|
||||
You create a valid ``keystoneclient.v2_0.client.Client`` object by passing
|
||||
a :class:`~keystoneauth1.session.Session` to the constructor. Authentication
|
||||
and examples of common tasks are provided below.
|
||||
|
||||
You can generally expect that when the client needs to propagate an exception
|
||||
it will raise an instance of subclass of
|
||||
``keystoneclient.exceptions.ClientException``
|
||||
|
||||
Authenticating
|
||||
==============
|
||||
|
||||
There are two ways to authenticate against keystone:
|
||||
* against the admin endpoint with the admin token
|
||||
* against the public endpoint with a username and password
|
||||
|
||||
If you are an administrator, you can authenticate by connecting to the admin
|
||||
endpoint and using the admin token (sometimes referred to as the service
|
||||
token). The token is specified as the ``admin_token`` configuration option in
|
||||
your keystone.conf config file, which is typically in /etc/keystone::
|
||||
|
||||
>>> from keystoneauth1.identity import v2
|
||||
>>> from keystoneauth1 import session
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> token = '012345SECRET99TOKEN012345'
|
||||
>>> endpoint = 'http://192.168.206.130:35357/v2.0'
|
||||
>>> auth = v2.Token(auth_url=endpoint, token=token)
|
||||
>>> sess = session.Session(auth=auth)
|
||||
>>> keystone = client.Client(session=sess)
|
||||
|
||||
If you have a username and password, authentication is done against the
|
||||
public endpoint. You must also specify a tenant that is associated with the
|
||||
user::
|
||||
|
||||
>>> from keystoneauth1.identity import v2
|
||||
>>> from keystoneauth1 import session
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> username='adminUser'
|
||||
>>> password='secretword'
|
||||
>>> tenant_name='openstackDemo'
|
||||
>>> auth_url='http://192.168.206.130:5000/v2.0'
|
||||
>>> auth = v2.Password(username=username, password=password,
|
||||
... tenant_name=tenant_name, auth_url=auth_url)
|
||||
>>> sess = session.Session(auth=auth)
|
||||
>>> keystone = client.Client(session=sess)
|
||||
|
||||
Creating tenants
|
||||
================
|
||||
|
||||
This example will create a tenant named *openstackDemo*::
|
||||
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> keystone.tenants.create(tenant_name="openstackDemo",
|
||||
... description="Default Tenant", enabled=True)
|
||||
<Tenant {u'id': u'9b7962da6eb04745b477ae920ad55939', u'enabled': True, u'description': u'Default Tenant', u'name': u'openstackDemo'}>
|
||||
|
||||
Creating users
|
||||
==============
|
||||
|
||||
This example will create a user named *adminUser* with a password *secretword*
|
||||
in the openstackDemo tenant. We first need to retrieve the tenant::
|
||||
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> tenants = keystone.tenants.list()
|
||||
>>> my_tenant = [x for x in tenants if x.name=='openstackDemo'][0]
|
||||
>>> my_user = keystone.users.create(name="adminUser",
|
||||
... password="secretword",
|
||||
... tenant_id=my_tenant.id)
|
||||
|
||||
Creating roles and adding users
|
||||
===============================
|
||||
|
||||
This example will create an admin role and add the *my_user* user to that
|
||||
role, but only for the *my_tenant* tenant:
|
||||
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> role = keystone.roles.create('admin')
|
||||
>>> my_tenant = ...
|
||||
>>> my_user = ...
|
||||
>>> keystone.roles.add_user_role(my_user, role, my_tenant)
|
||||
|
||||
Creating services and endpoints
|
||||
===============================
|
||||
|
||||
This example will create the service and corresponding endpoint for the
|
||||
Compute service::
|
||||
|
||||
>>> from keystoneclient.v2_0 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> service = keystone.services.create(name="nova", service_type="compute",
|
||||
... description="Nova Compute Service")
|
||||
>>> keystone.endpoints.create(
|
||||
... region="RegionOne", service_id=service.id,
|
||||
... publicurl="http://192.168.206.130:8774/v2/%(tenant_id)s",
|
||||
... adminurl="http://192.168.206.130:8774/v2/%(tenant_id)s",
|
||||
... internalurl="http://192.168.206.130:8774/v2/%(tenant_id)s")
|
@@ -1,140 +0,0 @@
|
||||
=======================
|
||||
Using the V3 Client API
|
||||
=======================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The main concepts in the Identity v3 API are:
|
||||
|
||||
* :py:mod:`~keystoneclient.v3.credentials`
|
||||
* :py:mod:`~keystoneclient.v3.domain_configs`
|
||||
* :py:mod:`~keystoneclient.v3.domains`
|
||||
* :py:mod:`~keystoneclient.v3.endpoints`
|
||||
* :py:mod:`~keystoneclient.v3.groups`
|
||||
* :py:mod:`~keystoneclient.v3.policies`
|
||||
* :py:mod:`~keystoneclient.v3.projects`
|
||||
* :py:mod:`~keystoneclient.v3.regions`
|
||||
* :py:mod:`~keystoneclient.v3.role_assignments`
|
||||
* :py:mod:`~keystoneclient.v3.roles`
|
||||
* :py:mod:`~keystoneclient.v3.services`
|
||||
* :py:mod:`~keystoneclient.v3.tokens`
|
||||
* :py:mod:`~keystoneclient.v3.users`
|
||||
|
||||
The :py:mod:`keystoneclient.v3.client` API lets you query and make changes
|
||||
through ``managers``. For example, to manipulate a project (formerly
|
||||
called tenant), you interact with a
|
||||
:py:class:`keystoneclient.v3.projects.ProjectManager` object.
|
||||
|
||||
You obtain access to managers through attributes of a
|
||||
:py:class:`keystoneclient.v3.client.Client` object. For example, the
|
||||
``projects`` attribute of a ``Client`` object is a projects manager::
|
||||
|
||||
>>> from keystoneclient.v3 import client
|
||||
>>> keystone = client.Client(...)
|
||||
>>> keystone.projects.list() # List projects
|
||||
|
||||
While it is possible to instantiate a
|
||||
:py:class:`keystoneclient.v3.client.Client` object (as done above for
|
||||
clarity), the recommended approach is to use the discovery mechanism
|
||||
provided by the :py:class:`keystoneclient.client.Client` class. The
|
||||
appropriate class will be instantiated depending on the API versions
|
||||
available::
|
||||
|
||||
>>> from keystoneclient import client
|
||||
>>> keystone =
|
||||
... client.Client(auth_url='http://localhost:5000', ...)
|
||||
>>> type(keystone)
|
||||
<class 'keystoneclient.v3.client.Client'>
|
||||
|
||||
One can force the use of a specific version of the API, either by
|
||||
using the ``version`` keyword argument::
|
||||
|
||||
>>> from keystoneclient import client
|
||||
>>> keystone = client.Client(auth_url='http://localhost:5000',
|
||||
version=(2,), ...)
|
||||
>>> type(keystone)
|
||||
<class 'keystoneclient.v2_0.client.Client'>
|
||||
>>> keystone = client.Client(auth_url='http://localhost:5000',
|
||||
version=(3,), ...)
|
||||
>>> type(keystone)
|
||||
<class 'keystoneclient.v3.client.Client'>
|
||||
|
||||
Or by specifying directly the specific API version authentication URL
|
||||
as the auth_url keyword argument::
|
||||
|
||||
>>> from keystoneclient import client
|
||||
>>> keystone =
|
||||
... client.Client(auth_url='http://localhost:5000/v2.0', ...)
|
||||
>>> type(keystone)
|
||||
<class 'keystoneclient.v2_0.client.Client'>
|
||||
>>> keystone =
|
||||
... client.Client(auth_url='http://localhost:5000/v3', ...)
|
||||
>>> type(keystone)
|
||||
<class 'keystoneclient.v3.client.Client'>
|
||||
|
||||
Upon successful authentication, a :py:class:`keystoneclient.v3.client.Client`
|
||||
object is returned (when using the Identity v3 API). Authentication and
|
||||
examples of common tasks are provided below.
|
||||
|
||||
You can generally expect that when the client needs to propagate an
|
||||
exception it will raise an instance of subclass of
|
||||
:class:`keystoneclient.exceptions.ClientException`.
|
||||
|
||||
Authenticating Using Sessions
|
||||
=============================
|
||||
|
||||
Instantiate a :py:class:`keystoneclient.v3.client.Client` using a
|
||||
:py:class:`~keystoneauth1.session.Session` to provide the authentication
|
||||
plugin, SSL/TLS certificates, and other data::
|
||||
|
||||
>>> from keystoneauth1.identity import v3
|
||||
>>> from keystoneauth1 import session
|
||||
>>> from keystoneclient.v3 import client
|
||||
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
|
||||
... user_id='myuserid',
|
||||
... password='mypassword',
|
||||
... project_id='myprojectid')
|
||||
>>> sess = session.Session(auth=auth)
|
||||
>>> keystone = client.Client(session=sess)
|
||||
|
||||
For more information on Sessions refer to: `Using Sessions`_.
|
||||
|
||||
.. _`Using Sessions`: using-sessions.html
|
||||
|
||||
Non-Session Authentication (deprecated)
|
||||
=======================================
|
||||
|
||||
The *deprecated* way to authenticate is to pass the username, the user's domain
|
||||
name (which will default to 'Default' if it is not specified), and a
|
||||
password::
|
||||
|
||||
>>> from keystoneclient import client
|
||||
>>> auth_url = 'http://localhost:5000'
|
||||
>>> username = 'adminUser'
|
||||
>>> user_domain_name = 'Default'
|
||||
>>> password = 'secreetword'
|
||||
>>> keystone = client.Client(auth_url=auth_url, version=(3,),
|
||||
... username=username, password=password,
|
||||
... user_domain_name=user_domain_name)
|
||||
|
||||
A :py:class:`~keystoneauth1.session.Session` should be passed to the Client
|
||||
instead. Using a Session you're not limited to authentication using a username
|
||||
and password but can take advantage of other more secure authentication
|
||||
methods.
|
||||
|
||||
You may optionally specify a domain or project (along with its project
|
||||
domain name), to obtain a scoped token::
|
||||
|
||||
>>> from keystoneclient import client
|
||||
>>> auth_url = 'http://localhost:5000'
|
||||
>>> username = 'adminUser'
|
||||
>>> user_domain_name = 'Default'
|
||||
>>> project_name = 'demo'
|
||||
>>> project_domain_name = 'Default'
|
||||
>>> password = 'secreetword'
|
||||
>>> keystone = client.Client(auth_url=auth_url, version=(3,),
|
||||
... username=username, password=password,
|
||||
... user_domain_name=user_domain_name,
|
||||
... project_name=project_name,
|
||||
... project_domain_name=project_domain_name)
|
@@ -1,200 +0,0 @@
|
||||
==============
|
||||
Using Sessions
|
||||
==============
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The :py:class:`keystoneauth1.session.Session` class was introduced into
|
||||
keystoneclient as an attempt to bring a unified interface to the various
|
||||
OpenStack clients that share common authentication and request parameters
|
||||
between a variety of services.
|
||||
|
||||
The model for using a Session and auth plugin as well as the general terms used
|
||||
have been heavily inspired by the `requests <http://docs.python-requests.org>`_
|
||||
library. However neither the Session class nor any of the authentication
|
||||
plugins rely directly on those concepts from the requests library so you should
|
||||
not expect a direct translation.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Common client authentication
|
||||
|
||||
Authentication is handled by one of a variety of authentication plugins and
|
||||
then this authentication information is shared between all the services that
|
||||
use the same Session object.
|
||||
|
||||
- Security maintenance
|
||||
|
||||
Security code is maintained in a single place and reused between all
|
||||
clients such that in the event of problems it can be fixed in a single
|
||||
location.
|
||||
|
||||
- Standard discovery mechanisms
|
||||
|
||||
Clients are not expected to have any knowledge of an identity token or any
|
||||
other form of identification credential. Service and endpoint discovery are
|
||||
handled by the Session and plugins.
|
||||
|
||||
|
||||
Sessions for Users
|
||||
==================
|
||||
|
||||
The Session object is the contact point to your OpenStack cloud services. It
|
||||
stores the authentication credentials and connection information required to
|
||||
communicate with OpenStack such that it can be reused to communicate with many
|
||||
services. When creating services this Session object is passed to the client
|
||||
so that it may use this information.
|
||||
|
||||
A Session will authenticate on demand. When a request that requires
|
||||
authentication passes through the Session the authentication plugin will be
|
||||
asked for a valid token. If a valid token is available it will be used
|
||||
otherwise the authentication plugin may attempt to contact the authentication
|
||||
service and fetch a new one.
|
||||
|
||||
An example from keystoneclient::
|
||||
|
||||
>>> from keystoneauth1.identity import v3
|
||||
>>> from keystoneauth1 import session
|
||||
>>> from keystoneclient.v3 import client
|
||||
|
||||
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
|
||||
... username='myuser',
|
||||
... password='mypassword',
|
||||
... project_id='proj',
|
||||
... user_domain_id='domain')
|
||||
>>> sess = session.Session(auth=auth,
|
||||
... verify='/path/to/ca.cert')
|
||||
>>> ks = client.Client(session=sess)
|
||||
>>> users = ks.users.list()
|
||||
|
||||
As clients adopt this means of operating they will be created in a similar
|
||||
fashion by passing the Session object to the client's constructor.
|
||||
|
||||
|
||||
Migrating keystoneclient to use a Session
|
||||
-----------------------------------------
|
||||
|
||||
By using a session with a keystoneclient Client we presume that you have opted
|
||||
in to new behavior defined by the session. For example authentication is now
|
||||
on-demand rather than on creation. To allow this change in behavior there are
|
||||
a number of functions that have changed behavior or are no longer available.
|
||||
|
||||
For example the
|
||||
:py:meth:`keystoneclient.httpclient.HTTPClient.authenticate` method used
|
||||
to be able to always re-authenticate the current client and fetch a new token.
|
||||
As this is now controlled by the Session and not the client this has changed,
|
||||
however the function will still exist to provide compatibility with older
|
||||
clients.
|
||||
|
||||
Likewise certain parameters such as ``user_id`` and ``auth_token`` that used to
|
||||
be available on the client object post authentication will remain
|
||||
uninitialized.
|
||||
|
||||
When converting an application to use a session object with keystoneclient you
|
||||
should be aware of the possibility of changes to authentication and
|
||||
authentication parameters and make sure to test your code thoroughly. It should
|
||||
have no impact on the typical CRUD interaction with the client.
|
||||
|
||||
|
||||
Sharing Authentication Plugins
|
||||
------------------------------
|
||||
|
||||
A session can only contain one authentication plugin however there is nothing
|
||||
that specifically binds the authentication plugin to that session, a new
|
||||
Session can be created that reuses the existing authentication plugin::
|
||||
|
||||
>>> new_sess = session.Session(auth=sess.auth,
|
||||
verify='/path/to/different-cas.cert')
|
||||
|
||||
In this case we cannot know which session object will be used when the plugin
|
||||
performs the authentication call so the command must be able to succeed with
|
||||
either.
|
||||
|
||||
Authentication plugins can also be provided on a per-request basis. This will
|
||||
be beneficial in a situation where a single session is juggling multiple
|
||||
authentication credentials::
|
||||
|
||||
>>> sess.get('https://my.keystone.com:5000/v3',
|
||||
auth=my_auth_plugin)
|
||||
|
||||
If an auth plugin is provided via parameter then it will override any auth
|
||||
plugin on the session.
|
||||
|
||||
Sessions for Client Developers
|
||||
==============================
|
||||
|
||||
Sessions are intended to take away much of the hassle of dealing with
|
||||
authentication data and token formats. Clients should be able to specify filter
|
||||
parameters for selecting the endpoint and have the parsing of the catalog
|
||||
managed for them.
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
|
||||
When making a request with a session object you can simply pass the keyword
|
||||
parameter ``authenticated`` to indicate whether the argument should contain a
|
||||
token, by default a token is included if an authentication plugin is available::
|
||||
|
||||
>>> # In keystone this route is unprotected by default
|
||||
>>> resp = sess.get('https://my.keystone.com:5000/v3',
|
||||
authenticated=False)
|
||||
|
||||
|
||||
Service Discovery
|
||||
-----------------
|
||||
|
||||
In OpenStack the URLs of available services are distributed to the user as a
|
||||
part of the token they receive called the Service Catalog. Clients are expected
|
||||
to use the URLs from the Service Catalog rather than have them provided.
|
||||
|
||||
In general a client does not need to know the full URL for the server that they
|
||||
are communicating with, simply that it should send a request to a path
|
||||
belonging to the correct service.
|
||||
|
||||
This is controlled by the ``endpoint_filter`` parameter to a request which
|
||||
contains all the information an authentication plugin requires to determine the
|
||||
correct URL to which to send a request. When using this mode only the path for
|
||||
the request needs to be specified::
|
||||
|
||||
>>> resp = session.get('/v3/users',
|
||||
endpoint_filter={'service_type': 'identity',
|
||||
'interface': 'public',
|
||||
'region_name': 'myregion'})
|
||||
|
||||
``endpoint_filter`` accepts a number of arguments with which it can determine
|
||||
an endpoint url:
|
||||
|
||||
- ``service_type``: the type of service. For example ``identity``, ``compute``,
|
||||
``volume`` or many other predefined identifiers.
|
||||
|
||||
- ``interface``: the network exposure the interface has. This will be one of:
|
||||
|
||||
- ``public``: An endpoint that is available to the wider internet or network.
|
||||
- ``internal``: An endpoint that is only accessible within the private network.
|
||||
- ``admin``: An endpoint to be used for administrative tasks.
|
||||
|
||||
- ``region_name``: the name of the region where the endpoint resides.
|
||||
|
||||
The endpoint filter is a simple key-value filter and can be provided with any
|
||||
number of arguments. It is then up to the auth plugin to correctly use the
|
||||
parameters it understands.
|
||||
|
||||
The session object determines the URL matching the filter and append to it the
|
||||
provided path and so create a valid request. If multiple URL matches are found
|
||||
then any one may be chosen.
|
||||
|
||||
While authentication plugins will endeavour to maintain a consistent set of
|
||||
arguments for an ``endpoint_filter`` the concept of an authentication plugin is
|
||||
purposefully generic and a specific mechanism may not know how to interpret
|
||||
certain arguments and ignore them. For example the
|
||||
:py:class:`keystoneauth1.identity.generic.token.Token` plugin (which is used
|
||||
when you want to always use a specific endpoint and token combination) will
|
||||
always return the same endpoint regardless of the parameters to
|
||||
``endpoint_filter`` or a custom OpenStack authentication mechanism may not have
|
||||
the concept of multiple ``interface`` options and choose to ignore that
|
||||
parameter.
|
||||
|
||||
There is some expectation on the user that they understand the limitations of
|
||||
the authentication system they are using.
|
@@ -1,23 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID1jCCAr6gAwIBAgIJAJOtRP2+wrM/MA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD
|
||||
VQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55
|
||||
dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMG
|
||||
CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2Vs
|
||||
ZiBTaWduZWQwIBcNMTMwOTEzMTYyNTQyWhgPMjA3MjAzMDcxNjI1NDJaMIGeMQow
|
||||
CAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1
|
||||
bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTEl
|
||||
MCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxML
|
||||
U2VsZiBTaWduZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCl8906
|
||||
EaRpibQFcCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTe
|
||||
qQEZtHpC3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LT
|
||||
Dkjox0eA9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08
|
||||
XPJX+F0bUDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1
|
||||
df1fBtZ+6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyT
|
||||
RvEoFie7qtGADIofAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
|
||||
AQEFBQADggEBAJRMdEwAdN+crqI9dBLYlbBbnQ8xr9mk+REMdz9+SKhDCNdVisWU
|
||||
iLEZvK/aozrsRsDi81JjS4Tz0wXo8zsPPoDnXgDYEicNPTKifbPKgHdDIGFOwBKn
|
||||
y2cF6fHEn8n3KIBrDCNY6rHcYGZ7lbq/8eF0GoYQboPiuYesvVpynPmIK5/Mmire
|
||||
EuuZALAe1IFqqFt+l6tiJU2JWUFjLkFARMOD14qFZm+SInl64toi08j6gdou+NMW
|
||||
7GEMbVHwNTafM/TgFN5j0yP9SAnYubckLSyH6hwR+rM8dztP5769joxQfnc9O/Bn
|
||||
TBD9KFpeQv6VJWLAxiIKcQCRTTDJLZZ0MQI=
|
||||
-----END CERTIFICATE-----
|
@@ -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-----
|
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDpTCCAo0CAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV
|
||||
BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK
|
||||
EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr
|
||||
ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x
|
||||
MzA5MTMxNjI1NDNaGA8yMDcyMDMwNzE2MjU0M1owgY8xCzAJBgNVBAYTAlVTMQsw
|
||||
CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh
|
||||
Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv
|
||||
cGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMz5WsgsuX3rZUdLwQpZXN2Ro7LQ6jEZnreBqMztVObw
|
||||
BuC1WdiJsg6dVlC7PVdt+0gY1c8WFg1TKmsucxesQSyfGAPg+9T/hsRMb6y12uJx
|
||||
fp3Wgqqw0U1HsXvMiaJH87MaGnt043BxzF+R9fhAcDk6Cyj5cx9J0LvZJEOzN4J4
|
||||
ZRyO6j/DZZItb3lK5W9xkuoT+mTdDZOQJnXyG818uiWfjdCkLjr1ruytRcBOo4na
|
||||
Y828voT/A7I95+YCgKgbjiUWhHeTaNmMEQiGy0nGYfteC+oSsHOlxZ3b12azzHPk
|
||||
83Bh2ez0Ih9vcZoe9DqvlFOXfv9q8OsYc5Yo6gPTXEsCAwEAATANBgkqhkiG9w0B
|
||||
AQUFAAOCAQEAmaYE98kOQWu6DV84ZcZP/OdT8eeu3vdB247nRj+6+GYItN/Gzqt4
|
||||
HVvz7c+FVTolCcAQQ+z3XGswI9fIJ78Hb0p9CgnLprc3L7Xtk60Im59Xlf3tcurn
|
||||
r/ZnSDcjRBXKiEDrSM0VrhAnc0GoSeb6aDWopec+1hWOWfBVAg9R8yJgU9sUgO3O
|
||||
0gimGyrw8eubmNhckSQLJTunUTsrkcBjuSg63wAD9OqCiX6c2eoQr+0YBp2eV2/n
|
||||
aOiJXWNLbeueMKSYiJNyyvM/dlON7/56cdwDTzKzgD34TImouM5VKipUwCX1ovLu
|
||||
ITLzALzpqFFzc8ugV9pMgUKtDbZoPp9EEA==
|
||||
-----END CERTIFICATE-----
|
@@ -1,22 +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-----
|
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": "2038-01-18T21:14:07Z",
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"id": "placeholder",
|
||||
"tenant": {
|
||||
"id": "tenant_id1",
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "tenant_name1"
|
||||
}
|
||||
},
|
||||
"serviceCatalog": [
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "volume",
|
||||
"name": "volume"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:9292/v1",
|
||||
"publicURL": "http://127.0.0.1:9292/v1"
|
||||
}
|
||||
],
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:35357/v2.0",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://127.0.0.1:35357/v2.0",
|
||||
"publicURL": "http://127.0.0.1:5000/v2.0"
|
||||
}
|
||||
],
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"username": "revoked_username1",
|
||||
"roles_links": [
|
||||
"role1",
|
||||
"role2"
|
||||
],
|
||||
"id": "revoked_user_id1",
|
||||
"roles": [
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"name": "revoked_username1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIOTQYJKoZIhvcNAQcCoIIOPjCCDjoCAQExCTAHBgUrDgMCGjCCDFoGCSqGSIb3
|
||||
DQEHAaCCDEsEggxHew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6
|
||||
IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMzgtMDEtMThUMjE6MTQ6MDda
|
||||
IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow
|
||||
N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg
|
||||
ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p
|
||||
ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg
|
||||
ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu
|
||||
YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN
|
||||
CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg
|
||||
ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg
|
||||
ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu
|
||||
MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3
|
||||
LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v
|
||||
MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx
|
||||
N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K
|
||||
ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg
|
||||
ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg
|
||||
IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg
|
||||
ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg
|
||||
ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x
|
||||
MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn
|
||||
aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50
|
||||
ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5
|
||||
MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg
|
||||
XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg
|
||||
ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg
|
||||
ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog
|
||||
ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg
|
||||
ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6
|
||||
Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli
|
||||
YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn
|
||||
aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6
|
||||
ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2
|
||||
MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz
|
||||
NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg
|
||||
ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl
|
||||
IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg
|
||||
fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp
|
||||
bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg
|
||||
ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz
|
||||
NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi
|
||||
OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg
|
||||
ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl
|
||||
IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u
|
||||
ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7
|
||||
DQogICAgICAgICAgICAidXNlcm5hbWUiOiAicmV2b2tlZF91c2VybmFtZTEiLA0K
|
||||
ICAgICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJy
|
||||
b2xlMSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwN
|
||||
CiAgICAgICAgICAgICJpZCI6ICJyZXZva2VkX3VzZXJfaWQxIiwNCiAgICAgICAg
|
||||
ICAgICJyb2xlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAg
|
||||
ICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIs
|
||||
DQogICAgICAgICAgICAgICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAg
|
||||
ICAgICAgIH0sDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAg
|
||||
ICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAg
|
||||
ICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAgICAg
|
||||
ICB9DQogICAgICAgICAgICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAicmV2b2tl
|
||||
ZF91c2VybmFtZTEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHKMIIBxgIBATCB
|
||||
pDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYD
|
||||
VQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5
|
||||
c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDAS
|
||||
BgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIIB
|
||||
AA2C5qslA4D7vzbiPJ+PzI6CWKH4fxy2nl6wFneHRlzflRGVtbk7/gwVpgHvVH8+
|
||||
FvQEWeXiCvpXDcHUae0YsdB6aifDRkRctoBwWZkSIkLtdLjZTBrwoOBD2cWPTlr6
|
||||
gFPp0ARCKVP87YXiKHXStvivZDQFbnBrPTZbGwsCZFXzDYtVPkDvgWOIzHP+olB0
|
||||
k0wrFXdTQrr62GmkUdgmY31SBLAmPRlvbFBsdM8R62EVc9Mdk7A8Xenpib6+3hPV
|
||||
7Jgj5IcC3WWtI1A/WOzuEepfW5AU3bcmsJ4UrsJdZLPYqxy/FS37s7oekBOfSR+Y
|
||||
WSVmaaTY21X3kOqAQULJTDI=
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJylVkt3ozgT3etXzD6nTwBjJ17MQjyMRSzZYAxIOwMJIB524gePX_8Jk0xn0n2me77xxlCC4ureW1X69k38NNNC5A8db4ebbwAjhHaQ2k8HhrJrTKAT6wcRczxd1w1Jh47Z6h5caunuzUixbnFd10rJ0rev1hZFE2A45hLuRbBQzTRlT8-dnVGFlPEE5-tce0C1e90r_gXxQyrWjvGEyCxwX2joiHWYA8xhg3OpwVupXS-cDnuHlhiHhsiHfKXDnIVZsw_tMu7QDOmowwZWVx5sV56p-gZqwZqb0prDSZCjE9LtI9OHB-0mshacBdk1stwyHtckFkyzqHZGZJV_oYF9Aiy4BaS49svhi_tghJZYwwNTKVTKAm9vCcS9XA5bEdsqo2pxSRbzCxiC7w8ULCQ8rsomscprlAsk1lSOrHb-sm3ES4KXmh2p4hs0dLPImtdDMhBMTrmAVsTW_BjVbj8EhxgN3PM-mPq7orkh2i9dKTYO11VvdqRTmxWHF8GXCkgfK6sJbVc9lShnFVZYThUs497pSbBTqUcbVpFqbZQ55WLFSzKUD4jskinlFdyg6nbHguQYKdNNVO3ykYupxMJh3-0_ogADjP8fhSYDWrVHKvtbb5TvkCzdZp0_XrGHJrd96mq75umE9PSacPNKuJuTivassjntdz0gBpaZl2WEaxVVqLoO7Jxw2hLFzF98aVBHSOY2kVJekiV5iazysh9dGoVCHSA0ncbWbtS-mp-SQesBXiVME3yJ1yLh8u-qgX8p2xTzohv4-lACDFL8FyXAzzNr8u-RW3Rg7aGB3d8i7DNf-0DOmLLLwQBVFMaZbW9fqkUVXqhYGPwv6v9TwjHR0C-YJR-jckQH_ll7Z0B3wdtHhVhIYVw4oCKceFjCvV-uLVMB2GKc8XRKK6QQbk7oWJnvhKo3uHHl1_tgfvGU6bvEApHldwL5Cfi-jW81XmVSsoSzVffYYh4PKIR05mxtiCamzxW8VX9qdfArr_9KDfBv9vvjdu05uMkj-htbatfBOLE8P4n_t1sTXZyTQaVkWTbvKvFIkZskdP-yO_jwe1TNlSHjSh8b5l8Jx0QitiiioLx85Qx8JQ2LEiVeLLaDROxHRXZfFAGfJfmVIj9J3oBE9PZ9QH5VhTI2YCNqpRP3f7M9-D3fizlQu8dkWeRfrP8GWFj2iTW_MEHg-KLfsxC9z8Xh-vNAsUvRXN6G2ZiEYk68q_DRHMQY8_tQaY9RdR7nQ2d3kdJ-DJ7xOreTzxMMCJ8rkXIu2WIux4rffRpltxe-y1gWI8G0EVYuqJdVwly9mM7OlHI7I71oqnxRYS9WqJeIxorbr70xFr2ReeZHqd8G8VDOFTZIxSxTZTzLcI-kdYA66sWiPlDLhGVJJWwrmviPQ9YWk8nadaiWky_sdixkw8GiCCfficSC6JdQatN0-SRONlqbIg1AT9eOhq7V3HzCMLWgvDM1F2vEM1cYFuN9hnXfx63eQ1tLia_B1IMF0bCLGmBCaviOszSb0kuC6eU5ZGJ071qTQ2d8-ODpu3kjZoGXiEPHvjddDB9vifUWI7BV_Gk8ca-ilbe2B7mWFq9ZkVvzRtJ0xwwW1bl8Dokk2n3pWLdE_S1RN73GVdyChQG345ewp8ukjCya7pSyjiq_gOnwtdjStqc1bNAew--nM3E406Czg0ATZMAFn0pmu102GdE2eWhrLybzSqvOEc8n8LJlq0g9L06bbtIfD1acv21OyvLk6kb3Bp4QtCYpT6PzDLZP8n5Wwf1dc7w7blCXcsuzZEWPC3y_UFf1RZVbQ1XWj538TKM7PF89WkDG98-hu9laucfd3RVqao5fpSe-Wbjqw7qfxcfkGDMyu3cbVWXO9m55ThBaeQQnC1p6BKiBVOuHh24fsocHLV3fvzqlVlPJC-zjYfase-fr9IyuxdWfNefEhnpyj9y7N_rcsOeFaGOgxmdovBYUBqbjPhQPrSvL-LHbrifzqzQld_3GOLGNUt_Npe40zarJpFyJOb905oi60SsEslMv7oOYuA9v_Cl5aJhHZhMEX7B9-BPcjtMmMb4frf8Hm6bNOA==
|
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": "2038-01-18T21:14:07Z",
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"id": "placeholder",
|
||||
"tenant": {
|
||||
"id": "tenant_id1",
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "tenant_name1"
|
||||
}
|
||||
},
|
||||
"serviceCatalog": [
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "volume",
|
||||
"name": "volume"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:9292/v1",
|
||||
"publicURL": "http://127.0.0.1:9292/v1"
|
||||
}
|
||||
],
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:35357/v2.0",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://127.0.0.1:35357/v2.0",
|
||||
"publicURL": "http://127.0.0.1:5000/v2.0"
|
||||
}
|
||||
],
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"username": "user_name1",
|
||||
"roles_links": [
|
||||
"role1",
|
||||
"role2"
|
||||
],
|
||||
"id": "user_id1",
|
||||
"roles": [
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"name": "user_name1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIONwYJKoZIhvcNAQcCoIIOKDCCDiQCAQExCTAHBgUrDgMCGjCCDEQGCSqGSIb3
|
||||
DQEHAaCCDDUEggwxew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6
|
||||
IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMzgtMDEtMThUMjE6MTQ6MDda
|
||||
IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow
|
||||
N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg
|
||||
ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p
|
||||
ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg
|
||||
ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu
|
||||
YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN
|
||||
CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg
|
||||
ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg
|
||||
ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu
|
||||
MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3
|
||||
LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v
|
||||
MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx
|
||||
N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K
|
||||
ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg
|
||||
ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg
|
||||
IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg
|
||||
ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg
|
||||
ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x
|
||||
MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn
|
||||
aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50
|
||||
ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5
|
||||
MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg
|
||||
XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg
|
||||
ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg
|
||||
ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog
|
||||
ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg
|
||||
ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6
|
||||
Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli
|
||||
YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn
|
||||
aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6
|
||||
ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2
|
||||
MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz
|
||||
NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg
|
||||
ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl
|
||||
IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg
|
||||
fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp
|
||||
bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg
|
||||
ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz
|
||||
NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi
|
||||
OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg
|
||||
ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl
|
||||
IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u
|
||||
ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7
|
||||
DQogICAgICAgICAgICAidXNlcm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAg
|
||||
ICAgICAicm9sZXNfbGlua3MiOiBbDQogICAgICAgICAgICAgICAgInJvbGUxIiwN
|
||||
CiAgICAgICAgICAgICAgICAicm9sZTIiDQogICAgICAgICAgICBdLA0KICAgICAg
|
||||
ICAgICAgImlkIjogInVzZXJfaWQxIiwNCiAgICAgICAgICAgICJyb2xlcyI6IFsN
|
||||
CiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICJpZCI6ICJm
|
||||
MDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAg
|
||||
ICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAgICAgICAgIH0sDQogICAg
|
||||
ICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQiOiAiZjAzZmRh
|
||||
OGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAgICAgICAgICAg
|
||||
ICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAg
|
||||
ICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAidXNlcl9uYW1lMSINCiAgICAgICAg
|
||||
fQ0KICAgIH0NCn0NCjGCAcowggHGAgEBMIGkMIGeMQowCAYDVQQFEwE1MQswCQYD
|
||||
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTESMBAGA1UE
|
||||
ChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3DQEJARYW
|
||||
a2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2VsZiBTaWduZWQCAREw
|
||||
BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEggEAxyHPqb53KXaWJH1IE6IFp3zzm5vl
|
||||
zlotcMxMepMRIxQPUDwJrP2ZJwXemQXVTpRa3Aer7hSkCRlyI++mcj/rD4h5Ygb0
|
||||
q9sscjfeZB11Y436E4ZhXCdTfrtmKyBlHMqyhTBz64zroN0P+DVH7OLZDX/gqN2U
|
||||
KTX99HTN+LvUa8VqQYIzsjNv80CU6pog/YOCGPixjMKE9m9xYUr9huKZUxliHtX2
|
||||
AHoCfQPhI8nsnNHLzCx6u5xIM7A69ZIDPQ82hSHC58k+g0bq9uflRCixBSD7ulR7
|
||||
7ZRJM8IgOgFGpNeuyKcHJsCdPpZS8p1MmDCkwTOt5Kvf7Nopz+Cc325uOA==
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJylVkmTmzgUvutXzN2VCotxm0MObMbQSG52ixtLG5DBdrttC_j1I3An6XRSk8wMVS6jJ_H0vfe97dMn9qiGaaG_NOiPi08AWpa1KbH9eEys6pYjxc21I5M9Dhp7ck1xjU4LlLVahme9hJpJNE3d56bmv5i-lYlAd421kjIhKY2yxNxzb1dYQE0uwnpTqw_WwbulQnS1yLFke6dcRHwSezu8ddm-UgNIFAprjkKf6zYrt4fBsUP6kSL-WDuaUifbiqZbu8l7a2FpVg91OHcCpXMCYx7pVgc2xOA2RBHj2nq1NPuUaONBm2bmiiRxdctMr8nve1wSS1V2cO_I2uiKY_sVJPEk4PJD1Iw3pvEdWmGOByRuKzR76E8K2JpvRlOYWU3Wrq7FSr6CUfh2YJ9sEcnbhhZmc8tqhsSU-Mzs5J1P2UfML4fkhIVIx1uvykz5MCoDsfhaM2jMr_IpO3jDKBxlOPYuaSxF4Z5OiNK1x-X68eYMRo_6OXWIcmX-mgM05IIj4s4ZMIdJ0kIhqbEAeTi4A4rDOQ4wTVrUbvSmxoTtBEVl1SMiu0mE5gYmqJrdJ3FxygTpKWvD-u4LiUu2o93dP6IAI4z_jkLlAW67E-YjP7jTdyzWHt3UyxsMLHGyU5t3G1KKaMC3ghg3RLwatXhIWpvgIRwA0iGfBFWFiNpiAc83sV0jgjskGPUu4kZ2GGUezYTmWqzRLjOba3qP0mzL2AGMUyk3wzv3rfxajFyP8FoWNPEH-YEpXP_IGviXtEmQ7PvRX1-ZACMV_4cJ8GvNKv9nzt33YBNYo3f_yGHv_ZXGfJUIYQ1GqCwxLok_3XRgWXjFbGOMf5b_7xTeFY31IjH5U9bc0YF_5t4d0V2hvxSQaQkJYRHQIoICyMEhajamIQBoJiQhpYRbS0DEEPE9M98cOp_g5m10SGP5GgjSG8UMkRn1DPkriCIbTjneVlyxVhZOv-wgyUcUjDpjsdFZEdNkAfrzX4Y6-F2s_44N8G_s_dlcWwYTPay-JWv1NgZOzsuv7P88FdHVpRhZKtYNfWOJZAJPi633LdzB13jPWlkYNTravWB-U3hXxGSrfRY3148-Ax-dBlmKoiBn5lhM9jMj4QdGwHtKfsfIL5RTULDansbod1nIQ12hLFd6tv4h7MGfxT3rAwfvVKz39YfQP4Nk2wyFKV8T5sD7h9GQbK23vji-v28o03o3KQiMSRnIWbVhDeUHBKxQsJYWfi0a43tvNdz71sfnQtSPXQu8daU-E7rmO2XN_u5MTFnY7nFQtSyQBkhcCRO7QgOrn2TVwiAXAA4KVkRh97EOTsgYzLe0_npzC3XUJqYxT0hVwcHiwCa2ehzkLBesLmHhiVoWoqxg_9xQ30w58MV7R4Lv9ky3d-yAvAtMTcmPtCzXplIaKrTMPfs9Q_dINQXrkeuuDGrw0H2lQHMngWlQOwoHw4HK3lT40NBUqLmc0RlEcdUSRaqSB1qE-KyVpIIFHTPPh6pigulwBe1AVJusQRyO0Rl6BtXppNgxaOV8ozowGqjBb_MRG49soHg4ZjOQlIveLWsjJRsVHe6KnFbuk8EIoWpNqJQOOqEQvSa1GqRxcWXDicYUGFSlePVI57pSHanuvp_YDFV1FTZ8GcrR8fnhBZ6Ka4w_z1waumEnBQICw9PlSQwpmuOXQEtDY1bOTjj9LPl8uh4cdbkwrm2OWkvkdNecc8q88Qq03ivo7FKXPokgTtSDB88PHJnrPsGz2usVbDw-n8O63D4f3dNgPKk7a_biR4EmIrWSU4srF7vhtnDD54cdmMmZX1mv-7amy94ZxKBOLw9pcsj4gX19SR_oSrDzM5JO9SyfyVJcbZ6e02A2S-OLTC4FkJf-jddP9bBYPl1m5Voqs3WnWhDXnVq-SMre4j9_3qsCryp28xwfnzDPZaQ4s5GkWyzkGNyiy9Z9sG_4Obsttd3jUhXFonIWc_9Y53m3ibLSDg2P1fIjuZ1Z3WnDTVPBLjy2p8H98gVME7OB9O_T89-mTsWe
|
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": "2010-06-02T14:47:34Z",
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"id": "placeholder",
|
||||
"tenant": {
|
||||
"id": "tenant_id1",
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "tenant_name1"
|
||||
}
|
||||
},
|
||||
"serviceCatalog": [
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "volume",
|
||||
"name": "volume"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:9292/v1",
|
||||
"publicURL": "http://127.0.0.1:9292/v1"
|
||||
}
|
||||
],
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne",
|
||||
"internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"
|
||||
}
|
||||
],
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
},
|
||||
{
|
||||
"endpoints_links": [],
|
||||
"endpoints": [
|
||||
{
|
||||
"adminURL": "http://127.0.0.1:35357/v2.0",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://127.0.0.1:35357/v2.0",
|
||||
"publicURL": "http://127.0.0.1:5000/v2.0"
|
||||
}
|
||||
],
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"username": "user_name1",
|
||||
"roles_links": [
|
||||
"role1",
|
||||
"role2"
|
||||
],
|
||||
"id": "user_id1",
|
||||
"roles": [
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"name": "user_name1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIINuQYJKoZIhvcNAQcCoIINqjCCDaYCAQExCTAHBgUrDgMCGjCCC8YGCSqGSIb3
|
||||
DQEHAaCCC7cEgguzew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6
|
||||
IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIwMTAtMDYtMDJUMTQ6NDc6MzRa
|
||||
IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow
|
||||
N1oiLA0KICAgICAgICAgICAgImlkIjogInBsYWNlaG9sZGVyIiwNCiAgICAgICAg
|
||||
ICAgICJ0ZW5hbnQiOiB7DQogICAgICAgICAgICAgICAgImlkIjogInRlbmFudF9p
|
||||
ZDEiLA0KICAgICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAg
|
||||
ICAgICAgICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgICAgICJu
|
||||
YW1lIjogInRlbmFudF9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwN
|
||||
CiAgICAgICAgInNlcnZpY2VDYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg
|
||||
ICAgICAgICAgICAgICJlbmRwb2ludHNfbGlua3MiOiBbXSwNCiAgICAgICAgICAg
|
||||
ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAiYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAu
|
||||
MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3
|
||||
LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1YmxpY1VSTCI6ICJodHRwOi8v
|
||||
MTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYx
|
||||
N2EiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0K
|
||||
ICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAg
|
||||
ICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAg
|
||||
IHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAg
|
||||
ICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAg
|
||||
ew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8x
|
||||
MjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVn
|
||||
aW9uIjogInJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50
|
||||
ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5
|
||||
MjkyL3YxIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg
|
||||
XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAg
|
||||
ICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg
|
||||
ICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQog
|
||||
ICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAg
|
||||
ICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6
|
||||
Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli
|
||||
YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn
|
||||
aW9uT25lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbFVSTCI6
|
||||
ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2
|
||||
MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz
|
||||
NWU4YTYwZmNmODliYjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAg
|
||||
ICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRl
|
||||
IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAg
|
||||
fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xp
|
||||
bmtzIjogW10sDQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAg
|
||||
ICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImFkbWlu
|
||||
VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz
|
||||
NTM1Ny92Mi4wIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwi
|
||||
OiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YyLjAiDQogICAgICAgICAgICAgICAg
|
||||
ICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJ0eXBl
|
||||
IjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9u
|
||||
ZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgInVzZXIiOiB7
|
||||
DQogICAgICAgICAgICAidXNlcm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAg
|
||||
ICAgICAicm9sZXNfbGlua3MiOiBbDQogICAgICAgICAgICAgICAgInJvbGUxIiwN
|
||||
CiAgICAgICAgICAgICAgICAicm9sZTIiDQogICAgICAgICAgICBdLA0KICAgICAg
|
||||
ICAgICAgImlkIjogInVzZXJfaWQxIiwNCiAgICAgICAgICAgICJyb2xlcyI6IFsN
|
||||
CiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICJuYW1lIjog
|
||||
InJvbGUxIg0KICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgew0K
|
||||
ICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMiINCiAgICAgICAgICAg
|
||||
ICAgICB9DQogICAgICAgICAgICBdLA0KICAgICAgICAgICAgIm5hbWUiOiAidXNl
|
||||
cl9uYW1lMSINCiAgICAgICAgfQ0KICAgIH0NCn0NCjGCAcowggHGAgEBMIGkMIGe
|
||||
MQowCAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcT
|
||||
CVN1bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9u
|
||||
ZTElMCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UE
|
||||
AxMLU2VsZiBTaWduZWQCAREwBwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEggEAw7K9
|
||||
7FaxXE6QNbsWmTAo/mtppDB2hv2DCwxMnjaZuOlV3g7UGnF8mxHjWd2Pcj1r0oGb
|
||||
0iACE9qmoZVHTPWU6WWBClAIF/bcs6Y+5S10bCu1uRVrzUCsLEbbJOLxBZG1qiEZ
|
||||
opLn6pBIOY8ovxcoKKmI56JgsqVGclZM5yH9Z9E5hSZgMREJZFZcVHA3pTJeTjc2
|
||||
9Mpb3RS5Q/FXf2nP09YA4Mp9+J15gFH/YuhBQiyo+LqvHtg+DdWdxcM3keAaTuxw
|
||||
Z8Cd26T+cTv1iS5qXcykd8OP7V0eIF7i39wshXGm6B9XpwFEYiLTZy7398O/yeGd
|
||||
izImJNpCowBA0Pyr8w==
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJylVtuSmzgQfddX7PtUKiDsGfOQBy4yhrHEcLd4MzAGZLA99pjb16_Ak2QySW2yu66iDC3RnO5zulufPvGfigyT_KVhb3z4BLBpmnZOrcdjbBZNShQn1Y7c9jho_JdqioM6zVdWah6c9RxrBtM0dZ8amvdieGYiAd1BK2XLjSxHeU6F594qKCRVKuHSLtUH8-A2WxheTXbM-doplYgYR-6Obhy-rpQAM6XFpdBiT-jspdNj_9gR_dgS8ViuNaWMN0W73VhV2pv3pmb2WEft2lcgv_pQRwKwmSPZDAtRaV5MzTrF2rjRahNjyeKoaBLDrdLbmhBH8yI5ODdkdXilkXUBcTQZhPQQVuMXt9ENWmaMG-bCBlZ77E0O-LNYjaHwsKqkXl6zpXwFo_Ftwz7eEJbWVZsZVZOUHIkxFxOjk3dey1_ieTnEJwpDnW7cIjHkw-gMRNKl5NB4XuVTcnCH0TjaaOS-bqN5GOzbCdF25QqpfmzWA-pJP2vXTLnyfM0AGVK4lmi3HqhAWVxjGJcUYhEPzkCiYEZ92sY1qW29KinjK35WmOWIyKpiWDVggqpZfRxlpwTOn5I6KG-5mAvxZoy7-0cUYITx31GoIqB1d6Ji6Pk3-o7Zym3tctFg35SmOLVZZ7NcIgNtMoYawtyS1HSIa4vRIRgA0bEY-0VBmFpTSGd2ZJWE0Y5AVO5CYWSHU-a2Cayu2YrsEqO6bm8qTTacHcA5nadGcOO-li_ZyPUIr-aiiT7YD9zh6kfWwL-kbY7Zvh_z9ZUJMFLxf5gAv_asin-W3H0PbN8cs_tHCXufr20kFjEMSjBC5YXxGnvTlw68Cq-UL4z65_X_zuHN0dgvYkM8JdUNHfhn7p0R3RV7C0gME8aMK6AmjPhYwENY2QaCABsxi1k-p7UJCUMSvVXmW0JnE9y0Dg_bSL76cP5GMUdkhD1HfgFhaOGpxutCyFbK_bpfdJilIwpOHbq3dd7ENBlib_ZLqYPfaf13bIB_E-_P4VoymOjh_S1eqc0onFSUL_z_PDXR5Ws2spStqvaNJZZAsc027je5g696T2oZjh7X2q1hfnN4c8Rty30SVdePOQMfk4Z5iRI_5eGY3PYzI8EHRsB7Sn7HyC-ctyDjvX0bkd9VoYh1peW10vPnH2QP_kz3fA4c3FO22pcfpH8G8aYaMkO-xjyBtxfDId6Yb3NxvH8_UKbn3eTAR5MzkPJuwwfKDwh4o-AjLfjaNMb73qyE96NPTGHYj1MLvE2lPoFd9Z2yan9LJm25bPfUL2oupAEzZ06ZVZCB90-2rLGfQkD9jDdR3H3sgxMyDvOtrL9-ucY6qWMDzWJWFHgw-XSOzJ76Ka8Fs4u5PEnNJcob9s8D9S2Ug5i9TyT4Hs_09Y5vkHe-oSnpsc3zlaHkSMWmsefXM3aOraZQPXScJWqRiJ1LCzRnMhiotcJgQGus7A1FDJCmYs0RUIeY4qg5CVUl9bWQiEk9n2dcdDw8D6uKAabNBbZ8Sa2Sigg0ImfsolZvJ8dr1Bbrb1T7qMIa_nY-4scjCygujfgZaJ5KbpPUoZKMjg43R-ta7uMBBVg1J1RKh9cBDC9xqfrbKLvyw4nGHaBWbenysZ3pSnFsdef9iQ2pqqPwwxdShifbKSSRDulneAkzL_1s95acEXAHC8PNXUdeP_Qrz9qeXvW6znWfvOrq4irIun8XtKKPVnfijJEHMs9DT9RWUmE9KjynDFjbJfPxS7Mx0iWWFs9RWj46L3Z3V587XM1PWwNCXoTJ2UNDv1d9vvuz9tLXV_kxDWYYgqHYd8_N6XzoH2b-xWpw0y_gJtAsxErjRb5G4RbKz_YBO2VeLXPDpyxSbVs8N2ZTBVgCiztMAyErXrdbb7N_Me8fcjVnq9dBsKTF4alod0-SuyzE3LNe81ZdFU6TCv48WWXN-hB7O_B8DmemaSyebLh7CO6kaJFerYooCIa2zek7UjVGq6ck30Okq4_3s0OV2WpyLwkwwsqXL2A6MSOifz89_w1E1sSW
|
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": "2112-08-17T15:35:34Z",
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"id": "01e032c996ef4406b144335915a41e79"
|
||||
},
|
||||
"serviceCatalog": {},
|
||||
"user": {
|
||||
"username": "user_name1",
|
||||
"roles_links": [],
|
||||
"id": "c9c89e3be3ee453fbf00c7966f6d3fbd",
|
||||
"roles": [
|
||||
{
|
||||
"id": "359da42d31c04437a32812aeb79e9c0b",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "581af19726fa4af5bda745789ab2bf2b",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"name": "user_name1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIE9gYJKoZIhvcNAQcCoIIE5zCCBOMCAQExCTAHBgUrDgMCGjCCAwMGCSqGSIb3
|
||||
DQEHAaCCAvQEggLwew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJ0b2tlbiI6
|
||||
IHsNCiAgICAgICAgICAgICJleHBpcmVzIjogIjIxMTItMDgtMTdUMTU6MzU6MzRa
|
||||
IiwNCiAgICAgICAgICAgICJpc3N1ZWRfYXQiOiAiMjAwMi0wMS0xOFQyMToxNDow
|
||||
N1oiLA0KICAgICAgICAgICAgImlkIjogIjAxZTAzMmM5OTZlZjQ0MDZiMTQ0MzM1
|
||||
OTE1YTQxZTc5Ig0KICAgICAgICB9LA0KICAgICAgICAic2VydmljZUNhdGFsb2ci
|
||||
OiB7fSwNCiAgICAgICAgInVzZXIiOiB7DQogICAgICAgICAgICAidXNlcm5hbWUi
|
||||
OiAidXNlcl9uYW1lMSIsDQogICAgICAgICAgICAicm9sZXNfbGlua3MiOiBbXSwN
|
||||
CiAgICAgICAgICAgICJpZCI6ICJjOWM4OWUzYmUzZWU0NTNmYmYwMGM3OTY2ZjZk
|
||||
M2ZiZCIsDQogICAgICAgICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICAgICAg
|
||||
ew0KICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMzU5ZGE0MmQzMWMwNDQzN2Ez
|
||||
MjgxMmFlYjc5ZTljMGIiLA0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJy
|
||||
b2xlMSINCiAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgImlkIjogIjU4MWFmMTk3MjZmYTRhZjViZGE3NDU3
|
||||
ODlhYjJiZjJiIiwNCiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIi
|
||||
DQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAg
|
||||
ICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHK
|
||||
MIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT
|
||||
AkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazERMA8G
|
||||
A1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFj
|
||||
ay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqGSIb3
|
||||
DQEBAQUABIIBAMmrhRIUjSd+SLUAYn+18MDB8MXiaiF+FJQbu86IFW3OpL86ksvg
|
||||
CTP44Rvu1F4vvoZAQ60/tOfFVNTnBgnMv0NEfl4huiFqYrXjCphnNFQ5OYnmU6LR
|
||||
bFV+dvjZXWUn0wJDroUUEjbgyy/mqUnULzQgUzyK7Ho8T0dWahQc7EFMNVjoeKfa
|
||||
K7DeRe9trNNHM8anKVaeKhpWIfzbxiwIwypukce6wVGfdhaP+58jeFnGwHUIsY8V
|
||||
8rzWj9UN46ko61piMAZljcktbrpqw2fDJ1H9Xl23G83rnXY7uVLQWUe7fRcUFtQt
|
||||
gQvKsGkN2hqlOgMT/FxFM3HC8kcl3wmzrNA=
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJxdVNmWqjoQfecr7nuvs5pBW30MIWKiCSKT5A1QZqQdEOTrb7DPHVmLIVWVqr03Vfn1S1w6MjH7A1JnWvySKMZGa4dk23KcPxMG7AS2wlaVEILZDAIbDdAFGz3zbkZGoTnZo5kJnavp4FiTDBttQCSMfImyzIzPL5KHKqsTjRZWoS_w5fCMVL_DZZsJ33eiMYUHhzQ82sIPComWoKeF3FNHHqy1_aJuOzCj7ZnSFjsICn7M--hI6uSFvzDEwo9eOxfMdi7SfAMpklVSRdxyUOA7huSbw3dgTwOvpyMpLbdSeRDKzABqWCLxpiNzq4EFSBYxmmQ5ZDVVSlT_dWrqknssP5nre6wmbwqp02f44o_8iH9Tmr5JFwZKPdGSfhvSuFk_uIvesJNmdedHlsZm3UU_WsTHKVFTV9Mm3NB5OGZz7rJCEo-au7ZCVV5woUc4JnNW8oY19sgbUuFiQkCesemP0-ZAuxdR8CMgHb25xE3BRQTTgPbMsEemopGW2UCbdR2WiahSl9TEb2RvlM6kEXnF6lBTQV_aQcHrL2ilN6PBuqFupVGBInQPOS_9QhTRmOFpllHnYUkEUlK8kTXzXIoD7w3nzdvFRerL09_4W6T_a5QelRUNsf6aGioJoSQ6rc8iu8_4bIAlwHrGfB14LnC9AY6A_KxDF9S-S-17D-3Q8G0bo54YtoscierABIqH9IEST_O7-FKrYSD4HXCPwDt4i_p6n5h-52kH0aX3Ablg_5P47koQPerzkcmxOheieD3u_z0Xlb7O-Y0f6_Fkrjru6c8pUfKTqIs1cpHowe5R9q5koP7h8mBo8Jp9c5GQC0boP4MEmJ5V17wqzFUv64L-WgLAEROnxy40lpf0Fg28fjkQr5bP1g0-Qt-trp_4RXab1bDZlQvFPkffLGmr62wLSLTYS9b2MhKrIzeowvOwgP35VOcfVbq7nLEefJzqb1nfpgf1dh0sdWM-QmW3WJwrvzBhh9bFl0R2kU3U3eqYwodf6ddV2OfzzAwWt0fqJI62dwS7fUn0wd2q22tM5LzxcyqjjMNZt46kpJtbY5PjW218jXuv1s3ncAZhvFAeymKFu2I8xOuxSZf-vH76-_IKeLy0WKvq7Hgbv6A0dJ-seV45vZufmqwrsZ68hlxvo2ZBFrkV55blvzbp_nOZmZes3LycHQkPVlzKz9N2OXxJS00ui6s_aPNNIDjeWsvY-vuP9mOuIel96iFm_HMC_gm2VKmF
|
@@ -1,140 +0,0 @@
|
||||
{
|
||||
"token": {
|
||||
"catalog": [
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "3b5e554bcf114f2483e8a1be7a0506d1",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "54abd2dc463c4ba4a72915498f8ecad1",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "70a7efa4b1b941968357cc43ae1419ee",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "5707c3fc0a294703a3c638e9cf6a6c3a",
|
||||
"type": "volume",
|
||||
"name": "volume"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "92217a3b95394492859bc49fd474382f",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "f20563bdf66f4efa8a1f11d99b672be1",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "375f9ba459a447738fb60fe5fc26e9aa",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "15c21aae6b274a8da52e0a068e908aac",
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "edbd9f50f66746ae9ed11dc3b1ae35da",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "9e03c46c80a34a159cb39f5cb0498b92",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "1df0b44d92634d59bd0e0d60cf7ce432",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "2f404fdb89154c589efbc10726b029ec",
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "a4501e141a4b4e14bf282e7bffd81dc5",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:35357/v3",
|
||||
"region": "RegionOne"
|
||||
},
|
||||
{
|
||||
"id": "3d17e3227bfc4483b58de5eaa584e360",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:35357/v3",
|
||||
"region": "RegionOne"
|
||||
},
|
||||
{
|
||||
"id": "8cd4b957090f4ca5842a22e9a74099cd",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:5000/v3",
|
||||
"region": "RegionOne"
|
||||
}
|
||||
],
|
||||
"id": "c5d926d566424e4fba4f80c37916cde5",
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}
|
||||
],
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"expires_at": "2038-01-18T21:14:07Z",
|
||||
"audit_ids": ["ZzzZ2ZZYqT8OzfUVvrjEITQ", "cCCCCCctTzO1-XUk5STybw"],
|
||||
"project": {
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "tenant_name1",
|
||||
"id": "tenant_id1",
|
||||
"domain": {
|
||||
"id": "domain_id1",
|
||||
"name": "domain_name1"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"name": "revoked_username1",
|
||||
"id": "revoked_user_id1",
|
||||
"domain": {
|
||||
"id": "domain_id1",
|
||||
"name": "domain_name1"
|
||||
}
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
"password"
|
||||
]
|
||||
}
|
||||
}
|
@@ -1,123 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIWqQYJKoZIhvcNAQcCoIIWmjCCFpYCAQExCTAHBgUrDgMCGjCCFLYGCSqGSIb3
|
||||
DQEHAaCCFKcEghSjew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgImNhdGFsb2ci
|
||||
OiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6
|
||||
IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgImlkIjogIjNiNWU1NTRiY2YxMTRmMjQ4M2U4YTFiZTdhMDUwNmQxIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4iLA0KICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3
|
||||
NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAg
|
||||
ICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICJpZCI6ICI1NGFiZDJkYzQ2M2M0YmE0YTcyOTE1NDk4
|
||||
ZjhlY2FkMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjog
|
||||
ImludGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0
|
||||
cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli
|
||||
YjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVn
|
||||
aW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAg
|
||||
ICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiNzBhN2VmYTRi
|
||||
MWI5NDE5NjgzNTdjYzQzYWUxNDE5ZWUiLA0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgImludGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUz
|
||||
NDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
InJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAg
|
||||
ICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICI1NzA3YzNm
|
||||
YzBhMjk0NzAzYTNjNjM4ZTljZjZhNmMzYSIsDQogICAgICAgICAgICAgICAgInR5
|
||||
cGUiOiAidm9sdW1lIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ2b2x1bWUi
|
||||
DQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg
|
||||
ICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICJpZCI6ICI5MjIxN2EzYjk1Mzk0NDkyODU5YmM0OWZk
|
||||
NDc0MzgyZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjog
|
||||
ImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDov
|
||||
LzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJy
|
||||
ZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAg
|
||||
ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi
|
||||
OiAiZjIwNTYzYmRmNjZmNGVmYThhMWYxMWQ5OWI2NzJiZTEiLA0KICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92
|
||||
MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9u
|
||||
ZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAg
|
||||
ew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjM3NWY5YmE0NTlhNDQ3
|
||||
NzM4ZmI2MGZlNWZjMjZlOWFhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJp
|
||||
bnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1
|
||||
cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAg
|
||||
ICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQi
|
||||
OiAiMTVjMjFhYWU2YjI3NGE4ZGE1MmUwYTA2OGU5MDhhYWMiLA0KICAgICAgICAg
|
||||
ICAgICAgICJ0eXBlIjogImltYWdlIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6
|
||||
ICJnbGFuY2UiDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAg
|
||||
ICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsN
|
||||
CiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICJlZGJkOWY1MGY2Njc0NmFl
|
||||
OWVkMTFkYzNiMWFlMzVkYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50
|
||||
ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwi
|
||||
OiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThh
|
||||
NjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lv
|
||||
biI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAg
|
||||
ICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICI5
|
||||
ZTAzYzQ2YzgwYTM0YTE1OWNiMzlmNWNiMDQ5OGI5MiIsDQogICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEv
|
||||
NjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAg
|
||||
ICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICJpZCI6ICIxZGYwYjQ0ZDkyNjM0ZDU5YmQwZTBkNjBjZjdjZTQz
|
||||
MiIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1Ymxp
|
||||
YyIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcu
|
||||
MC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdh
|
||||
IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25l
|
||||
Ig0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAg
|
||||
ICAgICAgICAgICAgICAiaWQiOiAiMmY0MDRmZGI4OTE1NGM1ODllZmJjMTA3MjZi
|
||||
MDI5ZWMiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjogImNvbXB1dGUiLA0KICAg
|
||||
ICAgICAgICAgICAgICJuYW1lIjogIm5vdmEiDQogICAgICAgICAgICB9LA0KICAg
|
||||
ICAgICAgICAgew0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAg
|
||||
ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6
|
||||
ICJhNDUwMWUxNDFhNGI0ZTE0YmYyODJlN2JmZmQ4MWRjNSIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTozNTM1Ny92MyIs
|
||||
DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSIN
|
||||
CiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0K
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjNkMTdlMzIyN2JmYzQ0ODNi
|
||||
NThkZTVlYWE1ODRlMzYwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRl
|
||||
cmZhY2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVy
|
||||
bCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAgICAgICAgICAg
|
||||
ICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAiaWQiOiAiOGNkNGI5NTcwOTBmNGNhNTg0MmEyMmU5YTc0MDk5Y2Qi
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJwdWJsaWMi
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAu
|
||||
MC4xOjUwMDAvdjMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6
|
||||
ICJSZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAg
|
||||
ICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICJjNWQ5MjZkNTY2NDI0ZTRm
|
||||
YmE0ZjgwYzM3OTE2Y2RlNSIsDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaWRl
|
||||
bnRpdHkiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImtleXN0b25lIg0KICAg
|
||||
ICAgICAgICAgfQ0KICAgICAgICBdLA0KICAgICAgICAiaXNzdWVkX2F0IjogIjIw
|
||||
MDItMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImV4cGlyZXNfYXQiOiAiMjAz
|
||||
OC0wMS0xOFQyMToxNDowN1oiLA0KICAgICAgICAicHJvamVjdCI6IHsNCiAgICAg
|
||||
ICAgICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlv
|
||||
biI6IG51bGwsDQogICAgICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLA0K
|
||||
ICAgICAgICAgICAgImlkIjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRv
|
||||
bWFpbiI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQog
|
||||
ICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAg
|
||||
ICAgfQ0KICAgICAgICB9LA0KICAgICAgICAidXNlciI6IHsNCiAgICAgICAgICAg
|
||||
ICJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUxIiwNCiAgICAgICAgICAgICJpZCI6
|
||||
ICJyZXZva2VkX3VzZXJfaWQxIiwNCiAgICAgICAgICAgICJkb21haW4iOiB7DQog
|
||||
ICAgICAgICAgICAgICAgImlkIjogImRvbWFpbl9pZDEiLA0KICAgICAgICAgICAg
|
||||
ICAgICJuYW1lIjogImRvbWFpbl9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAg
|
||||
ICAgfSwNCiAgICAgICAgInJvbGVzIjogWw0KICAgICAgICAgICAgew0KICAgICAg
|
||||
ICAgICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYz
|
||||
MSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTEiDQogICAgICAgICAg
|
||||
ICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJpZCI6ICJmMDNm
|
||||
ZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAgICAg
|
||||
Im5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAg
|
||||
ICAgICJtZXRob2RzIjogWw0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAg
|
||||
ICBdDQogICAgfQ0KfQ0KMYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJ
|
||||
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYD
|
||||
VQQKEwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkB
|
||||
FhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIB
|
||||
ETAHBgUrDgMCGjANBgkqhkiG9w0BAQEFAASCAQCy1xOK1+nQy8tL3fORdWkcp0Y5
|
||||
88cNgl4sXmJOE1TOOEauMyVWE188gtxHelVDCFWr8kICALvAnPX0UbIhoEaxscey
|
||||
mvcUazUMP2WWSsBMgSXBfbl6amTZp5KMgpMmAuGjP1xok3yvOecEF6Szh8yE3Q5O
|
||||
sNEKsMI5UiJTDU7WWSUp1Zs7E4UvFjAepZGhIQWOCxSvEnrl3Mfw1f7HWKDBlijR
|
||||
4XnPqJPiTmYLjzyDmi31GOHWZM4nZxShHfidLblPV4AyA/gsCh27/cZxYW/Q+cyL
|
||||
wfQogs4g7XfNgLdDHlbvv7NCS06RhydhLeiqNUcCp4hnZZC16KDPWzJ2Ql5y
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJydWEt3ozgT3etXfPucPgPCpOPFLHgZ47FEwDws7QxODELYTvzA8OunsJ3uPDrT6a9z-uSE4FLdqntvlfLtG_wzHdej_7PIrP_hGyKeR4qGTf7ZcK845tQIcmsDzx5sy7LHgWUEzsmKjLG5ip_tFbFcYVnWNnCt2ZM78zIN2YEzNhbwcBM7q9WT-dBOlAzvZVZ6t954V2ZpoizcYZW38PNoV-buqMu15TGvg3I-a1bIW0-OmZt0ntisUm1XLtKg9Euj5MLoeB0WvssGLCIttWVJakcjLi9Jyk604wXFHkakc8qpZZRZPdrzGZxiTdoMnySZTYZTy_zu1bLqg3s1awjmFYuK2nedjohAZ2JSINqZNROjmkQ5ZtGypIKcvLKBD-hFlsbnbPJ6uOORVz4myg4OkA9jc5vXSTfHIwWdowuvId1qT2xnT6IiJsK5JVFwS-zl4hxsbUJWW8m0Ht6rrNahRJD6YTkabrl9gcLd4Z6l8tC_AAXdcusMq8qwWixS_RFq9CZDdC7Y9UNzfH548taXVCF4CQU-n7YcT1Q-6z8YyhzTdjE3lUU6PJwhZOtkl1lvcS_d5MBSXXkXVLB5WGTucP3SNcRTvcrd4TZbh69aqSt8PqlZSuWlA6Mq62Gd65G02QXWZjkOG4BwdySRp02FcSDW4OSLlUY7dlwK50hFWNKaAR_g5C7uqE1UHhUFFdA5zAZ-OikRFUAKfCkgtGbd47pUeCI5lsesGh6AH33614J6HROJpFGssJrWiESOwoWn-DaVQJATq2ONRYZKbF69ItMBatLyeiSuZOshyxxqhgBPH13N6-ZcvMU4VHJ7c5x2TkvbQXOGFm0GtMvxVGOnaccUJngNrCwZJipQOehoGgPfWcMhJR84zwT8KloWl6JdoZQXmvN0uc2wfp_V8Rk2ehEPjcKC1UHLXaJQAV_upKAuiEdUJxoFei8qntKiJ9wj8KEnWQ8D5TUvGL5yfpwAcaT4Vbs-6xb6ars-6xb6j3aB9h2Np6B71zsxUSkkqrAPwSkGiDbAgQ5CG6Xk0K7eXUBdeu5eqQwSXqaqvAjnqj4Ra6BQAR0QELz1o0BDBCIRDF9dIf2UQh8czDpavPeEHwF7TYDVvUgA_b8aeCkq-lnVClLyeg38Ca11RITXVxf4XamkqxRqQyA71llNFD_lFbVzBdyq5eWvaY1e8_qLtNaBXG1P6x4a-h1Vf9q819CIdawOawpaoG5Sg0MXqPd4kgJVUw_TblJCb99Q9XdMRZ9T9WtFRe_NgnZJDdQtaF_IKFBAxp0P06inNY8g7c7DPJIFu5IPvWbfIlULjt9iJ1EiiBgVLI0xE54GCh1w11FJHTdgPBj5bqwTu4AXyPsRt87c0aHHf60J2HzYZBjaOCb9gMn6OqH3hWJpuF-kg3Ow5XyyuzCyUJZj43ba3p2IyPsaQUudW9_ONUStISazwQer-qpTod_2Pw1LbsuaRib0n2nU5iBjULDtnMC9OgSTGR6Agbif9_8qMphUzQdo6DNsX4WG_tSFX6D5aQwLB1EQrckA3KWD_oL7SsEE0blI4Luh-FFR-v1i8R_URn_mwkFP7QOZ3WHwSTA2gADzTdCIgOaTfrRhWKIEFyvwAxCXcDR2ofoVyuC68lx0EWFdoremOaq4MEtqhxWkDj4ZVgAL2mhK4gYQHDwTUwm233prdXmeTMuxbK7UFbDGNMt5-M6JJzW1DQVWvtK3-ykVQsYrHey-ZJ3TwJbmgUiM1k8T8d6Js3qI2Y8JnRz42Dz2nLgsnfuzvaF3Y7vgrrqFFn7F2jqonYpoC4RpPxYqflWoN5BqR6GRceqnEklhMjERShKFvecNSL9c1Lzm9qrnufoyRD7Oi4szg_R36Gsc6Ckca-DE3Xu29p44-4yuBAcwM2LYi70-MxiowYBgT_XdUNI0KVgUDxB1YZwL44-c-HWm6G2qIBDbALqSj04sfz3eAII3YDhQ-tFGO0MnLsgXO6pvBy2LvLZ3YNBA44PQuPVxDYAdKZSQ9nY5rt7gZ11ypjO3Y9BE0AJUYGO_NzFQLwH7B1bWtEI8it-78TOfy27p9qm-nJh0fO5dV_3wmKWj7cuV6MeW9nNjl7BgnjGChanXvl8_JIfnZ5cF9HIoernm8Dk_LnBSzbX-tMn1xddT68M757sDuq7xxTKFOvQXj-vQ8OT29kFu2lRueZ4Eg0jb1knCcV5vR7MkDC_0pjaC6WcHmCrJeHtPZipL0p0aq6HO1vn5Wge0hWteIvloWCwv87OFVrdT0MM0cgYosT3ov6P4wtBS2EIeI9cy8k2zWo1dY-WYxHMr-P9Agk1jGcxOgmDkNDAbg11jBcxG8MB1mkkSd86UGJVrqLFjmcQKFOfkCCMwVzQxjTyyEqpmta4vQUCgxBkxjfO7yCrIJNJMmUmqgNqeSeg0dnM-aeo0xfRHSyNHEov8uPLCjXdihCxFUFU916BNdWJkfaD1JdC0Hra8c2JieueTjBOZxjjZ8dKMFunywFO4V8NhyGzY6J9mYBvFprGD15dwxzQDA-7TjtGOxMIPR9FjE37XlON2OLSOB7sjD972Dj3vk-d2KBcPs-i21Uf3T1YNbT7l2DUf13g_cx-GOztaP5Uj9n3HlcmoCOybMtQmpSKD5BQhO1wbnuf8VQxq81BFdydp7pVVfCrNm25YBtSST48zfdt8V7ARb2Ot3DaMTjl_rr2tk_ylofomW_jOatpsRxv_xr9ZVVYs75RsIBUdVHqzte-8gzmYPVZW87zOHruTtDb5Kdb8h0X2qHkomd3WT8Pd6GCnj3KCT-U8NZXn440q0yM7TXLn4K6G08aLk0qtd_e3M3pj4XEi56UbYWMNV1823R3Nm6ySHdnj1nkw_MhV8NPqefKPqdLh-AFnnXW_N-82BSmq2VgeqvvWHAyCv_9G5z-CONT--QeRfwEmaLr4
|
@@ -1,140 +0,0 @@
|
||||
{
|
||||
"token": {
|
||||
"methods": [
|
||||
"password"
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role1"
|
||||
},
|
||||
{
|
||||
"id": "f03fda8f8a3249b2a70fb1f176a7b631",
|
||||
"name": "role2"
|
||||
}
|
||||
],
|
||||
"issued_at": "2002-01-18T21:14:07Z",
|
||||
"expires_at": "2038-01-18T21:14:07Z",
|
||||
"audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"],
|
||||
"project": {
|
||||
"id": "tenant_id1",
|
||||
"domain": {
|
||||
"id": "domain_id1",
|
||||
"name": "domain_name1"
|
||||
},
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "tenant_name1"
|
||||
},
|
||||
"catalog": [
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "3b5e554bcf114f2483e8a1be7a0506d1",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "54abd2dc463c4ba4a72915498f8ecad1",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "70a7efa4b1b941968357cc43ae1419ee",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "5707c3fc0a294703a3c638e9cf6a6c3a",
|
||||
"type": "volume",
|
||||
"name": "volume"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "92217a3b95394492859bc49fd474382f",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "f20563bdf66f4efa8a1f11d99b672be1",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "375f9ba459a447738fb60fe5fc26e9aa",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:9292/v1",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "15c21aae6b274a8da52e0a068e908aac",
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "edbd9f50f66746ae9ed11dc3b1ae35da",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "9e03c46c80a34a159cb39f5cb0498b92",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
},
|
||||
{
|
||||
"id": "1df0b44d92634d59bd0e0d60cf7ce432",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a",
|
||||
"region": "regionOne"
|
||||
}
|
||||
],
|
||||
"id": "2f404fdb89154c589efbc10726b029ec",
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
},
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "a4501e141a4b4e14bf282e7bffd81dc5",
|
||||
"interface": "admin",
|
||||
"url": "http://127.0.0.1:35357/v3",
|
||||
"region": "RegionOne"
|
||||
},
|
||||
{
|
||||
"id": "3d17e3227bfc4483b58de5eaa584e360",
|
||||
"interface": "internal",
|
||||
"url": "http://127.0.0.1:35357/v3",
|
||||
"region": "RegionOne"
|
||||
},
|
||||
{
|
||||
"id": "8cd4b957090f4ca5842a22e9a74099cd",
|
||||
"interface": "public",
|
||||
"url": "http://127.0.0.1:5000/v3",
|
||||
"region": "RegionOne"
|
||||
}
|
||||
],
|
||||
"id": "c5d926d566424e4fba4f80c37916cde5",
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"domain": {
|
||||
"id": "domain_id1",
|
||||
"name": "domain_name1"
|
||||
},
|
||||
"name": "user_name1",
|
||||
"id": "user_id1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,123 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIWmgYJKoZIhvcNAQcCoIIWizCCFocCAQExCTAHBgUrDgMCGjCCFKcGCSqGSIb3
|
||||
DQEHAaCCFJgEghSUew0KICAgICJ0b2tlbiI6IHsNCiAgICAgICAgIm1ldGhvZHMi
|
||||
OiBbDQogICAgICAgICAgICAicGFzc3dvcmQiDQogICAgICAgIF0sDQogICAgICAg
|
||||
ICJyb2xlcyI6IFsNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiaWQi
|
||||
OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg
|
||||
ICAgICAgICJuYW1lIjogInJvbGUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg
|
||||
ICAgIHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3
|
||||
MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogInJvbGUy
|
||||
Ig0KICAgICAgICAgICAgfQ0KICAgICAgICBdLA0KICAgICAgICAiaXNzdWVkX2F0
|
||||
IjogIjIwMDItMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImV4cGlyZXNfYXQi
|
||||
OiAiMjAzOC0wMS0xOFQyMToxNDowN1oiLA0KICAgICAgICAicHJvamVjdCI6IHsN
|
||||
CiAgICAgICAgICAgICJpZCI6ICJ0ZW5hbnRfaWQxIiwNCiAgICAgICAgICAgICJk
|
||||
b21haW4iOiB7DQogICAgICAgICAgICAgICAgImlkIjogImRvbWFpbl9pZDEiLA0K
|
||||
ICAgICAgICAgICAgICAgICJuYW1lIjogImRvbWFpbl9uYW1lMSINCiAgICAgICAg
|
||||
ICAgIH0sDQogICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAgICAgICAg
|
||||
ICAiZGVzY3JpcHRpb24iOiBudWxsLA0KICAgICAgICAgICAgIm5hbWUiOiAidGVu
|
||||
YW50X25hbWUxIg0KICAgICAgICB9LA0KICAgICAgICAiY2F0YWxvZyI6IFsNCiAg
|
||||
ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAg
|
||||
ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi
|
||||
OiAiM2I1ZTU1NGJjZjExNGYyNDgzZThhMWJlN2EwNTA2ZDEiLA0KICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82
|
||||
NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAg
|
||||
ICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgImlkIjogIjU0YWJkMmRjNDYzYzRiYTRhNzI5MTU0OThmOGVjYWQx
|
||||
IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiaW50ZXJu
|
||||
YWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3
|
||||
LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2Ei
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUi
|
||||
DQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAgIHsN
|
||||
CiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICI3MGE3ZWZhNGIxYjk0MTk2
|
||||
ODM1N2NjNDNhZTE0MTllZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50
|
||||
ZXJmYWNlIjogInB1YmxpYyIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJs
|
||||
IjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2
|
||||
MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9u
|
||||
IjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAg
|
||||
ICAgICAgIF0sDQogICAgICAgICAgICAgICAgImlkIjogIjU3MDdjM2ZjMGEyOTQ3
|
||||
MDNhM2M2MzhlOWNmNmE2YzNhIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2
|
||||
b2x1bWUiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAg
|
||||
ICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBv
|
||||
aW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgImlkIjogIjkyMjE3YTNiOTUzOTQ0OTI4NTliYzQ5ZmQ0NzQzODJm
|
||||
IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4i
|
||||
LA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAu
|
||||
MC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6
|
||||
ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAg
|
||||
ICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICJmMjA1
|
||||
NjNiZGY2NmY0ZWZhOGExZjExZDk5YjY3MmJlMSIsDQogICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAg
|
||||
ICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMzc1ZjliYTQ1OWE0NDc3MzhmYjYw
|
||||
ZmU1ZmMyNmU5YWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj
|
||||
ZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJo
|
||||
dHRwOi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0N
|
||||
CiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICJpZCI6ICIxNWMy
|
||||
MWFhZTZiMjc0YThkYTUyZTBhMDY4ZTkwOGFhYyIsDQogICAgICAgICAgICAgICAg
|
||||
InR5cGUiOiAiaW1hZ2UiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImdsYW5j
|
||||
ZSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAg
|
||||
ICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgImlkIjogImVkYmQ5ZjUwZjY2NzQ2YWU5ZWQxMWRj
|
||||
M2IxYWUzNWRhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2Ui
|
||||
OiAiYWRtaW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRw
|
||||
Oi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5
|
||||
YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJl
|
||||
Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAg
|
||||
ICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjllMDNjNDZj
|
||||
ODBhMzRhMTU5Y2IzOWY1Y2IwNDk4YjkyIiwNCiAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICJpbnRlcmZhY2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm
|
||||
YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAg
|
||||
fSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgImlkIjogIjFkZjBiNDRkOTI2MzRkNTliZDBlMGQ2MGNmN2NlNDMyIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4
|
||||
Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAg
|
||||
ICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBdLA0KICAgICAgICAg
|
||||
ICAgICAgICJpZCI6ICIyZjQwNGZkYjg5MTU0YzU4OWVmYmMxMDcyNmIwMjllYyIs
|
||||
DQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29tcHV0ZSIsDQogICAgICAgICAg
|
||||
ICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAgICAgIH0sDQogICAgICAgICAg
|
||||
ICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAg
|
||||
ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogImE0NTAx
|
||||
ZTE0MWE0YjRlMTRiZjI4MmU3YmZmZDgxZGM1IiwNCiAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICJpbnRlcmZhY2UiOiAiYWRtaW4iLA0KICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwNCiAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAg
|
||||
ICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAiaWQiOiAiM2QxN2UzMjI3YmZjNDQ4M2I1OGRlNWVh
|
||||
YTU4NGUzNjAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6
|
||||
ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0
|
||||
dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjMiLA0KICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0s
|
||||
DQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICJpZCI6ICI4Y2Q0Yjk1NzA5MGY0Y2E1ODQyYTIyZTlhNzQwOTljZCIsDQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQogICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAw
|
||||
MC92MyIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lv
|
||||
bk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s
|
||||
DQogICAgICAgICAgICAgICAgImlkIjogImM1ZDkyNmQ1NjY0MjRlNGZiYTRmODBj
|
||||
Mzc5MTZjZGU1IiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpZGVudGl0eSIs
|
||||
DQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5c3RvbmUiDQogICAgICAgICAg
|
||||
ICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2VyIjogew0KICAgICAgICAgICAg
|
||||
ImRvbWFpbiI6IHsNCiAgICAgICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIs
|
||||
DQogICAgICAgICAgICAgICAgIm5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAg
|
||||
ICAgICAgfSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg
|
||||
ICAgICAgICAgImlkIjogInVzZXJfaWQxIg0KICAgICAgICB9DQogICAgfQ0KfQ0K
|
||||
MYIByjCCAcYCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD
|
||||
VQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sx
|
||||
ETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu
|
||||
c3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkq
|
||||
hkiG9w0BAQEFAASCAQCPCzpknZOfDONpHDWGrYTeyirjGGjrJem2EF2qsJ4K1x/V
|
||||
guNLX1AfRnRUC95wSpGS5VCQ+OSfSFmLjJQOnMqLZ1L2MkVfn0CIkqig19sgRZ+O
|
||||
hpi+0TpJ6XlCWRERJEICCOAHZ/M2iiiVFbFkIGtaJLw3HcXFreV+nEBuQSeIGH/H
|
||||
FjnmocYu9vy612YT47HcyQKNMaku3QBLzFTSTiGkS4ft9yT2pNMbHZsMmysaRKWl
|
||||
SfuA/DZHT6zi5D4lkxDBCexf3JAw4kOQSf/dirfDUKmIy4VPeAOuO1u86hN/coIS
|
||||
JvgAJGOVUxtZCQ9256dUvKa1pLpQAgW/Ok3oPulS
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJylWEl34rwS3etXvH2fPu0BElh6wthBcmw8IO2wnWDLNiRh8PDrXwlId4bu70u_lyxycKBUt-reWyW-f4cf3bId8h8DL8WL7wg7Ds5b6t7tmFOcMqL5mbGDZ2vTMEzbNzTf6oxQm-ub6MXcYMPmhmHsfNtYPttLJ1WR6VtzbQ0Pt5G12Tx1D70rpcqhTkvnxpnvyzSJpbU9rbIeXs_2ZWbPhkzNT1njl6tlu0HO1j2ldjw4fLdJ1H25TvzSK7WScW1gTVB4Nh3REPfErEvcWCq2WYkT2pGBFURxFIQHq1wYWpk2swNbwimG26dKV-OlO10Y-q3T1JUI7jS0xQqraFg0nm0NmPtjyt0CkUFvKJ81OMwUGuYl4bhzyhY-MC7SJDpnkzXTPQud8jGW9nBA_TDXn7ImHlbKTELn6Nxp8bA5YNM64LCIMLducOjfYDNfn4NtdcjqqaaqgCeyCk5pMnsSdUKiUD9x29MDTerjSqkrvHTEaUeayPUFwvVD9fT87AJRKxFLxgVtupoZoupBnyeR-ODT-bXhSuL_6TZ4hEM-Qcvt-IhoMpZWyvnh9Q1BnSmkX690aZ1Mj-L0dBvv0_kZP6eroEjt6fa1ayKDKrOnT3DKm1aOJbZyG5qQa_qzKgVol3rEfXrJbpfPgxZ55eSEQ0ddcO2IjVHn8Y1KBnrKuXUiPChJQ4EPcPIQDcTEMguLgnDonEJHXuKWiHAghXLhArRm-5o2EKxmSn1Kq-mRXQp6rYszUB7XJIwk2pAG4dCSGHckzyQ1EKSjTaTSUJOxyao3ZDpCwXrWzPiVbAJynUFBEeAR0eWsac-VXc8DKTN3p8Vg9aQftWdo4W5EhkxZqLRbDFSinDXAypIqWAYq-wNJIuA7bRmk5AHnKYd_hXlxKdoVSnmhOUvyp1QZ36dNdIaNXklEwgD44PfMxhLh8Gu7BbFBPLzqSOiPhahYQgpmWuUjqBBUe4aBsoYVVLlyfh6XqV3z37XrT91CX23Xn7qF_qFdoH1LZQno3nY6yisJh5XiQXCiAEQT4EAHoY11zaBdwl2cbTDO7CvPQcK5ENKZ3ldP4JEKCuXQAQ7Bey_0VYQhElbgdyhqLyHQB0uhAyk-Cec14BY0AQp-lQD6XzXwWlT0q6oVpOQIDfwNrccIc0dUF_hdyXioJGJCIDMa0wZLXsIqYmYSuFXPyt_TGr3l9RdpPQZy9YLWAhr6N6r-snmnJSEdaBM0BLRA7LgBhy6Q8HicAFUTRyGDW0Jv31H135iK_kzVrxUVfTQLMsQNULcgopChL4GMBw-mkaA1CyHtwVFYWBf0Sj70ln3rRC6Y8h47DmOO-aygSaRQ7qig0BGzLRk3UQvGoyDPjsbYLOAN-OOI26b27CjwX2tSp03Qpgq0cY7FgElFndDHQtEkOKyT0TlYvnL3F0YWUj7Xbhb9pMM8EzWCllo3npmpiBhTBS9Hn6zqq06F_rX_SVAys25IqEP_qUpMBjIGBZtWB-41IJjM8AAMxP5z_68ig5nYfoKG_oTtq9DQ37rwKzQviWDhwBIiDR6BuwzQX3DfmlOOx4zH8FeTvLAoPbFY_AO10d-5sC-ofcTLiQI-CcYGEGC-cRJi0HwsRpsCSxRnfAN-AOLilkovVL9CGV1XnosuQmVco_emOasY10tiBhWkDj4ZVAAL2qjX2PYhOHimQmqw_d7Zyvl5MuXzur1Sl6eK3Oar4IMTuw0xNQlWvtIzxZQKIOPNGOy-pIPVwpbmgEi03kti_tGJ02aq0J8TOj6yuX4SnLgsnYezvaEPY7tgtiy2r69Y2wC1kxHpgTD950JFbwr1DlJjSSTUOjGVcAKTifKgxmEgPG-ExXLRsIaZG8Fz-XWIfJ4XF2cG6e_R1zggKByp4MTDR7YKT1z-ia5Y8WFmRLAXOyIzGKj-CCuO7NlBTZK4oGE0QsSGcc61v3Lit5mi96mCQEwN6Io_O3H9-_EGEJwRVXxJjDYyaGNsg3wVS_ZMv6eh0wsHBg20HgiNGZ_XANiRghrSfsrn1Tv8dIjPdGZmBJrwe4AKbBR7EwX1YrB_YGVDKsTC6KMbv7BVPeS2SPX1xHhgK-fTqi9ajP6fVV8ciq6nypkS9--39ivzzqe7l3V_e97YizwByLPpE4P5gMSAcGrGH2ZRv6zrLjaL-4eGxfGW9esqduPZdTZG4zi26juoV_RQTbpFXMTrIQ5RPK_LvHfP2l6vyJAncSXuQj-vQqbz-6vQVp5i6uioh4ukllFxwWw3a7_dsFFncM3RNyTWtSjUwqgzBs29vIZpWMch9vet4VMz9n0HWa1r-qG1xLpma3Jk6R12IzU-pttaoQlc_wKntbTzm--str7P4JoTqbAWK_vOCrV7dIm8Dw3rUD-sCNxax1DlqHXeXYcrHcbPr_ZG-kkEyiAQgkjHVHW3OPBba3M-ybTaQ8iSrnFm5IlBQAaIrHf3Z43om-q5qEobTVtJB_wzTVtCHfQyJe6ErBKVzTaj52ktJzfLb5v18ZmC1e32cXCI5qRZzslkMg823voBTYYq2m8GfXVblmo0dM3LjN3xqVkYCzr6sV1KyTqii1l6WDem8cKauebfL3da5hH1YX0kB3S3s8JH95Yrw_PIcQ-yjZenh4leSlL5GLTsx2EXLshhkdaPVjtbG_2tGo-Ml930B6tGP4y9h0aBP1TLYtZNb8udfW9NW0t2T1M6dLvxMdzcDMroZlOOlIexdFw6M4Ybgp-rKB-k7Y2fHb4hecCPk-Tbg_zUV3f7LNaH2_HtbLr99qwVnTxedF1u3DkvvgwjZpJr_SLQ_Uh6Zg-LZnFaqgnaNo8_3Nq_XZh-86yufGsepL68H-fDj6bFE6dcNhN0_rLDIuavLz7-Cz0ItdI=
|
@@ -1 +0,0 @@
|
||||
{"revoked": [{"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}, {"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}]}
|
@@ -1,20 +0,0 @@
|
||||
-----BEGIN CMS-----
|
||||
MIIDTwYJKoZIhvcNAQcCoIIDQDCCAzwCAQExCTAHBgUrDgMCGjCCAVwGCSqGSIb3
|
||||
DQEHAaCCAU0EggFJeyJyZXZva2VkIjogW3siZXhwaXJlcyI6ICIyMTEyLTA4LTE0
|
||||
VDE3OjU4OjQ4WiIsICJpZCI6ICJkYjk4ZWQyYWY2YzY3MDdiZWM2ZGM2YzY4OTI3
|
||||
ODlhMCJ9LCB7ImV4cGlyZXMiOiAiMjExMi0wOC0xNFQxNzo1ODo0OFoiLCAiaWQi
|
||||
OiAiMTVjZTA1ZmQ0OTFiNzk3OTEwNjhlZDgwYTljN2Y1ZTcifSwgeyJleHBpcmVz
|
||||
IjogIjIxMTItMDgtMTRUMTc6NTg6NDhaIiwgImlkIjogImRiOThlZDJhZjZjNjcw
|
||||
N2JlYzZkYzZjNjg5Mjc4OWEwIn0sIHsiZXhwaXJlcyI6ICIyMTEyLTA4LTE0VDE3
|
||||
OjU4OjQ4WiIsICJpZCI6ICIxNWNlMDVmZDQ5MWI3OTc5MTA2OGVkODBhOWM3ZjVl
|
||||
NyJ9XX0xggHKMIIBxgIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMx
|
||||
CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5T
|
||||
dGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25l
|
||||
QG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIa
|
||||
MA0GCSqGSIb3DQEBAQUABIIBAGn4kryxJudTZYMf32gKnoNHeAXRb97CoCXiTgs2
|
||||
gu/blX/fwMdrL8GLg2puYR07XBgjo56vMsD94ZIRyhcS1lFti9veQHt7Xp8kbR8l
|
||||
nbx9fsOhMxUHLRnxioieA9T1ykP8ZvYV3hYCeXkIYhPgD4lAAAmNq99ZxBRS3csE
|
||||
DP+Xz1+UYvT6Qm/NWRuj7WIjofneIB7gT6L5irsU0qtMCQeqI3dsP9GSsy4HJvBR
|
||||
BBIzQ7fEMRCGTADbk4ml+6Dx+Jm5SO80NvinzxCjO3DbkcEG1pQ3RGVEn3gyzg2a
|
||||
ssaRU4ycbYACA99K5UzCtSj8glGXFa1cnx42nSn2LbfJP1M=
|
||||
-----END CMS-----
|
@@ -1 +0,0 @@
|
||||
PKIZ_eJx9VElzszgUvPMr5p5KBbPY5vAdtGAsYokAYr0ZbIvdSXDM8usHO1Uzl6lRlQ6v1dVPr6vrvb4uB5oWYX8h6j-KV4kSgvmQ2O_XlBT3nAE3R9cFczFCYB4QcM0RcbCHIvjGgiKrWvBwsJD_ZfkkUyXsmntwXMBANoXY2efJntI4vR-VsCbVVURqX6ZxMRxju8knsiaITJSb04ED7cBNWQqxqTpVoDmVq0Ul6QmyP1P0INp1UtVaGrlTEiVKMicqxacyjaiSWvRRaw4nquTgpqDINg4IbkgbarnVLD-gpVOCklbmSEt5cJA8sp07svm6cvBVdnbX8oBAeYzcUnoSeVilHKzS1pUdvivZXKsONwdWFU2KxZDwpmJKskp5Xl78QSxjNuc9_MzbcJYec5KKjJSTG8XiRrkXUJ6vGRdrhosjKQdB2ubpB2m90uEPUbtIq7RiVT5ITLGbZE7r5S6A0GmVa05kDqSTe7L_fwMf_kn_bSAZWcQaisM2xa5OI7KMlOuUA8WxwtrBsHAiqqZV2Ehsso04lkch9u9LJuAoCAQcwU-MYFeZ7xQIC6wCE3oUMm4eKKh_68X6MKSjhGZgQ8FCCAQHNYPUI4MJEhy67t4cGn6K9J9znBaZFYxmBdxf7pWjwBjSSOfSydpVx9n0KNg-ldFIia-Eeq5696wNRpuDCor6q6hLyxhkiFwz2rW35hwzOVP0RnKtp9L8FJr0e97m4w4D_7cT5WjFmsxKRKA0XdaGNRCPZrkF_d4BAzlKFMj_5HqJNQRuAODiBbA6rf6eRvvnxNOEXlRFvHdXtj-D2MuMDbqiuOSiVyTx85Y18dtloKfvw9Y6COXzJ_HkTQxFddXXd9pjQ0uJNxW5v2p2t9K4n939bRN_buvM2zZSl43GpXcKOgb7g9eN5bU8A4Ovpvpjm96TUC0SdI5rkhQfAmsNAKBlX4aRjtDz1bw3JfzxEs-rlyC587XbvrHI-6k20ZK7S3cmcCP4-qCX330gf90ocs9fRD31H4bl95O2t-_QkyAks7u5mNRDFgc4q7W2eVnj8cVudd_ZyuxedvOIKkdd3nLTWn26qmeFZqeKaRbKUer7oxdoU54lAAHDeNeDGd38aisaK94dV3k3akrnd8ohu9gfK_pHeu4hk-F_d9Lf2fB_ww==
|
@@ -1,117 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from keystoneclient.common import cms
|
||||
from keystoneclient import utils
|
||||
|
||||
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def make_filename(*args):
|
||||
return os.path.join(CURRENT_DIR, *args)
|
||||
|
||||
|
||||
def generate_revocation_list():
|
||||
REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked']
|
||||
revoked_list = []
|
||||
for token in REVOKED_TOKENS:
|
||||
with open(make_filename('cms', '%s.pkiz' % name), 'r') as f:
|
||||
token_data = f.read()
|
||||
id = utils.hash_signed_token(token_data.encode('utf-8'))
|
||||
revoked_list.append({
|
||||
'id': id,
|
||||
"expires": "2112-08-14T17:58:48Z"
|
||||
})
|
||||
with open(make_filename('cms', '%s.pem' % name), 'r') as f:
|
||||
pem_data = f.read()
|
||||
token_data = cms.cms_to_token(pem_data).encode('utf-8')
|
||||
id = utils.hash_signed_token(token_data)
|
||||
revoked_list.append({
|
||||
'id': id,
|
||||
"expires": "2112-08-14T17:58:48Z"
|
||||
})
|
||||
revoked_json = json.dumps({"revoked": revoked_list})
|
||||
with open(make_filename('cms', 'revocation_list.json'), 'w') as f:
|
||||
f.write(revoked_json)
|
||||
encoded = cms.pkiz_sign(revoked_json,
|
||||
SIGNING_CERT_FILE_NAME,
|
||||
SIGNING_KEY_FILE_NAME)
|
||||
with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f:
|
||||
f.write(encoded)
|
||||
|
||||
encoded = cms.cms_sign_data(revoked_json,
|
||||
SIGNING_CERT_FILE_NAME,
|
||||
SIGNING_KEY_FILE_NAME)
|
||||
with open(make_filename('cms', 'revocation_list.pem'), 'w') as f:
|
||||
f.write(encoded)
|
||||
|
||||
|
||||
CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem')
|
||||
SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem')
|
||||
SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem')
|
||||
EXAMPLE_TOKENS = ['auth_token_revoked',
|
||||
'auth_token_unscoped',
|
||||
'auth_token_scoped',
|
||||
'auth_token_scoped_expired',
|
||||
'auth_v3_token_scoped',
|
||||
'auth_v3_token_revoked']
|
||||
|
||||
|
||||
# Helper script to generate the sample data for testing
|
||||
# the signed tokens using the existing JSON data for the
|
||||
# MII-prefixed tokens. Uses the keys and certificates
|
||||
# generated in gen_pki.sh.
|
||||
def generate_der_form(name):
|
||||
derfile = make_filename('cms', '%s.der' % name)
|
||||
with open(derfile, 'w') as f:
|
||||
derform = cms.cms_sign_data(text,
|
||||
SIGNING_CERT_FILE_NAME,
|
||||
SIGNING_KEY_FILE_NAME, cms.PKIZ_CMS_FORM)
|
||||
f.write(derform)
|
||||
|
||||
for name in EXAMPLE_TOKENS:
|
||||
json_file = make_filename('cms', name + '.json')
|
||||
pkiz_file = make_filename('cms', name + '.pkiz')
|
||||
with open(json_file, 'r') as f:
|
||||
string_data = f.read()
|
||||
|
||||
# validate the JSON
|
||||
try:
|
||||
token_data = json.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')
|
||||
|
||||
# Uncomment to record the token uncompressed,
|
||||
# useful for debugging
|
||||
# generate_der_form(name)
|
||||
|
||||
encoded = cms.pkiz_sign(text,
|
||||
SIGNING_CERT_FILE_NAME,
|
||||
SIGNING_KEY_FILE_NAME)
|
||||
|
||||
# verify before writing
|
||||
cms.pkiz_verify(encoded,
|
||||
SIGNING_CERT_FILE_NAME,
|
||||
CA_CERT_FILE_NAME)
|
||||
|
||||
with open(pkiz_file, 'w') as f:
|
||||
f.write(encoded)
|
||||
|
||||
generate_revocation_list()
|
@@ -1,208 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# These functions generate the certificates and signed tokens for the tests.
|
||||
|
||||
DIR=`dirname "$0"`
|
||||
CURRENT_DIR=`cd "$DIR" && pwd`
|
||||
CERTS_DIR=$CURRENT_DIR/certs
|
||||
PRIVATE_DIR=$CURRENT_DIR/private
|
||||
CMS_DIR=$CURRENT_DIR/cms
|
||||
|
||||
|
||||
function rm_old {
|
||||
rm -rf $CERTS_DIR/*.pem
|
||||
rm -rf $PRIVATE_DIR/*.pem
|
||||
}
|
||||
|
||||
function cleanup {
|
||||
rm -rf *.conf > /dev/null 2>&1
|
||||
rm -rf index* > /dev/null 2>&1
|
||||
rm -rf *.crt > /dev/null 2>&1
|
||||
rm -rf newcerts > /dev/null 2>&1
|
||||
rm -rf *.pem > /dev/null 2>&1
|
||||
rm -rf serial* > /dev/null 2>&1
|
||||
}
|
||||
|
||||
function generate_ca_conf {
|
||||
echo '
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_keyfile = cakey.pem
|
||||
default_md = default
|
||||
|
||||
prompt = no
|
||||
distinguished_name = ca_distinguished_name
|
||||
|
||||
x509_extensions = ca_extensions
|
||||
|
||||
[ ca_distinguished_name ]
|
||||
serialNumber = 5
|
||||
countryName = US
|
||||
stateOrProvinceName = CA
|
||||
localityName = Sunnyvale
|
||||
organizationName = OpenStack
|
||||
organizationalUnitName = Keystone
|
||||
emailAddress = keystone@openstack.org
|
||||
commonName = Self Signed
|
||||
|
||||
[ ca_extensions ]
|
||||
basicConstraints = critical,CA:true
|
||||
' > ca.conf
|
||||
}
|
||||
|
||||
function generate_ssl_req_conf {
|
||||
echo '
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_keyfile = keystonekey.pem
|
||||
default_md = default
|
||||
|
||||
prompt = no
|
||||
distinguished_name = distinguished_name
|
||||
|
||||
[ distinguished_name ]
|
||||
countryName = US
|
||||
stateOrProvinceName = CA
|
||||
localityName = Sunnyvale
|
||||
organizationName = OpenStack
|
||||
organizationalUnitName = Keystone
|
||||
commonName = localhost
|
||||
emailAddress = keystone@openstack.org
|
||||
' > ssl_req.conf
|
||||
}
|
||||
|
||||
function generate_cms_signing_req_conf {
|
||||
echo '
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_keyfile = keystonekey.pem
|
||||
default_md = default
|
||||
|
||||
prompt = no
|
||||
distinguished_name = distinguished_name
|
||||
|
||||
[ distinguished_name ]
|
||||
countryName = US
|
||||
stateOrProvinceName = CA
|
||||
localityName = Sunnyvale
|
||||
organizationName = OpenStack
|
||||
organizationalUnitName = Keystone
|
||||
commonName = Keystone
|
||||
emailAddress = keystone@openstack.org
|
||||
' > cms_signing_req.conf
|
||||
}
|
||||
|
||||
function generate_signing_conf {
|
||||
echo '
|
||||
[ ca ]
|
||||
default_ca = signing_ca
|
||||
|
||||
[ signing_ca ]
|
||||
dir = .
|
||||
database = $dir/index.txt
|
||||
new_certs_dir = $dir/newcerts
|
||||
|
||||
certificate = $dir/certs/cacert.pem
|
||||
serial = $dir/serial
|
||||
private_key = $dir/private/cakey.pem
|
||||
|
||||
default_days = 21360
|
||||
default_crl_days = 30
|
||||
default_md = default
|
||||
|
||||
policy = policy_any
|
||||
|
||||
[ policy_any ]
|
||||
countryName = supplied
|
||||
stateOrProvinceName = supplied
|
||||
localityName = optional
|
||||
organizationName = supplied
|
||||
organizationalUnitName = supplied
|
||||
emailAddress = supplied
|
||||
commonName = supplied
|
||||
' > signing.conf
|
||||
}
|
||||
|
||||
function setup {
|
||||
touch index.txt
|
||||
echo '10' > serial
|
||||
generate_ca_conf
|
||||
mkdir newcerts
|
||||
}
|
||||
|
||||
function check_error {
|
||||
if [ $1 != 0 ] ; then
|
||||
echo "Failed! rc=${1}"
|
||||
echo 'Bailing ...'
|
||||
cleanup
|
||||
exit $1
|
||||
else
|
||||
echo 'Done'
|
||||
fi
|
||||
}
|
||||
|
||||
function generate_ca {
|
||||
echo 'Generating New CA Certificate ...'
|
||||
openssl req -x509 -newkey rsa:2048 -days 21360 -out $CERTS_DIR/cacert.pem -keyout $PRIVATE_DIR/cakey.pem -outform PEM -config ca.conf -nodes
|
||||
check_error $?
|
||||
}
|
||||
|
||||
function ssl_cert_req {
|
||||
echo 'Generating SSL Certificate Request ...'
|
||||
generate_ssl_req_conf
|
||||
openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/ssl_key.pem -keyform PEM -out ssl_req.pem -outform PEM -config ssl_req.conf -nodes
|
||||
check_error $?
|
||||
#openssl req -in req.pem -text -noout
|
||||
}
|
||||
|
||||
function cms_signing_cert_req {
|
||||
echo 'Generating CMS Signing Certificate Request ...'
|
||||
generate_cms_signing_req_conf
|
||||
openssl req -newkey rsa:2048 -keyout $PRIVATE_DIR/signing_key.pem -keyform PEM -out cms_signing_req.pem -outform PEM -config cms_signing_req.conf -nodes
|
||||
check_error $?
|
||||
#openssl req -in req.pem -text -noout
|
||||
}
|
||||
|
||||
function issue_certs {
|
||||
generate_signing_conf
|
||||
echo 'Issuing SSL Certificate ...'
|
||||
openssl ca -in ssl_req.pem -config signing.conf -batch
|
||||
check_error $?
|
||||
openssl x509 -in $CURRENT_DIR/newcerts/10.pem -out $CERTS_DIR/ssl_cert.pem
|
||||
check_error $?
|
||||
echo 'Issuing CMS Signing Certificate ...'
|
||||
openssl ca -in cms_signing_req.pem -config signing.conf -batch
|
||||
check_error $?
|
||||
openssl x509 -in $CURRENT_DIR/newcerts/11.pem -out $CERTS_DIR/signing_cert.pem
|
||||
check_error $?
|
||||
}
|
||||
|
||||
function check_openssl {
|
||||
echo 'Checking openssl availability ...'
|
||||
which openssl
|
||||
check_error $?
|
||||
}
|
||||
|
||||
JSON_FILES="${CMS_DIR}/auth_token_revoked.json ${CMS_DIR}/auth_token_unscoped.json ${CMS_DIR}/auth_token_scoped.json ${CMS_DIR}/auth_token_scoped_expired.json ${CMS_DIR}/revocation_list.json ${CMS_DIR}/auth_v3_token_scoped.json ${CMS_DIR}/auth_v3_token_revoked.json"
|
||||
|
||||
function gen_sample_cms {
|
||||
for json_file in $JSON_FILES
|
||||
do
|
||||
openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem}
|
||||
done
|
||||
}
|
||||
|
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCl8906EaRpibQF
|
||||
cCBWfxzLi5x/XpZ9iL6UX92NrSJxcDbaGws7s+GtjgDy8UOEonesRWTeqQEZtHpC
|
||||
3/UHHOnsA8F6ha/pq9LioqT7RehCnZCLBJwh5Ct+lclpWs15SkjJD2LTDkjox0eA
|
||||
9nOBx+XDlWyU/GAyqx5Wsvg/Kxr0iod9/4IcJdnSdUjq4v0Cxg/zNk08XPJX+F0b
|
||||
UDhgdUf7JrAmmS5LA8wphRnbIgtVsf6VN9HrbqtHAJDxh8gEfuwdhEW1df1fBtZ+
|
||||
6WMIF3IRSbIsZELFB6sqcyRj7HhMoWMkdEyPb2f8mq61MzTgE6lJGIyTRvEoFie7
|
||||
qtGADIofAgMBAAECggEBAJ47X3y2xaU7f0KQHsVafgI2JAnuDl+zusOOhJlJs8Wl
|
||||
0Sc1EgjjAxOQiqcaE96rap//qqYDTuFLjCenkuItV32KNzizr3+GLZWaruRHS6X4
|
||||
xpFG2/gUrsQL3fdudOxpP+01lmzW+f25xRvZ4VilWRabquSDntWxA0R3cOwKFbGD
|
||||
uuwbTw3pBrRfCk/2IdpQtRrvvkVIFiYT6b/zeCQzhp4RETbC0oxqcEEOIUGmimAV
|
||||
9cbwafinxCo54cOfX4JAh3j7Mp3eQUymoFk5gnmIeVe0QmpH2VkN7eItrhEvHKOk
|
||||
On7a5xvQ8s3wqPV5ZawHQcqar/p3QnGkiT6a+8LkIMECgYEA2iJ2DprTGZFRN0M7
|
||||
Yj4WLsSC3/GKK8eYsKG3TvMrmPqUDaiWLIvBoc1Le59x9eoF7Mha+WX+cAFL+GTg
|
||||
1sB+PUZZStpf1R1tGvMldvpQ+5GplUBpuQe4J0n5rCG6+5jkvSr7xO+G1B+C3GFq
|
||||
KR3iltiW5WJRVwh2k8yGvx3agyUCgYEAwsKFX82F7O+9IVud1JSQWmZMiyEK+DEX
|
||||
JRnwx4HBuWr+AZqbb0grRRb6x8JTUOD4T7DZGxTaAdfzzRjKU2sBAO8VCgaj2Auv
|
||||
5nsbvfXvrmDDCqwoaD2PMy+kgFvE0QTh65tzuGXl1IgpIYSC1JwnP6kOeUDbqE+k
|
||||
UXzfVZzDdvMCgYByk9dfJIPt0h7O4Em4+NO+DQqRhtYE2PqjDM60cZZc7IIICp2X
|
||||
GHHFA4i6jq3Vde9WyIbAqYpUWtoExzgylTm6BdGxN7NOxf4hQcZUEHepLIHfG85s
|
||||
mlloibrTZ4RH06+SjZlhgE9Z7JNYHvMcVc5HXc0k/9ep15AxYiUFDjFQ4QKBgG7i
|
||||
k089U4/X2wWgBNdgkmN1tQTNllJCmNvdzhG41dQ8j0vYe8C7BS+76qJLCGaW/6lX
|
||||
lfRuRcUg78UI5UDjPloKxR7FMwmxdb+yvdPEr2bH3qQ36nWW/u30pSMTnJYownwD
|
||||
MLp/AYCk2U4lBNwJ3+rF1ODCRY2pcnOWtg0nSL5zAoGAWRoOinogEnOodJzO7eB3
|
||||
TmL6M9QMyrAPBDsCnduJ8yW5mMUNod139YbSDxZPYwTLhK/GiHP/7OvLV5hg0s4s
|
||||
QKnNaMeEowX7dyEO4ehnbfzysxXPKLRVhWhN6MCUc71NMxqr7QkuCXAjJS6/G21+
|
||||
Im3+Xb3Scq+UZghR+jiEZF0=
|
||||
-----END PRIVATE KEY-----
|
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDM+VrILLl962VH
|
||||
S8EKWVzdkaOy0OoxGZ63gajM7VTm8AbgtVnYibIOnVZQuz1XbftIGNXPFhYNUypr
|
||||
LnMXrEEsnxgD4PvU/4bETG+stdricX6d1oKqsNFNR7F7zImiR/OzGhp7dONwccxf
|
||||
kfX4QHA5Ogso+XMfSdC72SRDszeCeGUcjuo/w2WSLW95SuVvcZLqE/pk3Q2TkCZ1
|
||||
8hvNfLoln43QpC469a7srUXATqOJ2mPNvL6E/wOyPefmAoCoG44lFoR3k2jZjBEI
|
||||
hstJxmH7XgvqErBzpcWd29dms8xz5PNwYdns9CIfb3GaHvQ6r5RTl37/avDrGHOW
|
||||
KOoD01xLAgMBAAECggEAaIi22qWsh+JYCW9B6NRAPyN6V8Sh2x6UykOO4cwb45b/
|
||||
+vOh+YPn0fo9vfhvxTnq0A8SY4WBA5SpanYK7kTEDEyqw7em1y7l/RB6V5t7IMb+
|
||||
6uIuS3zXkVEB3AApJSEK0Ql7/gBTydHPh+H5jnzWfujyLhhhtNBBarvH+drZcWio
|
||||
lWx8RERN4cH+3DZD/xxjH2Ff+X1XMvb8Xcup7MlWi2FtREg7LttLNWNK25iWjciP
|
||||
QwfWQIrURRJrD2IrOr9V2nuIEvRqRRBoO+pxJT2sC48NJ3hiKV2GtSQe2nRpQJ47
|
||||
f9MEsF5KVQOOn+aQ60EKOI0MpNPmpiCZ5hFvBrNuOQKBgQD6vueEdI9eJgz5YN+t
|
||||
XWdpNippv35RTD8R4bQcE6GqIUXOmtQFS2wPJLn7nisZUsGMNEs36Yl0T9iow63r
|
||||
5GNAfgzpqN1XZqaSMwAdxKmlBNYpAkVXHhv+1jN+9diDYmoj9T+3Q6Zvk5e/Liyp
|
||||
6i+TsDppwmmr2utWajhyJ7owFwKBgQDRROncTztGDYLfRcrIoYsPo79KQ8tqwd2a
|
||||
07Usch2kplTqojCUmmhMMFgV2eZPPiCjnEy2bAYh9I/oj7xG6EwApXTshZdCpivC
|
||||
rbUV64MakRTUP8IvM6PdI+apkJRsRUi/bSyIbcRlvEoCMNZhfj/5VY6w/jlwrPJj
|
||||
oBOCXBlB7QKBgQDGEbEeX1i03UfYYh6uep7qbEAaooqsu5cCkBDPMO6+TmQvLPyY
|
||||
Zhio6bEEQs/2w/lhwBk+xHqw5zXVMiWbtiB03F1k4eBeXxbrW+AWo7gCQ4zMfh+6
|
||||
Dm284wVwn9D1D/OaDevT31uEvcjb2ySq3/PPLSEnU8xXVaoa6/NEsX8Q5wKBgQCm
|
||||
2smULWBXZKJ6n00mVxdnqun0rsVcI6Mrta14+KwGAdEnG5achdivFsTE924YtLKV
|
||||
gSPxN4RUQokTprc52jHvOf1WMNYAADpYCOSfy55G6nKvIP8VX5lB00Qw4uRUx5FP
|
||||
gB7H0K2NaGmiAYqNRXqAtOUG3kyyOFMzeAjWIdTJqQKBgQCHzY1c7sS1vv7mPEkr
|
||||
6CpwoaEbZeFnWoHBA8Rd82psqfYsVJIRwk5Id8zgDSEmoEi8hQ9UrYbrFpLK77xq
|
||||
EYSxLQHTNlM0G3lyEsv/gJhwYYhdTYiW3Cx3F6Y++jyn9O/+hFMyQvuesAL7DUYE
|
||||
ptEfvzFprpQUpByXkIpuJub6fg==
|
||||
-----END PRIVATE KEY-----
|
@@ -1,28 +0,0 @@
|
||||
-----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-----
|
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# This script generates the crypto necessary for the SSL tests.
|
||||
|
||||
. gen_pki.sh
|
||||
|
||||
check_openssl
|
||||
rm_old
|
||||
cleanup
|
||||
setup
|
||||
generate_ca
|
||||
ssl_cert_req
|
||||
cms_signing_cert_req
|
||||
issue_certs
|
||||
gen_sample_cms
|
||||
cleanup
|
@@ -1,78 +0,0 @@
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""The python bindings for the OpenStack Identity (Keystone) project.
|
||||
|
||||
A Client object will allow you to communicate with the Identity server. The
|
||||
recommended way to get a Client object is to use
|
||||
:py:func:`keystoneclient.client.Client()`. :py:func:`~.Client()` uses version
|
||||
discovery to create a V3 or V2 client depending on what versions the Identity
|
||||
server supports and what version is requested.
|
||||
|
||||
Identity V2 and V3 clients can also be created directly. See
|
||||
:py:class:`keystoneclient.v3.client.Client` for the V3 client and
|
||||
:py:class:`keystoneclient.v2_0.client.Client` for the V2 client.
|
||||
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo('python-keystoneclient').version_string()
|
||||
|
||||
__all__ = (
|
||||
# Modules
|
||||
'generic',
|
||||
'v2_0',
|
||||
'v3',
|
||||
|
||||
# Packages
|
||||
'access',
|
||||
'client',
|
||||
'exceptions',
|
||||
'httpclient',
|
||||
'service_catalog',
|
||||
)
|
||||
|
||||
|
||||
class _LazyImporter(object):
|
||||
def __init__(self, module):
|
||||
self._module = module
|
||||
|
||||
def __getattr__(self, name):
|
||||
# NB: this is only called until the import has been done.
|
||||
# These submodules are part of the API without explicit importing, but
|
||||
# expensive to load, so we load them on-demand rather than up-front.
|
||||
lazy_submodules = [
|
||||
'access',
|
||||
'client',
|
||||
'exceptions',
|
||||
'generic',
|
||||
'httpclient',
|
||||
'service_catalog',
|
||||
'v2_0',
|
||||
'v3',
|
||||
]
|
||||
if name in lazy_submodules:
|
||||
return importlib.import_module('keystoneclient.%s' % name)
|
||||
|
||||
# Return module attributes like __all__ etc.
|
||||
return getattr(self._module, name)
|
||||
|
||||
|
||||
sys.modules[__name__] = _LazyImporter(sys.modules[__name__])
|
@@ -1,333 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""The passive components to version discovery.
|
||||
|
||||
The Discover object in discover.py contains functions that can create objects
|
||||
on your behalf. These functions are not usable from within the keystoneclient
|
||||
library because you will get dependency resolution issues.
|
||||
|
||||
The Discover object in this file provides the querying components of Discovery.
|
||||
This includes functions like url_for which allow you to retrieve URLs and the
|
||||
raw data specified in version discovery responses.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient import exceptions
|
||||
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'}
|
||||
|
||||
resp = session.get(url, headers=headers, authenticated=authenticated)
|
||||
|
||||
try:
|
||||
body_resp = resp.json()
|
||||
except ValueError: # nosec(cjschaef): raise a DiscoveryFailure below
|
||||
pass
|
||||
else:
|
||||
# In the event of querying a root URL we will get back a list of
|
||||
# available versions.
|
||||
try:
|
||||
return body_resp['versions']['values']
|
||||
except (KeyError, TypeError): # nosec(cjschaef): attempt to return
|
||||
# versions dict or query the endpoint or raise a DiscoveryFailure
|
||||
pass
|
||||
|
||||
# Most servers don't have a 'values' element so accept a simple
|
||||
# versions dict if available.
|
||||
try:
|
||||
return body_resp['versions']
|
||||
except KeyError: # nosec(cjschaef): query the endpoint or raise a
|
||||
# DiscoveryFailure
|
||||
pass
|
||||
|
||||
# Otherwise if we query an endpoint like /v2.0 then we will get back
|
||||
# just the one available version.
|
||||
try:
|
||||
return [body_resp['version']]
|
||||
except KeyError: # nosec(cjschaef): raise a DiscoveryFailure
|
||||
pass
|
||||
|
||||
err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text
|
||||
msg = _('Invalid Response - Bad version data returned: %s') % err_text
|
||||
raise exceptions.DiscoveryFailure(msg)
|
||||
|
||||
|
||||
def normalize_version_number(version):
|
||||
"""Turn a version representation into a tuple."""
|
||||
# trim the v from a 'v2.0' or similar
|
||||
try:
|
||||
version = version.lstrip('v')
|
||||
except AttributeError: # nosec(cjschaef): 'version' is not a str, try a
|
||||
# different type or raise a TypeError
|
||||
pass
|
||||
|
||||
# if it's an integer or a numeric as a string then normalize it
|
||||
# to a string, this ensures 1 decimal point
|
||||
try:
|
||||
num = float(version)
|
||||
except Exception: # nosec(cjschaef): 'version' is not a float, try a
|
||||
# different type or raise a TypeError
|
||||
pass
|
||||
else:
|
||||
version = str(num)
|
||||
|
||||
# if it's a string (or an integer) from above break it on .
|
||||
try:
|
||||
return tuple(map(int, version.split('.')))
|
||||
except Exception: # nosec(cjschaef): 'version' is not str (or an int),
|
||||
# try a different type or raise a TypeError
|
||||
pass
|
||||
|
||||
# last attempt, maybe it's a list or iterable.
|
||||
try:
|
||||
return tuple(map(int, version))
|
||||
except Exception: # nosec(cjschaef): 'version' is not an expected type,
|
||||
# raise a TypeError
|
||||
pass
|
||||
|
||||
raise TypeError(_('Invalid version specified: %s') % version)
|
||||
|
||||
|
||||
def version_match(required, candidate):
|
||||
"""Test that an available version satisfies the required version.
|
||||
|
||||
To be suitable a version must be of the same major version as required
|
||||
and be at least a match in minor/patch level.
|
||||
|
||||
eg. 3.3 is a match for a required 3.1 but 4.1 is not.
|
||||
|
||||
:param tuple required: the version that must be met.
|
||||
:param tuple candidate: the version to test against required.
|
||||
|
||||
:returns: True if candidate is suitable False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
# major versions must be the same (e.g. even though v2 is a lower
|
||||
# version than v3 we can't use it if v2 was requested)
|
||||
if candidate[0] != required[0]:
|
||||
return False
|
||||
|
||||
# prevent selecting a minor version less than what is required
|
||||
if candidate < required:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Discover(object):
|
||||
|
||||
CURRENT_STATUSES = ('stable', 'current', 'supported')
|
||||
DEPRECATED_STATUSES = ('deprecated',)
|
||||
EXPERIMENTAL_STATUSES = ('experimental',)
|
||||
|
||||
@positional()
|
||||
def __init__(self, session, url, authenticated=None):
|
||||
self._data = get_version_data(session, url,
|
||||
authenticated=authenticated)
|
||||
|
||||
def raw_version_data(self, allow_experimental=False,
|
||||
allow_deprecated=True, allow_unknown=False):
|
||||
"""Get raw version information from URL.
|
||||
|
||||
Raw data indicates that only minimal validation processing is performed
|
||||
on the data, so what is returned here will be the data in the same
|
||||
format it was received from the endpoint.
|
||||
|
||||
:param bool allow_experimental: Allow experimental version endpoints.
|
||||
:param bool allow_deprecated: Allow deprecated version endpoints.
|
||||
:param bool allow_unknown: Allow endpoints with an unrecognised status.
|
||||
|
||||
:returns: The endpoints returned from the server that match the
|
||||
criteria.
|
||||
:rtype: list
|
||||
"""
|
||||
versions = []
|
||||
for v in self._data:
|
||||
try:
|
||||
status = v['status']
|
||||
except KeyError:
|
||||
_LOGGER.warning('Skipping over invalid version data. '
|
||||
'No stability status in version.')
|
||||
continue
|
||||
|
||||
status = status.lower()
|
||||
|
||||
if status in self.CURRENT_STATUSES:
|
||||
versions.append(v)
|
||||
elif status in self.DEPRECATED_STATUSES:
|
||||
if allow_deprecated:
|
||||
versions.append(v)
|
||||
elif status in self.EXPERIMENTAL_STATUSES:
|
||||
if allow_experimental:
|
||||
versions.append(v)
|
||||
elif allow_unknown:
|
||||
versions.append(v)
|
||||
|
||||
return versions
|
||||
|
||||
def version_data(self, **kwargs):
|
||||
"""Get normalized version data.
|
||||
|
||||
Return version data in a structured way.
|
||||
|
||||
:returns: A list of version data dictionaries sorted by version number.
|
||||
Each data element in the returned list is a dictionary
|
||||
consisting of at least:
|
||||
|
||||
:version tuple: The normalized version of the endpoint.
|
||||
:url str: The url for the endpoint.
|
||||
:raw_status str: The status as provided by the server
|
||||
:rtype: list(dict)
|
||||
"""
|
||||
if kwargs.pop('unstable', None):
|
||||
kwargs.setdefault('allow_experimental', True)
|
||||
kwargs.setdefault('allow_unknown', True)
|
||||
data = self.raw_version_data(**kwargs)
|
||||
versions = []
|
||||
|
||||
for v in data:
|
||||
try:
|
||||
version_str = v['id']
|
||||
except KeyError:
|
||||
_LOGGER.info('Skipping invalid version data. Missing ID.')
|
||||
continue
|
||||
|
||||
try:
|
||||
links = v['links']
|
||||
except KeyError:
|
||||
_LOGGER.info('Skipping invalid version data. Missing links')
|
||||
continue
|
||||
|
||||
version_number = normalize_version_number(version_str)
|
||||
|
||||
for link in links:
|
||||
try:
|
||||
rel = link['rel']
|
||||
url = link['href']
|
||||
except (KeyError, TypeError):
|
||||
_LOGGER.info('Skipping invalid version link. '
|
||||
'Missing link URL or relationship.')
|
||||
continue
|
||||
|
||||
if rel.lower() == 'self':
|
||||
break
|
||||
else:
|
||||
_LOGGER.info('Skipping invalid version data. '
|
||||
'Missing link to endpoint.')
|
||||
continue
|
||||
|
||||
versions.append({'version': version_number,
|
||||
'url': url,
|
||||
'raw_status': v['status']})
|
||||
|
||||
versions.sort(key=lambda v: v['version'])
|
||||
return versions
|
||||
|
||||
def data_for(self, version, **kwargs):
|
||||
"""Return endpoint data for a version.
|
||||
|
||||
:param tuple version: The version is always a minimum version in the
|
||||
same major release as there should be no compatibility issues with
|
||||
using a version newer than the one asked for.
|
||||
|
||||
:returns: the endpoint data for a URL that matches the required version
|
||||
(the format is described in version_data) or None if no
|
||||
match.
|
||||
:rtype: dict
|
||||
"""
|
||||
version = normalize_version_number(version)
|
||||
version_data = self.version_data(**kwargs)
|
||||
|
||||
for data in reversed(version_data):
|
||||
if version_match(version, data['version']):
|
||||
return data
|
||||
|
||||
return None
|
||||
|
||||
def url_for(self, version, **kwargs):
|
||||
"""Get the endpoint url for a version.
|
||||
|
||||
:param tuple version: The version is always a minimum version in the
|
||||
same major release as there should be no compatibility issues with
|
||||
using a version newer than the one asked for.
|
||||
|
||||
:returns: The url for the specified version or None if no match.
|
||||
:rtype: str
|
||||
"""
|
||||
data = self.data_for(version, **kwargs)
|
||||
return data['url'] if data else None
|
||||
|
||||
|
||||
class _VersionHacks(object):
|
||||
"""A container to abstract the list of version hacks.
|
||||
|
||||
This could be done as simply a dictionary but is abstracted like this to
|
||||
make for easier testing.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._discovery_data = {}
|
||||
|
||||
def add_discover_hack(self, service_type, old, new=''):
|
||||
"""Add a new hack for a service type.
|
||||
|
||||
:param str service_type: The service_type in the catalog.
|
||||
:param re.RegexObject old: The pattern to use.
|
||||
:param str new: What to replace the pattern with.
|
||||
"""
|
||||
hacks = self._discovery_data.setdefault(service_type, [])
|
||||
hacks.append((old, new))
|
||||
|
||||
def get_discover_hack(self, service_type, url):
|
||||
"""Apply the catalog hacks and figure out an unversioned endpoint.
|
||||
|
||||
:param str service_type: the service_type to look up.
|
||||
:param str url: The original url that came from a service_catalog.
|
||||
|
||||
:returns: Either the unversioned url or the one from the catalog
|
||||
to try.
|
||||
"""
|
||||
for old, new in self._discovery_data.get(service_type, []):
|
||||
new_string, number_of_subs_made = old.subn(new, url)
|
||||
if number_of_subs_made > 0:
|
||||
return new_string
|
||||
|
||||
return url
|
||||
|
||||
|
||||
_VERSION_HACKS = _VersionHacks()
|
||||
_VERSION_HACKS.add_discover_hack('identity', re.compile('/v2.0/?$'), '/')
|
||||
|
||||
|
||||
def get_catalog_discover_hack(service_type, url):
|
||||
"""Apply the catalog hacks and figure out an unversioned endpoint.
|
||||
|
||||
This function is internal to keystoneclient.
|
||||
|
||||
:param str service_type: the service_type to look up.
|
||||
:param str url: The original url that came from a service_catalog.
|
||||
|
||||
:returns: Either the unversioned url or the one from the catalog to try.
|
||||
"""
|
||||
return _VERSION_HACKS.get_discover_hack(service_type, url)
|
@@ -1,886 +0,0 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import datetime
|
||||
import warnings
|
||||
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient import service_catalog
|
||||
|
||||
|
||||
# gap, in seconds, to determine whether the given token is about to expire
|
||||
STALE_TOKEN_DURATION = 30
|
||||
|
||||
|
||||
class AccessInfo(dict):
|
||||
"""Encapsulates a raw authentication token from keystone.
|
||||
|
||||
Provides helper methods for extracting useful values from that token.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, resp=None, body=None, region_name=None, auth_token=None,
|
||||
**kwargs):
|
||||
"""Factory function to create a new AccessInfo object.
|
||||
|
||||
Create AccessInfo object given a successful auth response & body
|
||||
or a user-provided dict.
|
||||
|
||||
.. warning::
|
||||
|
||||
Use of the region_name argument is deprecated as of the 1.7.0
|
||||
release and may be removed in the 2.0.0 release.
|
||||
|
||||
"""
|
||||
if region_name:
|
||||
warnings.warn(
|
||||
'Use of the region_name argument is deprecated as of the '
|
||||
'1.7.0 release and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
|
||||
if body is not None or len(kwargs):
|
||||
if AccessInfoV3.is_valid(body, **kwargs):
|
||||
if resp and not auth_token:
|
||||
auth_token = resp.headers['X-Subject-Token']
|
||||
# NOTE(jamielennox): these return AccessInfo because they
|
||||
# already have auth_token installed on them.
|
||||
if body:
|
||||
if region_name:
|
||||
body['token']['region_name'] = region_name
|
||||
return AccessInfoV3(auth_token, **body['token'])
|
||||
else:
|
||||
return AccessInfoV3(auth_token, **kwargs)
|
||||
elif AccessInfoV2.is_valid(body, **kwargs):
|
||||
if body:
|
||||
if region_name:
|
||||
body['access']['region_name'] = region_name
|
||||
auth_ref = AccessInfoV2(**body['access'])
|
||||
else:
|
||||
auth_ref = AccessInfoV2(**kwargs)
|
||||
else:
|
||||
raise NotImplementedError(_('Unrecognized auth response'))
|
||||
else:
|
||||
auth_ref = AccessInfoV2(**kwargs)
|
||||
|
||||
if auth_token:
|
||||
auth_ref.auth_token = auth_token
|
||||
|
||||
return auth_ref
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AccessInfo, self).__init__(*args, **kwargs)
|
||||
self.service_catalog = service_catalog.ServiceCatalog.factory(
|
||||
resource_dict=self, region_name=self._region_name)
|
||||
|
||||
@property
|
||||
def _region_name(self):
|
||||
return self.get('region_name')
|
||||
|
||||
def will_expire_soon(self, stale_duration=None):
|
||||
"""Determine if expiration is about to occur.
|
||||
|
||||
:returns: true if expiration is within the given duration
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
stale_duration = (STALE_TOKEN_DURATION if stale_duration is None
|
||||
else stale_duration)
|
||||
norm_expires = timeutils.normalize_time(self.expires)
|
||||
# (gyee) should we move auth_token.will_expire_soon() to timeutils
|
||||
# instead of duplicating code here?
|
||||
soon = (timeutils.utcnow() + datetime.timedelta(
|
||||
seconds=stale_duration))
|
||||
return norm_expires < soon
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
"""Determine if processing valid v2 or v3 token.
|
||||
|
||||
Validates from the auth body or a user-provided dict.
|
||||
|
||||
:returns: true if auth body matches implementing class
|
||||
:rtype: boolean
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_service_catalog(self):
|
||||
"""Return true if the authorization token has a service catalog.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
"""Return the token_id associated with the auth request.
|
||||
|
||||
To be used in headers for authenticating OpenStack API requests.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
return self['auth_token']
|
||||
|
||||
@auth_token.setter
|
||||
def auth_token(self, value):
|
||||
self['auth_token'] = value
|
||||
|
||||
@auth_token.deleter
|
||||
def auth_token(self):
|
||||
try:
|
||||
del self['auth_token']
|
||||
except KeyError: # nosec(cjschaef): 'auth_token' is not in the dict
|
||||
pass
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
"""Return the token expiration (as datetime object).
|
||||
|
||||
:returns: datetime
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def issued(self):
|
||||
"""Return the token issue time (as datetime object).
|
||||
|
||||
:returns: datetime
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
"""Return the username associated with the auth request.
|
||||
|
||||
Follows the pattern defined in the V2 API of first looking for 'name',
|
||||
returning that if available, and falling back to 'username' if name
|
||||
is unavailable.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Return the user id associated with the auth request.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
"""Return the user's domain id associated with the auth request.
|
||||
|
||||
For v2, it always returns 'default' which may be different from the
|
||||
Keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
"""Return the user's domain name associated with the auth request.
|
||||
|
||||
For v2, it always returns 'Default' which may be different from the
|
||||
Keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def role_ids(self):
|
||||
"""Return a list of user's role ids associated with the auth request.
|
||||
|
||||
:returns: a list of strings of role ids
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
"""Return a list of user's role names associated with the auth request.
|
||||
|
||||
:returns: a list of strings of role names
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
"""Return the domain name associated with the auth request.
|
||||
|
||||
:returns: str or None (if no domain associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
"""Return the domain id associated with the auth request.
|
||||
|
||||
:returns: str or None (if no domain associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
"""Return the project name associated with the auth request.
|
||||
|
||||
:returns: str or None (if no project associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def tenant_name(self):
|
||||
"""Synonym for project_name."""
|
||||
return self.project_name
|
||||
|
||||
@property
|
||||
def scoped(self):
|
||||
"""Return true if the auth token was scoped.
|
||||
|
||||
Return true if scoped to a tenant(project) or domain,
|
||||
and contains a populated service catalog.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is deprecated as of the 1.7.0 release in favor of
|
||||
project_scoped and may be removed in the 2.0.0 release.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
"""Return true if the auth token was scoped to a tenant(project).
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
"""Return true if the auth token was scoped to a domain.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
"""Return the trust id associated with the auth request.
|
||||
|
||||
:returns: str or None (if no trust associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
"""Return true if the auth token was scoped from a delegated trust.
|
||||
|
||||
The trust delegation is via the OS-TRUST v3 extension.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trustee_user_id(self):
|
||||
"""Return the trustee user id associated with a trust.
|
||||
|
||||
:returns: str or None (if no trust associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trustor_user_id(self):
|
||||
"""Return the trustor user id associated with a trust.
|
||||
|
||||
:returns: str or None (if no trust associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
"""Return the project ID associated with the auth request.
|
||||
|
||||
This returns None if the auth token wasn't scoped to a project.
|
||||
|
||||
:returns: str or None (if no project associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def tenant_id(self):
|
||||
"""Synonym for project_id."""
|
||||
return self.project_id
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
"""Return the project's domain id associated with the auth request.
|
||||
|
||||
For v2, it returns 'default' if a project is scoped or None which may
|
||||
be different from the keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
"""Return the project's domain name associated with the auth request.
|
||||
|
||||
For v2, it returns 'Default' if a project is scoped or None which may
|
||||
be different from the keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def auth_url(self):
|
||||
"""Return a tuple of identity URLs.
|
||||
|
||||
The identity URLs are from publicURL and adminURL for the service
|
||||
'identity' from the service catalog associated with the authorization
|
||||
request. If the authentication request wasn't scoped to a tenant
|
||||
(project), this property will return None.
|
||||
|
||||
DEPRECATED: this doesn't correctly handle region name. You should fetch
|
||||
it from the service catalog yourself. This may be removed in the 2.0.0
|
||||
release.
|
||||
|
||||
:returns: tuple of urls
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def management_url(self):
|
||||
"""Return the first adminURL of the identity endpoint.
|
||||
|
||||
The identity endpoint is from the service catalog
|
||||
associated with the authorization request, or None if the
|
||||
authentication request wasn't scoped to a tenant (project).
|
||||
|
||||
DEPRECATED: this doesn't correctly handle region name. You should fetch
|
||||
it from the service catalog yourself. This may be removed in the 2.0.0
|
||||
release.
|
||||
|
||||
:returns: tuple of urls
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Return the version of the auth token from identity service.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
return self.get('version')
|
||||
|
||||
@property
|
||||
def oauth_access_token_id(self):
|
||||
"""Return the access token ID if OAuth authentication used.
|
||||
|
||||
:returns: str or None.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def oauth_consumer_id(self):
|
||||
"""Return the consumer ID if OAuth authentication used.
|
||||
|
||||
:returns: str or None.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def is_federated(self):
|
||||
"""Return true if federation was used to get the token.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def audit_id(self):
|
||||
"""Return the audit ID if present.
|
||||
|
||||
:returns: str or None.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def audit_chain_id(self):
|
||||
"""Return the audit chain ID if present.
|
||||
|
||||
In the event that a token was rescoped then this ID will be the
|
||||
:py:attr:`audit_id` of the initial token. Returns None if no value
|
||||
present.
|
||||
|
||||
:returns: str or None.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def initial_audit_id(self):
|
||||
"""The audit ID of the initially requested token.
|
||||
|
||||
This is the :py:attr:`audit_chain_id` if present or the
|
||||
:py:attr:`audit_id`.
|
||||
"""
|
||||
return self.audit_chain_id or self.audit_id
|
||||
|
||||
|
||||
class AccessInfoV2(AccessInfo):
|
||||
"""An object for encapsulating raw v2 auth token from identity service."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AccessInfo, self).__init__(*args, **kwargs)
|
||||
self.update(version='v2.0')
|
||||
self.service_catalog = service_catalog.ServiceCatalog.factory(
|
||||
resource_dict=self,
|
||||
token=self['token']['id'],
|
||||
region_name=self._region_name)
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
if body:
|
||||
return 'access' in body
|
||||
elif kwargs:
|
||||
return kwargs.get('version') == 'v2.0'
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_service_catalog(self):
|
||||
return 'serviceCatalog' in self
|
||||
|
||||
@AccessInfo.auth_token.getter
|
||||
def auth_token(self):
|
||||
try:
|
||||
return super(AccessInfoV2, self).auth_token
|
||||
except KeyError:
|
||||
return self['token']['id']
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
return timeutils.parse_isotime(self['token']['expires'])
|
||||
|
||||
@property
|
||||
def issued(self):
|
||||
return timeutils.parse_isotime(self['token']['issued_at'])
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self['user'].get('name', self['user'].get('username'))
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
return self['user']['id']
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
return 'default'
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
return 'Default'
|
||||
|
||||
@property
|
||||
def role_ids(self):
|
||||
return self.get('metadata', {}).get('roles', [])
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
return [r['name'] for r in self['user'].get('roles', [])]
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
try:
|
||||
tenant_dict = self['token']['tenant']
|
||||
except KeyError: # nosec(cjschaef): no 'token' key or 'tenant' key in
|
||||
# token, return the name of the tenant or None
|
||||
pass
|
||||
else:
|
||||
return tenant_dict.get('name')
|
||||
|
||||
# pre grizzly
|
||||
try:
|
||||
return self['user']['tenantName']
|
||||
except KeyError: # nosec(cjschaef): no 'user' key or 'tenantName' in
|
||||
# 'user', attempt 'tenantId' or return None
|
||||
pass
|
||||
|
||||
# pre diablo, keystone only provided a tenantId
|
||||
try:
|
||||
return self['token']['tenantId']
|
||||
except KeyError: # nosec(cjschaef): no 'token' key or 'tenantName' or
|
||||
# 'tenantId' could be found, return None
|
||||
pass
|
||||
|
||||
@property
|
||||
def scoped(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use project_scoped instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'scoped is deprecated as of the 1.7.0 release in favor of '
|
||||
'project_scoped and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
if ('serviceCatalog' in self
|
||||
and self['serviceCatalog']
|
||||
and 'tenant' in self['token']):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
return 'tenant' in self['token']
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
return self.get('trust', {}).get('id')
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
return 'trust' in self
|
||||
|
||||
@property
|
||||
def trustee_user_id(self):
|
||||
return self.get('trust', {}).get('trustee_user_id')
|
||||
|
||||
@property
|
||||
def trustor_user_id(self):
|
||||
# this information is not available in the v2 token bug: #1331882
|
||||
return None
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
try:
|
||||
tenant_dict = self['token']['tenant']
|
||||
except KeyError: # nosec(cjschaef): no 'token' key or 'tenant' dict,
|
||||
# attempt to return 'tenantId' or return None
|
||||
pass
|
||||
else:
|
||||
return tenant_dict.get('id')
|
||||
|
||||
# pre grizzly
|
||||
try:
|
||||
return self['user']['tenantId']
|
||||
except KeyError: # nosec(cjschaef): no 'user' key or 'tenantId' in
|
||||
# 'user', attempt to retrieve from 'token' or return None
|
||||
pass
|
||||
|
||||
# pre diablo
|
||||
try:
|
||||
return self['token']['tenantId']
|
||||
except KeyError: # nosec(cjschaef): no 'token' key or 'tenantId'
|
||||
# could be found, return None
|
||||
pass
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
if self.project_id:
|
||||
return 'default'
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
if self.project_id:
|
||||
return 'Default'
|
||||
|
||||
@property
|
||||
def auth_url(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use service_catalog.get_urls() instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'auth_url is deprecated as of the 1.7.0 release in favor of '
|
||||
'service_catalog.get_urls() and may be removed in the 2.0.0 '
|
||||
'release.', DeprecationWarning)
|
||||
if self.service_catalog:
|
||||
return self.service_catalog.get_urls(service_type='identity',
|
||||
endpoint_type='publicURL',
|
||||
region_name=self._region_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def management_url(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use service_catalog.get_urls() instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'management_url is deprecated as of the 1.7.0 release in favor of '
|
||||
'service_catalog.get_urls() and may be removed in the 2.0.0 '
|
||||
'release.', DeprecationWarning)
|
||||
if self.service_catalog:
|
||||
return self.service_catalog.get_urls(service_type='identity',
|
||||
endpoint_type='adminURL',
|
||||
region_name=self._region_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def oauth_access_token_id(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def oauth_consumer_id(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_federated(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def audit_id(self):
|
||||
try:
|
||||
return self['token'].get('audit_ids', [])[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def audit_chain_id(self):
|
||||
try:
|
||||
return self['token'].get('audit_ids', [])[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
class AccessInfoV3(AccessInfo):
|
||||
"""An object encapsulating raw v3 auth token from identity service."""
|
||||
|
||||
def __init__(self, token, *args, **kwargs):
|
||||
super(AccessInfo, self).__init__(*args, **kwargs)
|
||||
self.update(version='v3')
|
||||
self.service_catalog = service_catalog.ServiceCatalog.factory(
|
||||
resource_dict=self,
|
||||
token=token,
|
||||
region_name=self._region_name)
|
||||
if token:
|
||||
self.auth_token = token
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
if body:
|
||||
return 'token' in body
|
||||
elif kwargs:
|
||||
return kwargs.get('version') == 'v3'
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_service_catalog(self):
|
||||
return 'catalog' in self
|
||||
|
||||
@property
|
||||
def is_federated(self):
|
||||
return 'OS-FEDERATION' in self['user']
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
return timeutils.parse_isotime(self['expires_at'])
|
||||
|
||||
@property
|
||||
def issued(self):
|
||||
return timeutils.parse_isotime(self['issued_at'])
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
return self['user']['id']
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
try:
|
||||
return self['user']['domain']['id']
|
||||
except KeyError:
|
||||
if self.is_federated:
|
||||
return None
|
||||
raise
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
try:
|
||||
return self['user']['domain']['name']
|
||||
except KeyError:
|
||||
if self.is_federated:
|
||||
return None
|
||||
raise
|
||||
|
||||
@property
|
||||
def role_ids(self):
|
||||
return [r['id'] for r in self.get('roles', [])]
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
return [r['name'] for r in self.get('roles', [])]
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self['user']['name']
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
domain = self.get('domain')
|
||||
if domain:
|
||||
return domain['name']
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
domain = self.get('domain')
|
||||
if domain:
|
||||
return domain['id']
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
project = self.get('project')
|
||||
if project:
|
||||
return project['id']
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
project = self.get('project')
|
||||
if project:
|
||||
return project['domain']['id']
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
project = self.get('project')
|
||||
if project:
|
||||
return project['domain']['name']
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
project = self.get('project')
|
||||
if project:
|
||||
return project['name']
|
||||
|
||||
@property
|
||||
def scoped(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use project_scoped instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'scoped is deprecated as of the 1.7.0 release in favor of '
|
||||
'project_scoped and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
return ('catalog' in self and self['catalog'] and 'project' in self)
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
return 'project' in self
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
return 'domain' in self
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
return self.get('OS-TRUST:trust', {}).get('id')
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
return 'OS-TRUST:trust' in self
|
||||
|
||||
@property
|
||||
def trustee_user_id(self):
|
||||
return self.get('OS-TRUST:trust', {}).get('trustee_user', {}).get('id')
|
||||
|
||||
@property
|
||||
def trustor_user_id(self):
|
||||
return self.get('OS-TRUST:trust', {}).get('trustor_user', {}).get('id')
|
||||
|
||||
@property
|
||||
def auth_url(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use service_catalog.get_urls() instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'auth_url is deprecated as of the 1.7.0 release in favor of '
|
||||
'service_catalog.get_urls() and may be removed in the 2.0.0 '
|
||||
'release.', DeprecationWarning)
|
||||
if self.service_catalog:
|
||||
return self.service_catalog.get_urls(service_type='identity',
|
||||
endpoint_type='public',
|
||||
region_name=self._region_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def management_url(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
Use service_catalog.get_urls() instead. It may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'management_url is deprecated as of the 1.7.0 release in favor of '
|
||||
'service_catalog.get_urls() and may be removed in the 2.0.0 '
|
||||
'release.', DeprecationWarning)
|
||||
if self.service_catalog:
|
||||
return self.service_catalog.get_urls(service_type='identity',
|
||||
endpoint_type='admin',
|
||||
region_name=self._region_name)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def oauth_access_token_id(self):
|
||||
return self.get('OS-OAUTH1', {}).get('access_token_id')
|
||||
|
||||
@property
|
||||
def oauth_consumer_id(self):
|
||||
return self.get('OS-OAUTH1', {}).get('consumer_id')
|
||||
|
||||
@property
|
||||
def audit_id(self):
|
||||
try:
|
||||
return self.get('audit_ids', [])[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def audit_chain_id(self):
|
||||
try:
|
||||
return self.get('audit_ids', [])[1]
|
||||
except IndexError:
|
||||
return None
|
@@ -1,223 +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 warnings
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from positional import positional
|
||||
|
||||
|
||||
class Adapter(object):
|
||||
"""An instance of a session with local variables.
|
||||
|
||||
A session is a global object that is shared around amongst many clients. It
|
||||
therefore contains state that is relevant to everyone. There is a lot of
|
||||
state such as the service type and region_name that are only relevant to a
|
||||
particular client that is using the session. An adapter provides a wrapper
|
||||
of client local data around the global session object.
|
||||
|
||||
:param session: The session object to wrap.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param str service_type: The default service_type for URL discovery.
|
||||
:param str service_name: The default service_name for URL discovery.
|
||||
:param str interface: The default interface for URL discovery.
|
||||
:param str region_name: The default region_name for URL discovery.
|
||||
:param str endpoint_override: Always use this endpoint URL for requests
|
||||
for this client.
|
||||
:param tuple version: The version that this API targets.
|
||||
:param auth: An auth plugin to use instead of the session one.
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
:param str user_agent: The User-Agent string to set.
|
||||
:param int connect_retries: the maximum number of retries that should
|
||||
be attempted for connection errors.
|
||||
Default None - use session default which
|
||||
is don't retry.
|
||||
:param logger: A logging object to use for requests that pass through this
|
||||
adapter.
|
||||
:type logger: logging.Logger
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, session, service_type=None, service_name=None,
|
||||
interface=None, region_name=None, endpoint_override=None,
|
||||
version=None, auth=None, user_agent=None,
|
||||
connect_retries=None, logger=None):
|
||||
warnings.warn(
|
||||
'keystoneclient.adapter.Adapter is deprecated as of the 2.1.0 '
|
||||
'release in favor of keystoneauth1.adapter.Adapter. It will be '
|
||||
'removed in future releases.', DeprecationWarning)
|
||||
|
||||
# NOTE(jamielennox): when adding new parameters to adapter please also
|
||||
# add them to the adapter call in httpclient.HTTPClient.__init__
|
||||
self.session = session
|
||||
self.service_type = service_type
|
||||
self.service_name = service_name
|
||||
self.interface = interface
|
||||
self.region_name = region_name
|
||||
self.endpoint_override = endpoint_override
|
||||
self.version = version
|
||||
self.user_agent = user_agent
|
||||
self.auth = auth
|
||||
self.connect_retries = connect_retries
|
||||
self.logger = logger
|
||||
|
||||
def _set_endpoint_filter_kwargs(self, kwargs):
|
||||
if self.service_type:
|
||||
kwargs.setdefault('service_type', self.service_type)
|
||||
if self.service_name:
|
||||
kwargs.setdefault('service_name', self.service_name)
|
||||
if self.interface:
|
||||
kwargs.setdefault('interface', self.interface)
|
||||
if self.region_name:
|
||||
kwargs.setdefault('region_name', self.region_name)
|
||||
if self.version:
|
||||
kwargs.setdefault('version', self.version)
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
self._set_endpoint_filter_kwargs(endpoint_filter)
|
||||
|
||||
if self.endpoint_override:
|
||||
kwargs.setdefault('endpoint_override', self.endpoint_override)
|
||||
|
||||
if self.auth:
|
||||
kwargs.setdefault('auth', self.auth)
|
||||
if self.user_agent:
|
||||
kwargs.setdefault('user_agent', self.user_agent)
|
||||
if self.connect_retries is not None:
|
||||
kwargs.setdefault('connect_retries', self.connect_retries)
|
||||
if self.logger:
|
||||
kwargs.setdefault('logger', self.logger)
|
||||
|
||||
return self.session.request(url, method, **kwargs)
|
||||
|
||||
def get_token(self, auth=None):
|
||||
"""Return a token as provided by the auth plugin.
|
||||
|
||||
:param auth: The auth plugin to use for token. Overrides the plugin
|
||||
on the session. (optional)
|
||||
:type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure: if a new token
|
||||
fetch fails.
|
||||
|
||||
:returns: A valid token.
|
||||
:rtype: string
|
||||
"""
|
||||
return self.session.get_token(auth or self.auth)
|
||||
|
||||
def get_endpoint(self, auth=None, **kwargs):
|
||||
"""Get an endpoint as provided by the auth plugin.
|
||||
|
||||
:param auth: The auth plugin to use for token. Overrides the plugin on
|
||||
the session. (optional)
|
||||
:type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not
|
||||
available.
|
||||
|
||||
:returns: An endpoint if available or None.
|
||||
:rtype: string
|
||||
"""
|
||||
if self.endpoint_override:
|
||||
return self.endpoint_override
|
||||
|
||||
self._set_endpoint_filter_kwargs(kwargs)
|
||||
return self.session.get_endpoint(auth or self.auth, **kwargs)
|
||||
|
||||
def invalidate(self, auth=None):
|
||||
"""Invalidate an authentication plugin."""
|
||||
return self.session.invalidate(auth or self.auth)
|
||||
|
||||
def get_user_id(self, auth=None):
|
||||
"""Return the authenticated user_id as provided by the auth plugin.
|
||||
|
||||
:param auth: The auth plugin to use for token. Overrides the plugin
|
||||
on the session. (optional)
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure:
|
||||
if a new token fetch fails.
|
||||
:raises keystoneclient.exceptions.MissingAuthPlugin:
|
||||
if a plugin is not available.
|
||||
|
||||
:returns: Current `user_id` or None if not supported by plugin.
|
||||
:rtype: string
|
||||
"""
|
||||
return self.session.get_user_id(auth or self.auth)
|
||||
|
||||
def get_project_id(self, auth=None):
|
||||
"""Return the authenticated project_id as provided by the auth plugin.
|
||||
|
||||
:param auth: The auth plugin to use for token. Overrides the plugin
|
||||
on the session. (optional)
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure:
|
||||
if a new token fetch fails.
|
||||
:raises keystoneclient.exceptions.MissingAuthPlugin:
|
||||
if a plugin is not available.
|
||||
|
||||
:returns: Current `project_id` or None if not supported by plugin.
|
||||
:rtype: string
|
||||
"""
|
||||
return self.session.get_project_id(auth or self.auth)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.request(url, 'GET', **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.request(url, 'HEAD', **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.request(url, 'POST', **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.request(url, 'PUT', **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.request(url, 'PATCH', **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.request(url, 'DELETE', **kwargs)
|
||||
|
||||
|
||||
class LegacyJsonAdapter(Adapter):
|
||||
"""Make something that looks like an old HTTPClient.
|
||||
|
||||
A common case when using an adapter is that we want an interface similar to
|
||||
the HTTPClients of old which returned the body as JSON as well.
|
||||
|
||||
You probably don't want this if you are starting from scratch.
|
||||
"""
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
headers.setdefault('Accept', 'application/json')
|
||||
|
||||
try:
|
||||
kwargs['json'] = kwargs.pop('body')
|
||||
except KeyError: # nosec(cjschaef): kwargs doesn't contain a 'body'
|
||||
# key, while 'json' is an optional argument for Session.request
|
||||
pass
|
||||
|
||||
resp = super(LegacyJsonAdapter, self).request(*args, **kwargs)
|
||||
|
||||
body = None
|
||||
if resp.text:
|
||||
try:
|
||||
body = jsonutils.loads(resp.text)
|
||||
except ValueError: # nosec(cjschaef): return None for body as
|
||||
# expected
|
||||
pass
|
||||
|
||||
return resp, body
|
@@ -1,38 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from keystoneclient.auth.base import * # noqa
|
||||
from keystoneclient.auth.cli import * # noqa
|
||||
from keystoneclient.auth.conf import * # noqa
|
||||
|
||||
|
||||
__all__ = (
|
||||
# auth.base
|
||||
'AUTH_INTERFACE',
|
||||
'BaseAuthPlugin',
|
||||
'get_available_plugin_names',
|
||||
'get_available_plugin_classes',
|
||||
'get_plugin_class',
|
||||
'IDENTITY_AUTH_HEADER_NAME',
|
||||
'PLUGIN_NAMESPACE',
|
||||
|
||||
# auth.cli
|
||||
'load_from_argparse_arguments',
|
||||
'register_argparse_arguments',
|
||||
|
||||
# auth.conf
|
||||
'get_common_conf_options',
|
||||
'get_plugin_options',
|
||||
'load_from_conf_options',
|
||||
'register_conf_options',
|
||||
)
|
@@ -1,374 +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 debtcollector import removals
|
||||
from keystoneauth1 import plugin
|
||||
import six
|
||||
import stevedore
|
||||
|
||||
from keystoneclient import exceptions
|
||||
|
||||
|
||||
# NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be
|
||||
# requested from get_endpoint. If a plugin receives this as the value of
|
||||
# 'interface' it should return the initial URL that was passed to the plugin.
|
||||
AUTH_INTERFACE = plugin.AUTH_INTERFACE
|
||||
|
||||
PLUGIN_NAMESPACE = 'keystoneclient.auth.plugin'
|
||||
IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token'
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def get_available_plugin_names():
|
||||
"""Get the names of all the plugins that are available on the system.
|
||||
|
||||
This is particularly useful for help and error text to prompt a user for
|
||||
example what plugins they may specify.
|
||||
|
||||
:returns: A list of names.
|
||||
:rtype: frozenset
|
||||
"""
|
||||
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE,
|
||||
invoke_on_load=False)
|
||||
return frozenset(mgr.names())
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def get_available_plugin_classes():
|
||||
"""Retrieve all the plugin classes available on the system.
|
||||
|
||||
:returns: A dict with plugin entrypoint name as the key and the plugin
|
||||
class as the value.
|
||||
:rtype: dict
|
||||
"""
|
||||
mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE,
|
||||
propagate_map_exceptions=True,
|
||||
invoke_on_load=False)
|
||||
|
||||
return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.plugin)))
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def get_plugin_class(name):
|
||||
"""Retrieve a plugin class by its entrypoint name.
|
||||
|
||||
:param str name: The name of the object to get.
|
||||
|
||||
:returns: An auth plugin class.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be
|
||||
created.
|
||||
"""
|
||||
try:
|
||||
mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE,
|
||||
name=name,
|
||||
invoke_on_load=False)
|
||||
except RuntimeError:
|
||||
raise exceptions.NoMatchingPlugin(name)
|
||||
|
||||
return mgr.driver
|
||||
|
||||
|
||||
class BaseAuthPlugin(object):
|
||||
"""The basic structure of an authentication plugin."""
|
||||
|
||||
def get_token(self, session, **kwargs):
|
||||
"""Obtain a token.
|
||||
|
||||
How the token is obtained is up to the plugin. If it is still valid
|
||||
it may be re-used, retrieved from cache or invoke an authentication
|
||||
request against a server.
|
||||
|
||||
There are no required kwargs. They are passed directly to the auth
|
||||
plugin and they are implementation specific.
|
||||
|
||||
Returning None will indicate that no token was able to be retrieved.
|
||||
|
||||
This function is misplaced as it should only be required for auth
|
||||
plugins that use the 'X-Auth-Token' header. However due to the way
|
||||
plugins evolved this method is required and often called to trigger an
|
||||
authentication request on a new plugin.
|
||||
|
||||
When implementing a new plugin it is advised that you implement this
|
||||
method, however if you don't require the 'X-Auth-Token' header override
|
||||
the `get_headers` method instead.
|
||||
|
||||
:param session: A session object so the plugin can make HTTP calls.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:return: A token to use.
|
||||
:rtype: string
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_headers(self, session, **kwargs):
|
||||
"""Fetch authentication headers for message.
|
||||
|
||||
This is a more generalized replacement of the older get_token to allow
|
||||
plugins to specify different or additional authentication headers to
|
||||
the OpenStack standard 'X-Auth-Token' header.
|
||||
|
||||
How the authentication headers are obtained is up to the plugin. If the
|
||||
headers are still valid they may be re-used, retrieved from cache or
|
||||
the plugin may invoke an authentication request against a server.
|
||||
|
||||
The default implementation of get_headers calls the `get_token` method
|
||||
to enable older style plugins to continue functioning unchanged.
|
||||
Subclasses should feel free to completely override this function to
|
||||
provide the headers that they want.
|
||||
|
||||
There are no required kwargs. They are passed directly to the auth
|
||||
plugin and they are implementation specific.
|
||||
|
||||
Returning None will indicate that no token was able to be retrieved and
|
||||
that authorization was a failure. Adding no authentication data can be
|
||||
achieved by returning an empty dictionary.
|
||||
|
||||
:param session: The session object that the auth_plugin belongs to.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: Headers that are set to authenticate a message or None for
|
||||
failure. Note that when checking this value that the empty
|
||||
dict is a valid, non-failure response.
|
||||
:rtype: dict
|
||||
"""
|
||||
token = self.get_token(session)
|
||||
|
||||
if not token:
|
||||
return None
|
||||
|
||||
return {IDENTITY_AUTH_HEADER_NAME: token}
|
||||
|
||||
def get_endpoint(self, session, **kwargs):
|
||||
"""Return an endpoint for the client.
|
||||
|
||||
There are no required keyword arguments to ``get_endpoint`` as a plugin
|
||||
implementation should use best effort with the information available to
|
||||
determine the endpoint. However there are certain standard options that
|
||||
will be generated by the clients and should be used by plugins:
|
||||
|
||||
- ``service_type``: what sort of service is required.
|
||||
- ``service_name``: the name of the service in the catalog.
|
||||
- ``interface``: what visibility the endpoint should have.
|
||||
- ``region_name``: the region the endpoint exists in.
|
||||
|
||||
:param session: The session object that the auth_plugin belongs to.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: The base URL that will be used to talk to the required
|
||||
service or None if not available.
|
||||
:rtype: string
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_connection_params(self, session, **kwargs):
|
||||
"""Return any additional connection parameters required for the plugin.
|
||||
|
||||
:param session: The session object that the auth_plugin belongs to.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: Headers that are set to authenticate a message or None for
|
||||
failure. Note that when checking this value that the empty
|
||||
dict is a valid, non-failure response.
|
||||
:rtype: dict
|
||||
"""
|
||||
return {}
|
||||
|
||||
def invalidate(self):
|
||||
"""Invalidate the current authentication data.
|
||||
|
||||
This should result in fetching a new token on next call.
|
||||
|
||||
A plugin may be invalidated if an Unauthorized HTTP response is
|
||||
returned to indicate that the token may have been revoked or is
|
||||
otherwise now invalid.
|
||||
|
||||
:returns: True if there was something that the plugin did to
|
||||
invalidate. This means that it makes sense to try again. If
|
||||
nothing happens returns False to indicate give up.
|
||||
:rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_user_id(self, session, **kwargs):
|
||||
"""Return a unique user identifier of the plugin.
|
||||
|
||||
Wherever possible the user id should be inferred from the token however
|
||||
there are certain URLs and other places that require access to the
|
||||
currently authenticated user id.
|
||||
|
||||
:param session: A session object so the plugin can make HTTP calls.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: A user identifier or None if one is not available.
|
||||
:rtype: str
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_project_id(self, session, **kwargs):
|
||||
"""Return the project id that we are authenticated to.
|
||||
|
||||
Wherever possible the project id should be inferred from the token
|
||||
however there are certain URLs and other places that require access to
|
||||
the currently authenticated project id.
|
||||
|
||||
:param session: A session object so the plugin can make HTTP calls.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: A project identifier or None if one is not available.
|
||||
:rtype: str
|
||||
"""
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
"""Return the list of parameters associated with the auth plugin.
|
||||
|
||||
This list may be used to generate CLI or config arguments.
|
||||
|
||||
:returns: A list of Param objects describing available plugin
|
||||
parameters.
|
||||
:rtype: List
|
||||
"""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def load_from_options(cls, **kwargs):
|
||||
"""Create a plugin from the arguments retrieved from get_options.
|
||||
|
||||
A client can override this function to do argument validation or to
|
||||
handle differences between the registered options and what is required
|
||||
to create the plugin.
|
||||
"""
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def register_argparse_arguments(cls, parser):
|
||||
"""Register the CLI options provided by a specific plugin.
|
||||
|
||||
Given a plugin class convert it's options into argparse arguments and
|
||||
add them to a parser.
|
||||
|
||||
:param parser: the parser to attach argparse options.
|
||||
:type parser: argparse.ArgumentParser
|
||||
"""
|
||||
# NOTE(jamielennox): ideally oslo_config would be smart enough to
|
||||
# handle all the Opt manipulation that goes on in this file. However it
|
||||
# is currently not. Options are handled in as similar a way as
|
||||
# possible to oslo_config such that when available we should be able to
|
||||
# transition.
|
||||
|
||||
for opt in cls.get_options():
|
||||
args = []
|
||||
envs = []
|
||||
|
||||
for o in [opt] + opt.deprecated_opts:
|
||||
args.append('--os-%s' % o.name)
|
||||
envs.append('OS_%s' % o.name.replace('-', '_').upper())
|
||||
|
||||
# select the first ENV that is not false-y or return None
|
||||
env_vars = (os.environ.get(e) for e in envs)
|
||||
default = six.next(six.moves.filter(None, env_vars), None)
|
||||
|
||||
parser.add_argument(*args,
|
||||
default=default or opt.default,
|
||||
metavar=opt.metavar,
|
||||
help=opt.help,
|
||||
dest='os_%s' % opt.dest)
|
||||
|
||||
@classmethod
|
||||
def load_from_argparse_arguments(cls, namespace, **kwargs):
|
||||
"""Load a specific plugin object from an argparse result.
|
||||
|
||||
Convert the results of a parse into the specified plugin.
|
||||
|
||||
:param namespace: The result from CLI parsing.
|
||||
:type namespace: argparse.Namespace
|
||||
|
||||
:returns: An auth plugin, or None if a name is not provided.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
"""
|
||||
def _getter(opt):
|
||||
return getattr(namespace, 'os_%s' % opt.dest)
|
||||
|
||||
return cls.load_from_options_getter(_getter, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def register_conf_options(cls, conf, group):
|
||||
"""Register the oslo_config options that are needed for a plugin.
|
||||
|
||||
:param conf: A config object.
|
||||
:type conf: oslo_config.cfg.ConfigOpts
|
||||
:param string group: The group name that options should be read from.
|
||||
"""
|
||||
plugin_opts = cls.get_options()
|
||||
conf.register_opts(plugin_opts, group=group)
|
||||
|
||||
@classmethod
|
||||
def load_from_conf_options(cls, conf, group, **kwargs):
|
||||
"""Load the plugin from a CONF object.
|
||||
|
||||
Convert the options already registered into a real plugin.
|
||||
|
||||
:param conf: A config object.
|
||||
:type conf: oslo_config.cfg.ConfigOpts
|
||||
:param string group: The group name that options should be read from.
|
||||
|
||||
:returns: An authentication Plugin.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
"""
|
||||
def _getter(opt):
|
||||
return conf[group][opt.dest]
|
||||
|
||||
return cls.load_from_options_getter(_getter, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def load_from_options_getter(cls, getter, **kwargs):
|
||||
"""Load a plugin from a getter function returning appropriate values.
|
||||
|
||||
To handle cases other than the provided CONF and CLI loading you can
|
||||
specify a custom loader function that will be queried for the option
|
||||
value.
|
||||
|
||||
The getter is a function that takes one value, an
|
||||
:py:class:`oslo_config.cfg.Opt` and returns a value to load with.
|
||||
|
||||
:param getter: A function that returns a value for the given opt.
|
||||
:type getter: callable
|
||||
|
||||
:returns: An authentication Plugin.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
"""
|
||||
plugin_opts = cls.get_options()
|
||||
|
||||
for opt in plugin_opts:
|
||||
val = getter(opt)
|
||||
if val is not None:
|
||||
val = opt.type(val)
|
||||
kwargs.setdefault(opt.dest, val)
|
||||
|
||||
return cls.load_from_options(**kwargs)
|
@@ -1,97 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from debtcollector import removals
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient.auth import base
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
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.
|
||||
|
||||
The function inspects the provided arguments so that it can also register
|
||||
the options required for that specific plugin if available.
|
||||
|
||||
:param argparse.ArgumentParser: the parser to attach argparse options to.
|
||||
:param List argv: the arguments provided to the application.
|
||||
:param str/class default: a default plugin name or a plugin object to use
|
||||
if one isn't specified by the CLI. default: None.
|
||||
|
||||
:returns: The plugin class that will be loaded or None if not provided.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be
|
||||
created.
|
||||
"""
|
||||
in_parser = argparse.ArgumentParser(add_help=False)
|
||||
env_plugin = os.environ.get('OS_AUTH_PLUGIN', default)
|
||||
for p in (in_parser, parser):
|
||||
p.add_argument('--os-auth-plugin',
|
||||
metavar='<name>',
|
||||
default=env_plugin,
|
||||
help='The auth plugin to load')
|
||||
|
||||
options, _args = in_parser.parse_known_args(argv)
|
||||
|
||||
if not options.os_auth_plugin:
|
||||
return None
|
||||
|
||||
if isinstance(options.os_auth_plugin, type):
|
||||
msg = 'Default Authentication options'
|
||||
plugin = options.os_auth_plugin
|
||||
else:
|
||||
msg = 'Options specific to the %s plugin.' % options.os_auth_plugin
|
||||
plugin = base.get_plugin_class(options.os_auth_plugin)
|
||||
|
||||
group = parser.add_argument_group('Authentication Options', msg)
|
||||
plugin.register_argparse_arguments(group)
|
||||
return plugin
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def load_from_argparse_arguments(namespace, **kwargs):
|
||||
"""Retrieve the created plugin from the completed argparse results.
|
||||
|
||||
Loads and creates the auth plugin from the information parsed from the
|
||||
command line by argparse.
|
||||
|
||||
:param Namespace namespace: The result from CLI parsing.
|
||||
|
||||
:returns: An auth plugin, or None if a name is not provided.
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be
|
||||
created.
|
||||
"""
|
||||
if not namespace.os_auth_plugin:
|
||||
return None
|
||||
|
||||
if isinstance(namespace.os_auth_plugin, type):
|
||||
plugin = namespace.os_auth_plugin
|
||||
else:
|
||||
plugin = base.get_plugin_class(namespace.os_auth_plugin)
|
||||
|
||||
return plugin.load_from_argparse_arguments(namespace, **kwargs)
|
@@ -1,132 +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 debtcollector import removals
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystoneclient.auth import base
|
||||
|
||||
_AUTH_PLUGIN_OPT = cfg.StrOpt('auth_plugin', help='Name of the plugin to load')
|
||||
|
||||
_section_help = 'Config Section from which to load plugin specific options'
|
||||
_AUTH_SECTION_OPT = cfg.StrOpt('auth_section', help=_section_help)
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def get_common_conf_options():
|
||||
"""Get the oslo_config options common for all auth plugins.
|
||||
|
||||
These may be useful without being registered for config file generation
|
||||
or to manipulate the options before registering them yourself.
|
||||
|
||||
The options that are set are:
|
||||
:auth_plugin: The name of the plugin to load.
|
||||
:auth_section: The config file section to load options from.
|
||||
|
||||
:returns: A list of oslo_config options.
|
||||
"""
|
||||
return [_AUTH_PLUGIN_OPT, _AUTH_SECTION_OPT]
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def get_plugin_options(name):
|
||||
"""Get the oslo_config options for a specific plugin.
|
||||
|
||||
This will be the list of config options that is registered and loaded by
|
||||
the specified plugin.
|
||||
|
||||
:returns: A list of oslo_config options.
|
||||
"""
|
||||
return base.get_plugin_class(name).get_options()
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def register_conf_options(conf, group):
|
||||
"""Register the oslo_config options that are needed for a plugin.
|
||||
|
||||
This only registers the basic options shared by all plugins. Options that
|
||||
are specific to a plugin are loaded just before they are read.
|
||||
|
||||
The defined options are:
|
||||
|
||||
- auth_plugin: the name of the auth plugin that will be used for
|
||||
authentication.
|
||||
- auth_section: the group from which further auth plugin options should be
|
||||
taken. If section is not provided then the auth plugin options will be
|
||||
taken from the same group as provided in the parameters.
|
||||
|
||||
:param conf: config object to register with.
|
||||
:type conf: oslo_config.cfg.ConfigOpts
|
||||
:param string group: The ini group to register options in.
|
||||
"""
|
||||
conf.register_opt(_AUTH_SECTION_OPT, group=group)
|
||||
|
||||
# NOTE(jamielennox): plugins are allowed to specify a 'section' which is
|
||||
# the group that auth options should be taken from. If not present they
|
||||
# come from the same as the base options were registered in. If present
|
||||
# then the auth_plugin option may be read from that section so add that
|
||||
# option.
|
||||
if conf[group].auth_section:
|
||||
group = conf[group].auth_section
|
||||
|
||||
conf.register_opt(_AUTH_PLUGIN_OPT, group=group)
|
||||
|
||||
|
||||
@removals.remove(
|
||||
message='keystoneclient auth plugins are deprecated. Use keystoneauth.',
|
||||
version='2.1.0',
|
||||
removal_version='3.0.0'
|
||||
)
|
||||
def load_from_conf_options(conf, group, **kwargs):
|
||||
"""Load a plugin from an oslo_config CONF object.
|
||||
|
||||
Each plugin will register their own required options and so there is no
|
||||
standard list and the plugin should be consulted.
|
||||
|
||||
The base options should have been registered with register_conf_options
|
||||
before this function is called.
|
||||
|
||||
:param conf: A conf object.
|
||||
:type conf: oslo_config.cfg.ConfigOpts
|
||||
:param string group: The group name that options should be read from.
|
||||
|
||||
:returns: An authentication Plugin or None if a name is not provided
|
||||
:rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin`
|
||||
|
||||
:raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be
|
||||
created.
|
||||
"""
|
||||
# NOTE(jamielennox): plugins are allowed to specify a 'section' which is
|
||||
# the group that auth options should be taken from. If not present they
|
||||
# come from the same as the base options were registered in.
|
||||
if conf[group].auth_section:
|
||||
group = conf[group].auth_section
|
||||
|
||||
name = conf[group].auth_plugin
|
||||
if not name:
|
||||
return None
|
||||
|
||||
plugin_class = base.get_plugin_class(name)
|
||||
plugin_class.register_conf_options(conf, group)
|
||||
return plugin_class.load_from_conf_options(conf, group, **kwargs)
|
@@ -1,37 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient.auth.identity import generic
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
|
||||
|
||||
BaseIdentityPlugin = base.BaseIdentityPlugin
|
||||
|
||||
V2Password = v2.Password
|
||||
V2Token = v2.Token
|
||||
|
||||
V3Password = v3.Password
|
||||
V3Token = v3.Token
|
||||
|
||||
Password = generic.Password
|
||||
Token = generic.Token
|
||||
|
||||
|
||||
__all__ = ('BaseIdentityPlugin',
|
||||
'Password',
|
||||
'Token',
|
||||
'V2Password',
|
||||
'V2Token',
|
||||
'V3Password',
|
||||
'V3Token')
|
@@ -1,48 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient.auth.identity import base
|
||||
|
||||
|
||||
class AccessInfoPlugin(base.BaseIdentityPlugin):
|
||||
"""A plugin that turns an existing AccessInfo object into a usable plugin.
|
||||
|
||||
There are cases where reuse of an auth_ref or AccessInfo object is
|
||||
warranted such as from a cache, from auth_token middleware, or another
|
||||
source.
|
||||
|
||||
Turn the existing access info object into an identity plugin. This plugin
|
||||
cannot be refreshed as the AccessInfo object does not contain any
|
||||
authorizing information.
|
||||
|
||||
:param auth_ref: the existing AccessInfo object.
|
||||
:type auth_ref: keystoneclient.access.AccessInfo
|
||||
:param auth_url: the url where this AccessInfo was retrieved from. Required
|
||||
if using the AUTH_INTERFACE with get_endpoint. (optional)
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, auth_ref, auth_url=None):
|
||||
super(AccessInfoPlugin, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=False)
|
||||
self.auth_ref = auth_ref
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
return self.auth_ref
|
||||
|
||||
def invalidate(self):
|
||||
# NOTE(jamielennox): Don't allow the default invalidation to occur
|
||||
# because on next authentication request we will only get the same
|
||||
# auth_ref object again.
|
||||
return False
|
@@ -1,422 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import logging
|
||||
import threading
|
||||
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
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_options():
|
||||
return [
|
||||
cfg.StrOpt('auth-url', help='Authentication URL'),
|
||||
]
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseIdentityPlugin(base.BaseAuthPlugin):
|
||||
|
||||
# we count a token as valid (not needing refreshing) if it is valid for at
|
||||
# least this many seconds before the token expiry time
|
||||
MIN_TOKEN_LIFE_SECONDS = 120
|
||||
|
||||
def __init__(self,
|
||||
auth_url=None,
|
||||
username=None,
|
||||
password=None,
|
||||
token=None,
|
||||
trust_id=None,
|
||||
reauthenticate=True):
|
||||
|
||||
super(BaseIdentityPlugin, self).__init__()
|
||||
|
||||
warnings.warn(
|
||||
'keystoneclient auth plugins are deprecated as of the 2.1.0 '
|
||||
'release in favor of keystoneauth1 plugins. They will be removed '
|
||||
'in future releases.', DeprecationWarning)
|
||||
|
||||
self.auth_url = auth_url
|
||||
self.auth_ref = None
|
||||
self.reauthenticate = reauthenticate
|
||||
|
||||
self._endpoint_cache = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._token = token
|
||||
self._trust_id = trust_id
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'username is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'username is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
self._username = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'password is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'password is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
self._password = value
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'token is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
return self._token
|
||||
|
||||
@token.setter
|
||||
def token(self, value):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'token is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
self._token = value
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'trust_id is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
return self._trust_id
|
||||
|
||||
@trust_id.setter
|
||||
def trust_id(self, value):
|
||||
"""Deprecated as of the 1.7.0 release.
|
||||
|
||||
It may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'trust_id is deprecated as of the 1.7.0 release and may be '
|
||||
'removed in the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
self._trust_id = value
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
"""Obtain a token from an OpenStack Identity Service.
|
||||
|
||||
This method is overridden by the various token version plugins.
|
||||
|
||||
This method should not be called independently and is expected to be
|
||||
invoked via the do_authenticate() method.
|
||||
|
||||
This method will be invoked if the AccessInfo object cached by the
|
||||
plugin is not valid. Thus plugins should always fetch a new AccessInfo
|
||||
when invoked. If you are looking to just retrieve the current auth data
|
||||
then you should use get_access().
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises keystoneclient.exceptions.InvalidResponse: The response
|
||||
returned wasn't
|
||||
appropriate.
|
||||
:raises keystoneclient.exceptions.HttpError: An error from an invalid
|
||||
HTTP response.
|
||||
|
||||
:returns: Token access information.
|
||||
:rtype: :py:class:`keystoneclient.access.AccessInfo`
|
||||
"""
|
||||
pass # pragma: no cover
|
||||
|
||||
def get_token(self, session, **kwargs):
|
||||
"""Return a valid auth token.
|
||||
|
||||
If a valid token is not present then a new one will be fetched.
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises keystoneclient.exceptions.HttpError: An error from an invalid
|
||||
HTTP response.
|
||||
|
||||
:return: A valid token.
|
||||
:rtype: string
|
||||
"""
|
||||
return self.get_access(session).auth_token
|
||||
|
||||
def _needs_reauthenticate(self):
|
||||
"""Return if the existing token needs to be re-authenticated.
|
||||
|
||||
The token should be refreshed if it is about to expire.
|
||||
|
||||
:returns: True if the plugin should fetch a new token. False otherwise.
|
||||
"""
|
||||
if not self.auth_ref:
|
||||
# authentication was never fetched.
|
||||
return True
|
||||
|
||||
if not self.reauthenticate:
|
||||
# don't re-authenticate if it has been disallowed.
|
||||
return False
|
||||
|
||||
if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS):
|
||||
# if it's about to expire we should re-authenticate now.
|
||||
return True
|
||||
|
||||
# otherwise it's fine and use the existing one.
|
||||
return False
|
||||
|
||||
def get_access(self, session, **kwargs):
|
||||
"""Fetch or return a current AccessInfo object.
|
||||
|
||||
If a valid AccessInfo is present then it is returned otherwise a new
|
||||
one will be fetched.
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises keystoneclient.exceptions.HttpError: An error from an invalid
|
||||
HTTP response.
|
||||
|
||||
:returns: Valid AccessInfo
|
||||
:rtype: :py:class:`keystoneclient.access.AccessInfo`
|
||||
"""
|
||||
# Hey Kids! Thread safety is important particularly in the case where
|
||||
# a service is creating an admin style plugin that will then proceed
|
||||
# to make calls from many threads. As a token expires all the threads
|
||||
# will try and fetch a new token at once, so we want to ensure that
|
||||
# only one thread tries to actually fetch from keystone at once.
|
||||
with self._lock:
|
||||
if self._needs_reauthenticate():
|
||||
self.auth_ref = self.get_auth_ref(session)
|
||||
|
||||
return self.auth_ref
|
||||
|
||||
def invalidate(self):
|
||||
"""Invalidate the current authentication data.
|
||||
|
||||
This should result in fetching a new token on next call.
|
||||
|
||||
A plugin may be invalidated if an Unauthorized HTTP response is
|
||||
returned to indicate that the token may have been revoked or is
|
||||
otherwise now invalid.
|
||||
|
||||
:returns: True if there was something that the plugin did to
|
||||
invalidate. This means that it makes sense to try again. If
|
||||
nothing happens returns False to indicate give up.
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.auth_ref:
|
||||
self.auth_ref = None
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_endpoint(self, session, service_type=None, interface=None,
|
||||
region_name=None, service_name=None, version=None,
|
||||
**kwargs):
|
||||
"""Return a valid endpoint for a service.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
the session and kwargs.
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param string service_type: The type of service to lookup the endpoint
|
||||
for. This plugin will return None (failure)
|
||||
if service_type is not provided.
|
||||
:param string interface: The exposure of the endpoint. Should be
|
||||
`public`, `internal`, `admin`, or `auth`.
|
||||
`auth` is special here to use the `auth_url`
|
||||
rather than a URL extracted from the service
|
||||
catalog. Defaults to `public`.
|
||||
:param string region_name: The region the endpoint should exist in.
|
||||
(optional)
|
||||
:param string service_name: The name of the service in the catalog.
|
||||
(optional)
|
||||
:param tuple version: The minimum version number required for this
|
||||
endpoint. (optional)
|
||||
|
||||
:raises keystoneclient.exceptions.HttpError: An error from an invalid
|
||||
HTTP response.
|
||||
|
||||
:return: A valid endpoint URL or None if not available.
|
||||
:rtype: string or None
|
||||
"""
|
||||
# NOTE(jamielennox): if you specifically ask for requests to be sent to
|
||||
# the auth url then we can ignore many of the checks. Typically if you
|
||||
# are asking for the auth endpoint it means that there is no catalog to
|
||||
# query however we still need to support asking for a specific version
|
||||
# of the auth_url for generic plugins.
|
||||
if interface is base.AUTH_INTERFACE:
|
||||
url = self.auth_url
|
||||
service_type = service_type or 'identity'
|
||||
|
||||
else:
|
||||
if not service_type:
|
||||
LOG.warning(
|
||||
'Plugin cannot return an endpoint without knowing the '
|
||||
'service type that is required. Add service_type to '
|
||||
'endpoint filtering data.')
|
||||
return None
|
||||
|
||||
if not interface:
|
||||
interface = 'public'
|
||||
|
||||
service_catalog = self.get_access(session).service_catalog
|
||||
url = service_catalog.url_for(service_type=service_type,
|
||||
endpoint_type=interface,
|
||||
region_name=region_name,
|
||||
service_name=service_name)
|
||||
|
||||
if not version:
|
||||
# NOTE(jamielennox): This may not be the best thing to default to
|
||||
# but is here for backwards compatibility. It may be worth
|
||||
# defaulting to the most recent version.
|
||||
return url
|
||||
|
||||
# NOTE(jamielennox): For backwards compatibility people might have a
|
||||
# versioned endpoint in their catalog even though they want to use
|
||||
# other endpoint versions. So we support a list of client defined
|
||||
# situations where we can strip the version component from a URL before
|
||||
# doing discovery.
|
||||
hacked_url = _discover.get_catalog_discover_hack(service_type, url)
|
||||
|
||||
try:
|
||||
disc = self.get_discovery(session, hacked_url, authenticated=False)
|
||||
except (exceptions.DiscoveryFailure,
|
||||
exceptions.HTTPError,
|
||||
exceptions.ConnectionError):
|
||||
# NOTE(jamielennox): Again if we can't contact the server we fall
|
||||
# back to just returning the URL from the catalog. This may not be
|
||||
# the best default but we need it for now.
|
||||
LOG.warning(
|
||||
'Failed to contact the endpoint at %s for discovery. Fallback '
|
||||
'to using that endpoint as the base url.', url)
|
||||
else:
|
||||
url = disc.url_for(version)
|
||||
|
||||
return url
|
||||
|
||||
def get_user_id(self, session, **kwargs):
|
||||
return self.get_access(session).user_id
|
||||
|
||||
def get_project_id(self, session, **kwargs):
|
||||
return self.get_access(session).project_id
|
||||
|
||||
@positional()
|
||||
def get_discovery(self, session, url, authenticated=None):
|
||||
"""Return the discovery object for a URL.
|
||||
|
||||
Check the session and the plugin cache to see if we have already
|
||||
performed discovery on the URL and if so return it, otherwise create
|
||||
a new discovery object, cache it and return it.
|
||||
|
||||
This function is expected to be used by subclasses and should not
|
||||
be needed by users.
|
||||
|
||||
:param session: A session object to discover with.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param str url: The url to lookup.
|
||||
:param bool authenticated: Include a token in the discovery call.
|
||||
(optional) Defaults to None (use a token
|
||||
if a plugin is installed).
|
||||
|
||||
:raises keystoneclient.exceptions.DiscoveryFailure: if for some reason
|
||||
the lookup fails.
|
||||
:raises keystoneclient.exceptions.HttpError: An error from an invalid
|
||||
HTTP response.
|
||||
|
||||
:returns: A discovery object with the results of looking up that URL.
|
||||
"""
|
||||
# NOTE(jamielennox): we want to cache endpoints on the session as well
|
||||
# so that they maintain sharing between auth plugins. Create a cache on
|
||||
# the session if it doesn't exist already.
|
||||
try:
|
||||
session_endpoint_cache = session._identity_endpoint_cache
|
||||
except AttributeError:
|
||||
session_endpoint_cache = session._identity_endpoint_cache = {}
|
||||
|
||||
# NOTE(jamielennox): There is a cache located on both the session
|
||||
# object and the auth plugin object so that they can be shared and the
|
||||
# cache is still usable
|
||||
for cache in (self._endpoint_cache, session_endpoint_cache):
|
||||
disc = cache.get(url)
|
||||
|
||||
if disc:
|
||||
break
|
||||
else:
|
||||
disc = _discover.Discover(session, url,
|
||||
authenticated=authenticated)
|
||||
self._endpoint_cache[url] = disc
|
||||
session_endpoint_cache[url] = disc
|
||||
|
||||
return disc
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(BaseIdentityPlugin, cls).get_options()
|
||||
options.extend(get_options())
|
||||
return options
|
@@ -1,21 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.auth.identity.generic.base import BaseGenericPlugin # noqa
|
||||
from keystoneclient.auth.identity.generic.password import Password # noqa
|
||||
from keystoneclient.auth.identity.generic.token import Token # noqa
|
||||
|
||||
|
||||
__all__ = ('BaseGenericPlugin',
|
||||
'Password',
|
||||
'Token',
|
||||
)
|
@@ -1,192 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from keystoneclient import _discover
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_options():
|
||||
return [
|
||||
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
|
||||
cfg.StrOpt('domain-name', help='Domain name to scope to'),
|
||||
cfg.StrOpt('tenant-id', help='Tenant ID to scope to'),
|
||||
cfg.StrOpt('tenant-name', help='Tenant name to scope to'),
|
||||
cfg.StrOpt('project-id', help='Project ID to scope to'),
|
||||
cfg.StrOpt('project-name', help='Project name to scope to'),
|
||||
cfg.StrOpt('project-domain-id',
|
||||
help='Domain ID containing project'),
|
||||
cfg.StrOpt('project-domain-name',
|
||||
help='Domain name containing project'),
|
||||
cfg.StrOpt('trust-id', help='Trust ID'),
|
||||
]
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseGenericPlugin(base.BaseIdentityPlugin):
|
||||
"""An identity plugin that is not version dependent.
|
||||
|
||||
Internally we will construct a version dependent plugin with the resolved
|
||||
URL and then proxy all calls from the base plugin to the versioned one.
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url,
|
||||
tenant_id=None,
|
||||
tenant_name=None,
|
||||
project_id=None,
|
||||
project_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
domain_id=None,
|
||||
domain_name=None,
|
||||
trust_id=None):
|
||||
super(BaseGenericPlugin, self).__init__(auth_url=auth_url)
|
||||
|
||||
self._project_id = project_id or tenant_id
|
||||
self._project_name = project_name or tenant_name
|
||||
self._project_domain_id = project_domain_id
|
||||
self._project_domain_name = project_domain_name
|
||||
self._domain_id = domain_id
|
||||
self._domain_name = domain_name
|
||||
self._trust_id = trust_id
|
||||
|
||||
self._plugin = None
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
# Override to remove deprecation.
|
||||
return self._trust_id
|
||||
|
||||
@trust_id.setter
|
||||
def trust_id(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._trust_id = value
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_plugin(self, session, version, url, raw_status=None):
|
||||
"""Create a plugin from the given parameters.
|
||||
|
||||
This function will be called multiple times with the version and url
|
||||
of a potential endpoint. If a plugin can be constructed that fits the
|
||||
params then it should return it. If not return None and then another
|
||||
call will be made with other available URLs.
|
||||
|
||||
:param session: A session object.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param tuple version: A tuple of the API version at the URL.
|
||||
:param string url: The base URL for this version.
|
||||
:param string raw_status: The status that was in the discovery field.
|
||||
|
||||
:returns: A plugin that can match the parameters or None if nothing.
|
||||
"""
|
||||
return None # pragma: no cover
|
||||
|
||||
@property
|
||||
def _has_domain_scope(self):
|
||||
"""Are there domain parameters.
|
||||
|
||||
Domain parameters are v3 only so returns if any are set.
|
||||
|
||||
:returns: True if a domain parameter is set, false otherwise.
|
||||
"""
|
||||
return any([self._domain_id, self._domain_name,
|
||||
self._project_domain_id, self._project_domain_name])
|
||||
|
||||
@property
|
||||
def _v2_params(self):
|
||||
"""Return parameters that are common to v2 plugins."""
|
||||
return {'trust_id': self._trust_id,
|
||||
'tenant_id': self._project_id,
|
||||
'tenant_name': self._project_name}
|
||||
|
||||
@property
|
||||
def _v3_params(self):
|
||||
"""Return parameters that are common to v3 plugins."""
|
||||
return {'trust_id': self._trust_id,
|
||||
'project_id': self._project_id,
|
||||
'project_name': self._project_name,
|
||||
'project_domain_id': self._project_domain_id,
|
||||
'project_domain_name': self._project_domain_name,
|
||||
'domain_id': self._domain_id,
|
||||
'domain_name': self._domain_name}
|
||||
|
||||
def _do_create_plugin(self, session):
|
||||
plugin = None
|
||||
|
||||
try:
|
||||
disc = self.get_discovery(session,
|
||||
self.auth_url,
|
||||
authenticated=False)
|
||||
except (exceptions.DiscoveryFailure,
|
||||
exceptions.HTTPError,
|
||||
exceptions.ConnectionError):
|
||||
LOG.warning('Discovering versions from the identity service '
|
||||
'failed when creating the password plugin. '
|
||||
'Attempting to determine version from URL.')
|
||||
|
||||
url_parts = urlparse.urlparse(self.auth_url)
|
||||
path = url_parts.path.lower()
|
||||
|
||||
if path.startswith('/v2.0') and not self._has_domain_scope:
|
||||
plugin = self.create_plugin(session, (2, 0), self.auth_url)
|
||||
elif path.startswith('/v3'):
|
||||
plugin = self.create_plugin(session, (3, 0), self.auth_url)
|
||||
|
||||
else:
|
||||
disc_data = disc.version_data()
|
||||
|
||||
for data in disc_data:
|
||||
version = data['version']
|
||||
|
||||
if (_discover.version_match((2,), version) and
|
||||
self._has_domain_scope):
|
||||
# NOTE(jamielennox): if there are domain parameters there
|
||||
# is no point even trying against v2 APIs.
|
||||
continue
|
||||
|
||||
plugin = self.create_plugin(session,
|
||||
version,
|
||||
data['url'],
|
||||
raw_status=data['raw_status'])
|
||||
|
||||
if plugin:
|
||||
break
|
||||
|
||||
if plugin:
|
||||
return plugin
|
||||
|
||||
# so there were no URLs that i could use for auth of any version.
|
||||
msg = _('Could not determine a suitable URL for the plugin')
|
||||
raise exceptions.DiscoveryFailure(msg)
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
if not self._plugin:
|
||||
self._plugin = self._do_create_plugin(session)
|
||||
|
||||
return self._plugin.get_auth_ref(session, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(BaseGenericPlugin, cls).get_options()
|
||||
options.extend(get_options())
|
||||
return options
|
@@ -1,84 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient.auth.identity.generic import password
|
||||
from keystoneclient import exceptions as exc
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
class DefaultCLI(password.Password):
|
||||
"""A Plugin that provides typical authentication options for CLIs.
|
||||
|
||||
This plugin provides standard username and password authentication options
|
||||
as well as allowing users to override with a custom token and endpoint.
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, endpoint=None, token=None, **kwargs):
|
||||
super(DefaultCLI, self).__init__(**kwargs)
|
||||
|
||||
self._token = token
|
||||
self._endpoint = endpoint
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(DefaultCLI, cls).get_options()
|
||||
options.extend([cfg.StrOpt('endpoint',
|
||||
help='A URL to use instead of a catalog'),
|
||||
cfg.StrOpt('token',
|
||||
secret=True,
|
||||
help='Always use the specified token')])
|
||||
return options
|
||||
|
||||
def get_token(self, *args, **kwargs):
|
||||
if self._token:
|
||||
return self._token
|
||||
|
||||
return super(DefaultCLI, self).get_token(*args, **kwargs)
|
||||
|
||||
def get_endpoint(self, *args, **kwargs):
|
||||
if self._endpoint:
|
||||
return self._endpoint
|
||||
|
||||
return super(DefaultCLI, self).get_endpoint(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def load_from_argparse_arguments(cls, namespace, **kwargs):
|
||||
token = kwargs.get('token') or namespace.os_token
|
||||
endpoint = kwargs.get('endpoint') or namespace.os_endpoint
|
||||
auth_url = kwargs.get('auth_url') or namespace.os_auth_url
|
||||
|
||||
if token and not endpoint:
|
||||
# if a user provides a token then they must also provide an
|
||||
# endpoint because we aren't fetching a token to get a catalog from
|
||||
msg = _('A service URL must be provided with a token')
|
||||
raise exc.CommandError(msg)
|
||||
elif (not token) and (not auth_url):
|
||||
# if you don't provide a token you are going to provide at least an
|
||||
# auth_url with which to authenticate.
|
||||
raise exc.CommandError(_('Expecting an auth URL via either '
|
||||
'--os-auth-url or env[OS_AUTH_URL]'))
|
||||
|
||||
plugin = super(DefaultCLI, cls).load_from_argparse_arguments(namespace,
|
||||
**kwargs)
|
||||
|
||||
if (not token) and (not plugin._password):
|
||||
# we do this after the load so that the base plugin has an
|
||||
# opportunity to prompt the user for a password
|
||||
raise exc.CommandError(_('Expecting a password provided via '
|
||||
'either --os-password, env[OS_PASSWORD], '
|
||||
'or prompted response'))
|
||||
|
||||
return plugin
|
@@ -1,89 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient import _discover
|
||||
from keystoneclient.auth.identity.generic import base
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient import utils
|
||||
|
||||
|
||||
def get_options():
|
||||
return [
|
||||
cfg.StrOpt('user-id', help='User id'),
|
||||
cfg.StrOpt('username', dest='username', help='Username',
|
||||
deprecated_name='user-name'),
|
||||
cfg.StrOpt('user-domain-id', help="User's domain id"),
|
||||
cfg.StrOpt('user-domain-name', help="User's domain name"),
|
||||
cfg.StrOpt('password', secret=True, help="User's password"),
|
||||
]
|
||||
|
||||
|
||||
class Password(base.BaseGenericPlugin):
|
||||
"""A common user/password authentication plugin.
|
||||
|
||||
:param string username: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string password: Password for authentication.
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, auth_url, username=None, user_id=None, password=None,
|
||||
user_domain_id=None, user_domain_name=None, **kwargs):
|
||||
super(Password, self).__init__(auth_url=auth_url, **kwargs)
|
||||
|
||||
self._username = username
|
||||
self._user_id = user_id
|
||||
self._password = password
|
||||
self._user_domain_id = user_domain_id
|
||||
self._user_domain_name = user_domain_name
|
||||
|
||||
def create_plugin(self, session, version, url, raw_status=None):
|
||||
if _discover.version_match((2,), version):
|
||||
if self._user_domain_id or self._user_domain_name:
|
||||
# If you specify any domain parameters it won't work so quit.
|
||||
return None
|
||||
|
||||
return v2.Password(auth_url=url,
|
||||
user_id=self._user_id,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
**self._v2_params)
|
||||
|
||||
elif _discover.version_match((3,), version):
|
||||
return v3.Password(auth_url=url,
|
||||
user_id=self._user_id,
|
||||
username=self._username,
|
||||
user_domain_id=self._user_domain_id,
|
||||
user_domain_name=self._user_domain_name,
|
||||
password=self._password,
|
||||
**self._v3_params)
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Password, cls).get_options()
|
||||
options.extend(get_options())
|
||||
return options
|
||||
|
||||
@classmethod
|
||||
def load_from_argparse_arguments(cls, namespace, **kwargs):
|
||||
if not (kwargs.get('password') or namespace.os_password):
|
||||
kwargs['password'] = utils.prompt_user_password()
|
||||
|
||||
return super(Password, cls).load_from_argparse_arguments(namespace,
|
||||
**kwargs)
|
@@ -1,48 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystoneclient import _discover
|
||||
from keystoneclient.auth.identity.generic import base
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
|
||||
|
||||
def get_options():
|
||||
return [
|
||||
cfg.StrOpt('token', secret=True, help='Token to authenticate with'),
|
||||
]
|
||||
|
||||
|
||||
class Token(base.BaseGenericPlugin):
|
||||
"""Generic token auth plugin.
|
||||
|
||||
:param string token: Token for authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url, token=None, **kwargs):
|
||||
super(Token, self).__init__(auth_url, **kwargs)
|
||||
self._token = token
|
||||
|
||||
def create_plugin(self, session, version, url, raw_status=None):
|
||||
if _discover.version_match((2,), version):
|
||||
return v2.Token(url, self._token, **self._v2_params)
|
||||
|
||||
elif _discover.version_match((3,), version):
|
||||
return v3.Token(url, self._token, **self._v3_params)
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Token, cls).get_options()
|
||||
options.extend(get_options())
|
||||
return options
|
@@ -1,242 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
from positional import positional
|
||||
import six
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient import utils
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
"""Identity V2 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string tenant_id: Tenant ID for project scoping.
|
||||
:param string tenant_name: Tenant name for project scoping.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Auth, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('tenant-id', help='Tenant ID'),
|
||||
cfg.StrOpt('tenant-name', help='Tenant Name'),
|
||||
cfg.StrOpt('trust-id', help='Trust ID'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
@positional()
|
||||
def __init__(self, auth_url,
|
||||
trust_id=None,
|
||||
tenant_id=None,
|
||||
tenant_name=None,
|
||||
reauthenticate=True):
|
||||
super(Auth, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=reauthenticate)
|
||||
|
||||
self._trust_id = trust_id
|
||||
self.tenant_id = tenant_id
|
||||
self.tenant_name = tenant_name
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
# Override to remove deprecation.
|
||||
return self._trust_id
|
||||
|
||||
@trust_id.setter
|
||||
def trust_id(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._trust_id = value
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
headers = {'Accept': 'application/json'}
|
||||
url = self.auth_url.rstrip('/') + '/tokens'
|
||||
params = {'auth': self.get_auth_data(headers)}
|
||||
|
||||
if self.tenant_id:
|
||||
params['auth']['tenantId'] = self.tenant_id
|
||||
elif self.tenant_name:
|
||||
params['auth']['tenantName'] = self.tenant_name
|
||||
if self.trust_id:
|
||||
params['auth']['trust_id'] = self.trust_id
|
||||
|
||||
_logger.debug('Making authentication request to %s', url)
|
||||
resp = session.post(url, json=params, headers=headers,
|
||||
authenticated=False, log=False)
|
||||
|
||||
try:
|
||||
resp_data = resp.json()['access']
|
||||
except (KeyError, ValueError):
|
||||
raise exceptions.InvalidResponse(response=resp)
|
||||
|
||||
return access.AccessInfoV2(**resp_data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_data(self, headers=None):
|
||||
"""Return the authentication section of an auth plugin.
|
||||
|
||||
:param dict headers: The headers that will be sent with the auth
|
||||
request if a plugin needs to add to them.
|
||||
:return: A dict of authentication data for the auth type.
|
||||
:rtype: dict
|
||||
"""
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
_NOT_PASSED = object()
|
||||
|
||||
|
||||
class Password(Auth):
|
||||
"""A plugin for authenticating with a username and password.
|
||||
|
||||
A username or user_id must be provided.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string username: Username for authentication.
|
||||
:param string password: Password for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string tenant_id: Tenant ID for tenant scoping.
|
||||
:param string tenant_name: Tenant name for tenant scoping.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
|
||||
:raises TypeError: if a user_id or username is not provided.
|
||||
"""
|
||||
|
||||
@positional(4)
|
||||
def __init__(self, auth_url, username=_NOT_PASSED, password=None,
|
||||
user_id=_NOT_PASSED, **kwargs):
|
||||
super(Password, self).__init__(auth_url, **kwargs)
|
||||
|
||||
if username is _NOT_PASSED and user_id is _NOT_PASSED:
|
||||
msg = 'You need to specify either a username or user_id'
|
||||
raise TypeError(msg)
|
||||
|
||||
if username is _NOT_PASSED:
|
||||
username = None
|
||||
if user_id is _NOT_PASSED:
|
||||
user_id = None
|
||||
|
||||
self.user_id = user_id
|
||||
self._username = username
|
||||
self._password = password
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
# Override to remove deprecation.
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._username = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
# Override to remove deprecation.
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._password = value
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
auth = {'password': self.password}
|
||||
|
||||
if self.username:
|
||||
auth['username'] = self.username
|
||||
elif self.user_id:
|
||||
auth['userId'] = self.user_id
|
||||
|
||||
return {'passwordCredentials': auth}
|
||||
|
||||
@classmethod
|
||||
def load_from_argparse_arguments(cls, namespace, **kwargs):
|
||||
if not (kwargs.get('password') or namespace.os_password):
|
||||
kwargs['password'] = utils.prompt_user_password()
|
||||
|
||||
return super(Password, cls).load_from_argparse_arguments(namespace,
|
||||
**kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Password, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('username',
|
||||
dest='username',
|
||||
deprecated_name='user-name',
|
||||
help='Username to login with'),
|
||||
cfg.StrOpt('user-id', help='User ID to login with'),
|
||||
cfg.StrOpt('password', secret=True, help='Password to use'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
|
||||
class Token(Auth):
|
||||
"""A plugin for authenticating with an existing token.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string token: Existing token for authentication.
|
||||
:param string tenant_id: Tenant ID for tenant scoping.
|
||||
:param string tenant_name: Tenant name for tenant scoping.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
super(Token, self).__init__(auth_url, **kwargs)
|
||||
self._token = token
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
# Override to remove deprecation.
|
||||
return self._token
|
||||
|
||||
@token.setter
|
||||
def token(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._token = value
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
if headers is not None:
|
||||
headers['X-Auth-Token'] = self.token
|
||||
return {'token': {'id': self.token}}
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Token, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('token', secret=True, help='Token'),
|
||||
])
|
||||
|
||||
return options
|
@@ -1,30 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.auth.identity.v3.base import * # noqa
|
||||
from keystoneclient.auth.identity.v3.federated import * # noqa
|
||||
from keystoneclient.auth.identity.v3.password import * # noqa
|
||||
from keystoneclient.auth.identity.v3.token import * # noqa
|
||||
|
||||
|
||||
__all__ = ('Auth',
|
||||
'AuthConstructor',
|
||||
'AuthMethod',
|
||||
'BaseAuth',
|
||||
|
||||
'FederatedBaseAuth',
|
||||
|
||||
'Password',
|
||||
'PasswordMethod',
|
||||
|
||||
'Token',
|
||||
'TokenMethod')
|
@@ -1,265 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import json
|
||||
import logging
|
||||
|
||||
from oslo_config import cfg
|
||||
from positional import positional
|
||||
import six
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth')
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuth(base.BaseIdentityPlugin):
|
||||
"""Identity V3 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param List auth_methods: A collection of methods to authenticate with.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string project_id: Project ID for project scoping.
|
||||
:param string project_name: Project name for project scoping.
|
||||
:param string project_domain_id: Project's domain ID for project.
|
||||
:param string project_domain_name: Project's domain name for project.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
:param bool include_catalog: Include the service catalog in the returned
|
||||
token. (optional) default True.
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, auth_url,
|
||||
trust_id=None,
|
||||
domain_id=None,
|
||||
domain_name=None,
|
||||
project_id=None,
|
||||
project_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
reauthenticate=True,
|
||||
include_catalog=True):
|
||||
super(BaseAuth, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=reauthenticate)
|
||||
self._trust_id = trust_id
|
||||
self.domain_id = domain_id
|
||||
self.domain_name = domain_name
|
||||
self.project_id = project_id
|
||||
self.project_name = project_name
|
||||
self.project_domain_id = project_domain_id
|
||||
self.project_domain_name = project_domain_name
|
||||
self.include_catalog = include_catalog
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
# Override to remove deprecation.
|
||||
return self._trust_id
|
||||
|
||||
@trust_id.setter
|
||||
def trust_id(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._trust_id = value
|
||||
|
||||
@property
|
||||
def token_url(self):
|
||||
"""The full URL where we will send authentication data."""
|
||||
return '%s/auth/tokens' % self.auth_url.rstrip('/')
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
return None # pragma: no cover
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(BaseAuth, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('domain-id', help='Domain ID to scope to'),
|
||||
cfg.StrOpt('domain-name', help='Domain name to scope to'),
|
||||
cfg.StrOpt('project-id', help='Project ID to scope to'),
|
||||
cfg.StrOpt('project-name', help='Project name to scope to'),
|
||||
cfg.StrOpt('project-domain-id',
|
||||
help='Domain ID containing project'),
|
||||
cfg.StrOpt('project-domain-name',
|
||||
help='Domain name containing project'),
|
||||
cfg.StrOpt('trust-id', help='Trust ID'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
|
||||
class Auth(BaseAuth):
|
||||
"""Identity V3 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param List auth_methods: A collection of methods to authenticate with.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string project_id: Project ID for project scoping.
|
||||
:param string project_name: Project name for project scoping.
|
||||
:param string project_domain_id: Project's domain ID for project.
|
||||
:param string project_domain_name: Project's domain name for project.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
:param bool include_catalog: Include the service catalog in the returned
|
||||
token. (optional) default True.
|
||||
:param bool unscoped: Force the return of an unscoped token. This will make
|
||||
the keystone server return an unscoped token even if
|
||||
a default_project_id is set for this user.
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url, auth_methods, **kwargs):
|
||||
self.unscoped = kwargs.pop('unscoped', False)
|
||||
super(Auth, self).__init__(auth_url=auth_url, **kwargs)
|
||||
self.auth_methods = auth_methods
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
headers = {'Accept': 'application/json'}
|
||||
body = {'auth': {'identity': {}}}
|
||||
ident = body['auth']['identity']
|
||||
rkwargs = {}
|
||||
|
||||
for method in self.auth_methods:
|
||||
name, auth_data = method.get_auth_data(session,
|
||||
self,
|
||||
headers,
|
||||
request_kwargs=rkwargs)
|
||||
ident.setdefault('methods', []).append(name)
|
||||
ident[name] = auth_data
|
||||
|
||||
if not ident:
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_('Authentication method required (e.g. password)'))
|
||||
|
||||
mutual_exclusion = [bool(self.domain_id or self.domain_name),
|
||||
bool(self.project_id or self.project_name),
|
||||
bool(self.trust_id),
|
||||
bool(self.unscoped)]
|
||||
|
||||
if sum(mutual_exclusion) > 1:
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_('Authentication cannot be scoped to multiple targets. Pick '
|
||||
'one of: project, domain, trust or unscoped'))
|
||||
|
||||
if self.domain_id:
|
||||
body['auth']['scope'] = {'domain': {'id': self.domain_id}}
|
||||
elif self.domain_name:
|
||||
body['auth']['scope'] = {'domain': {'name': self.domain_name}}
|
||||
elif self.project_id:
|
||||
body['auth']['scope'] = {'project': {'id': self.project_id}}
|
||||
elif self.project_name:
|
||||
scope = body['auth']['scope'] = {'project': {}}
|
||||
scope['project']['name'] = self.project_name
|
||||
|
||||
if self.project_domain_id:
|
||||
scope['project']['domain'] = {'id': self.project_domain_id}
|
||||
elif self.project_domain_name:
|
||||
scope['project']['domain'] = {'name': self.project_domain_name}
|
||||
elif self.trust_id:
|
||||
body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}}
|
||||
elif self.unscoped:
|
||||
body['auth']['scope'] = {'unscoped': {}}
|
||||
|
||||
# NOTE(jamielennox): we add nocatalog here rather than in token_url
|
||||
# directly as some federation plugins require the base token_url
|
||||
token_url = self.token_url
|
||||
if not self.include_catalog:
|
||||
token_url += '?nocatalog'
|
||||
|
||||
_logger.debug('Making authentication request to %s', token_url)
|
||||
resp = session.post(token_url, json=body, headers=headers,
|
||||
authenticated=False, log=False, **rkwargs)
|
||||
|
||||
try:
|
||||
_logger.debug(json.dumps(resp.json()))
|
||||
resp_data = resp.json()['token']
|
||||
except (KeyError, ValueError):
|
||||
raise exceptions.InvalidResponse(response=resp)
|
||||
|
||||
return access.AccessInfoV3(resp.headers['X-Subject-Token'],
|
||||
**resp_data)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AuthMethod(object):
|
||||
"""One part of a V3 Authentication strategy.
|
||||
|
||||
V3 Tokens allow multiple methods to be presented when authentication
|
||||
against the server. Each one of these methods is implemented by an
|
||||
AuthMethod.
|
||||
|
||||
Note: When implementing an AuthMethod use the method_parameters
|
||||
and do not use positional arguments. Otherwise they can't be picked up by
|
||||
the factory method and don't work as well with AuthConstructors.
|
||||
"""
|
||||
|
||||
_method_parameters = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for param in self._method_parameters:
|
||||
setattr(self, param, kwargs.pop(param, None))
|
||||
|
||||
if kwargs:
|
||||
msg = _("Unexpected Attributes: %s") % ", ".join(kwargs)
|
||||
raise AttributeError(msg)
|
||||
|
||||
@classmethod
|
||||
def _extract_kwargs(cls, kwargs):
|
||||
"""Remove parameters related to this method from other kwargs."""
|
||||
return dict([(p, kwargs.pop(p, None))
|
||||
for p in cls._method_parameters])
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
"""Return the authentication section of an auth plugin.
|
||||
|
||||
:param session: The communication session.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param base.Auth auth: The auth plugin calling the method.
|
||||
:param dict headers: The headers that will be sent with the auth
|
||||
request if a plugin needs to add to them.
|
||||
:return: The identifier of this plugin and a dict of authentication
|
||||
data for the auth type.
|
||||
:rtype: tuple(string, dict)
|
||||
"""
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AuthConstructor(Auth):
|
||||
"""Abstract base class for creating an Auth Plugin.
|
||||
|
||||
The Auth Plugin created contains only one authentication method. This
|
||||
is generally the required usage.
|
||||
|
||||
An AuthConstructor creates an AuthMethod based on the method's
|
||||
arguments and the auth_method_class defined by the plugin. It then
|
||||
creates the auth plugin with only that authentication method.
|
||||
"""
|
||||
|
||||
_auth_method_class = None
|
||||
|
||||
def __init__(self, auth_url, *args, **kwargs):
|
||||
method_kwargs = self._auth_method_class._extract_kwargs(kwargs)
|
||||
method = self._auth_method_class(*args, **method_kwargs)
|
||||
super(AuthConstructor, self).__init__(auth_url, [method], **kwargs)
|
@@ -1,121 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from keystoneclient.auth.identity.v3 import base
|
||||
from keystoneclient.auth.identity.v3 import token
|
||||
|
||||
__all__ = ('FederatedBaseAuth',)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class FederatedBaseAuth(base.BaseAuth):
|
||||
|
||||
rescoping_plugin = token.Token
|
||||
|
||||
def __init__(self, auth_url, identity_provider, protocol, **kwargs):
|
||||
"""Class constructor for federated authentication plugins.
|
||||
|
||||
Accepting following parameters:
|
||||
|
||||
:param auth_url: URL of the Identity Service
|
||||
:type auth_url: string
|
||||
:param identity_provider: Name of the Identity Provider the client
|
||||
will authenticate against. This parameter
|
||||
will be used to build a dynamic URL used to
|
||||
obtain unscoped OpenStack token.
|
||||
:type identity_provider: string
|
||||
:param protocol: Protocol name configured on the keystone service
|
||||
provider side
|
||||
:type protocol: string
|
||||
|
||||
"""
|
||||
super(FederatedBaseAuth, self).__init__(auth_url=auth_url, **kwargs)
|
||||
self.identity_provider = identity_provider
|
||||
self.protocol = protocol
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(FederatedBaseAuth, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('identity-provider',
|
||||
help="Identity Provider's name"),
|
||||
cfg.StrOpt('protocol', help="Name of the federated protocol used "
|
||||
"for federated authentication. Must "
|
||||
"match its counterpart name "
|
||||
"configured at the keystone service "
|
||||
"provider. Typically values would be "
|
||||
"'saml2' or 'oidc'.")
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
@property
|
||||
def federated_token_url(self):
|
||||
"""Full URL where authorization data is sent."""
|
||||
values = {
|
||||
'host': self.auth_url.rstrip('/'),
|
||||
'identity_provider': self.identity_provider,
|
||||
'protocol': self.protocol
|
||||
}
|
||||
url = ("%(host)s/OS-FEDERATION/identity_providers/"
|
||||
"%(identity_provider)s/protocols/%(protocol)s/auth")
|
||||
url = url % values
|
||||
|
||||
return url
|
||||
|
||||
def _get_scoping_data(self):
|
||||
return {'trust_id': self.trust_id,
|
||||
'domain_id': self.domain_id,
|
||||
'domain_name': self.domain_name,
|
||||
'project_id': self.project_id,
|
||||
'project_name': self.project_name,
|
||||
'project_domain_id': self.project_domain_id,
|
||||
'project_domain_name': self.project_domain_name}
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
"""Authenticate retrieve token information.
|
||||
|
||||
This is a multi-step process where a client does federated authn
|
||||
receives an unscoped token.
|
||||
|
||||
If an unscoped token is successfully received and scoping information
|
||||
is present then the token is rescoped to that target.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: a token data representation
|
||||
:rtype: :py:class:`keystoneclient.access.AccessInfo`
|
||||
|
||||
"""
|
||||
auth_ref = self.get_unscoped_auth_ref(session)
|
||||
scoping = self._get_scoping_data()
|
||||
|
||||
if any(scoping.values()):
|
||||
token_plugin = self.rescoping_plugin(self.auth_url,
|
||||
token=auth_ref.auth_token,
|
||||
**scoping)
|
||||
|
||||
auth_ref = token_plugin.get_auth_ref(session)
|
||||
|
||||
return auth_ref
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_unscoped_auth_ref(self, session, **kwargs):
|
||||
"""Fetch unscoped federated token."""
|
||||
pass # pragma: no cover
|
@@ -1,97 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystoneclient.auth.identity.v3 import base
|
||||
from keystoneclient import utils
|
||||
|
||||
|
||||
__all__ = ('PasswordMethod', 'Password')
|
||||
|
||||
|
||||
class PasswordMethod(base.AuthMethod):
|
||||
"""Construct a User/Password based authentication method.
|
||||
|
||||
:param string password: Password for authentication.
|
||||
:param string username: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
"""
|
||||
|
||||
_method_parameters = ['user_id',
|
||||
'username',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'password']
|
||||
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
user = {'password': self.password}
|
||||
|
||||
if self.user_id:
|
||||
user['id'] = self.user_id
|
||||
elif self.username:
|
||||
user['name'] = self.username
|
||||
|
||||
if self.user_domain_id:
|
||||
user['domain'] = {'id': self.user_domain_id}
|
||||
elif self.user_domain_name:
|
||||
user['domain'] = {'name': self.user_domain_name}
|
||||
|
||||
return 'password', {'user': user}
|
||||
|
||||
|
||||
class Password(base.AuthConstructor):
|
||||
"""A plugin for authenticating with a username and password.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param string password: Password for authentication.
|
||||
:param string username: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string project_id: Project ID for project scoping.
|
||||
:param string project_name: Project name for project scoping.
|
||||
:param string project_domain_id: Project's domain ID for project.
|
||||
:param string project_domain_name: Project's domain name for project.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
"""
|
||||
|
||||
_auth_method_class = PasswordMethod
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Password, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('user-id', help='User ID'),
|
||||
cfg.StrOpt('username', dest='username', help='Username',
|
||||
deprecated_name='user-name'),
|
||||
cfg.StrOpt('user-domain-id', help="User's domain id"),
|
||||
cfg.StrOpt('user-domain-name', help="User's domain name"),
|
||||
cfg.StrOpt('password', secret=True, help="User's password"),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
@classmethod
|
||||
def load_from_argparse_arguments(cls, namespace, **kwargs):
|
||||
if not (kwargs.get('password') or namespace.os_password):
|
||||
kwargs['password'] = utils.prompt_user_password()
|
||||
|
||||
return super(Password, cls).load_from_argparse_arguments(namespace,
|
||||
**kwargs)
|
@@ -1,65 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystoneclient.auth.identity.v3 import base
|
||||
|
||||
|
||||
__all__ = ('TokenMethod', 'Token')
|
||||
|
||||
|
||||
class TokenMethod(base.AuthMethod):
|
||||
"""Construct an Auth plugin to fetch a token from a token.
|
||||
|
||||
:param string token: Token for authentication.
|
||||
"""
|
||||
|
||||
_method_parameters = ['token']
|
||||
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
headers['X-Auth-Token'] = self.token
|
||||
return 'token', {'id': self.token}
|
||||
|
||||
|
||||
class Token(base.AuthConstructor):
|
||||
"""A plugin for authenticating with an existing Token.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param string token: Token for authentication.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string project_id: Project ID for project scoping.
|
||||
:param string project_name: Project name for project scoping.
|
||||
:param string project_domain_id: Project's domain ID for project.
|
||||
:param string project_domain_name: Project's domain name for project.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
"""
|
||||
|
||||
_auth_method_class = TokenMethod
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
super(Token, self).__init__(auth_url, token=token, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Token, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('token',
|
||||
secret=True,
|
||||
help='Token to authenticate with'),
|
||||
])
|
||||
|
||||
return options
|
@@ -1,62 +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 warnings
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystoneclient.auth import base
|
||||
|
||||
|
||||
class Token(base.BaseAuthPlugin):
|
||||
"""A provider that will always use the given token and endpoint.
|
||||
|
||||
This is really only useful for testing and in certain CLI cases where you
|
||||
have a known endpoint and admin token that you want to use.
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint, token):
|
||||
# NOTE(jamielennox): endpoint is reserved for when plugins
|
||||
# can be used to provide that information
|
||||
warnings.warn(
|
||||
'TokenEndpoint plugin is deprecated as of the 2.1.0 release in '
|
||||
'favor of keystoneauth1.token_endpoint.Token. It will be removed '
|
||||
'in future releases.',
|
||||
DeprecationWarning)
|
||||
|
||||
self.endpoint = endpoint
|
||||
self.token = token
|
||||
|
||||
def get_token(self, session):
|
||||
return self.token
|
||||
|
||||
def get_endpoint(self, session, **kwargs):
|
||||
"""Return the supplied endpoint.
|
||||
|
||||
Using this plugin the same endpoint is returned regardless of the
|
||||
parameters passed to the plugin.
|
||||
"""
|
||||
return self.endpoint
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(Token, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('endpoint',
|
||||
help='The endpoint that will always be used'),
|
||||
cfg.StrOpt('token',
|
||||
secret=True,
|
||||
help='The token that will always be used'),
|
||||
])
|
||||
|
||||
return options
|
@@ -1,549 +0,0 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Base utilities to build API operation managers and objects on top of."""
|
||||
|
||||
import abc
|
||||
import copy
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
from keystoneauth1 import plugin
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from keystoneclient import exceptions as ksc_exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def filter_none(**kwargs):
|
||||
"""Remove any entries from a dictionary where the value is None."""
|
||||
return dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||
|
||||
|
||||
def filter_kwargs(f):
|
||||
@functools.wraps(f)
|
||||
def func(*args, **kwargs):
|
||||
new_kwargs = {}
|
||||
for key, ref in kwargs.items():
|
||||
if ref is None:
|
||||
# drop null values
|
||||
continue
|
||||
|
||||
id_value = getid(ref)
|
||||
if id_value != ref:
|
||||
# If an object with an id was passed, then use the id, e.g.:
|
||||
# user: user(id=1) becomes user_id: 1
|
||||
key = '%s_id' % key
|
||||
|
||||
new_kwargs[key] = id_value
|
||||
|
||||
return f(*args, **new_kwargs)
|
||||
return func
|
||||
|
||||
|
||||
class Manager(object):
|
||||
"""Basic manager type providing common operations.
|
||||
|
||||
Managers interact with a particular type of API (servers, flavors, images,
|
||||
etc.) and provide CRUD operations for them.
|
||||
|
||||
:param client: instance of BaseClient descendant for HTTP requests
|
||||
|
||||
"""
|
||||
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, client):
|
||||
super(Manager, self).__init__()
|
||||
self.client = client
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
"""The client.
|
||||
|
||||
.. warning::
|
||||
|
||||
This property is deprecated as of the 1.7.0 release in favor of
|
||||
:meth:`client` and may be removed in the 2.0.0 release.
|
||||
|
||||
"""
|
||||
warnings.warn(
|
||||
'api is deprecated as of the 1.7.0 release in favor of client and '
|
||||
'may be removed in the 2.0.0 release', DeprecationWarning)
|
||||
return self.client
|
||||
|
||||
def _list(self, url, response_key, obj_class=None, body=None, **kwargs):
|
||||
"""List the collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param obj_class: class for constructing the returned objects
|
||||
(self.resource_class will be used by default)
|
||||
:param body: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
if body:
|
||||
resp, body = self.client.post(url, body=body, **kwargs)
|
||||
else:
|
||||
resp, body = self.client.get(url, **kwargs)
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
data = body[response_key]
|
||||
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
||||
# unlike other services which just return the list...
|
||||
try:
|
||||
data = data['values']
|
||||
except (KeyError, TypeError): # nosec(cjschaef): keystone data values
|
||||
# not as expected (see comment above), assumption is that values
|
||||
# are already returned in a list (so simply utilize that list)
|
||||
pass
|
||||
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def _get(self, url, response_key, **kwargs):
|
||||
"""Get an object from collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'server'
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
resp, body = self.client.get(url, **kwargs)
|
||||
return self.resource_class(self, body[response_key], loaded=True)
|
||||
|
||||
def _head(self, url, **kwargs):
|
||||
"""Retrieve request headers for an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
resp, body = self.client.head(url, **kwargs)
|
||||
return resp.status_code == 204
|
||||
|
||||
def _post(self, url, body, response_key, return_raw=False, **kwargs):
|
||||
"""Create an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param body: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param return_raw: flag to force returning raw JSON instead of
|
||||
Python object of self.resource_class
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
resp, body = self.client.post(url, body=body, **kwargs)
|
||||
if return_raw:
|
||||
return body[response_key]
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
def _put(self, url, body=None, response_key=None, **kwargs):
|
||||
"""Update an object with PUT method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param body: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
resp, body = self.client.put(url, body=body, **kwargs)
|
||||
# PUT requests may not return a body
|
||||
if body is not None:
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _patch(self, url, body=None, response_key=None, **kwargs):
|
||||
"""Update an object with PATCH method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param body: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
resp, body = self.client.patch(url, body=body, **kwargs)
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _delete(self, url, **kwargs):
|
||||
"""Delete an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers/my-server'
|
||||
:param kwargs: Additional arguments will be passed to the request.
|
||||
"""
|
||||
return self.client.delete(url, **kwargs)
|
||||
|
||||
def _update(self, url, body=None, response_key=None, method="PUT",
|
||||
**kwargs):
|
||||
methods = {"PUT": self.client.put,
|
||||
"POST": self.client.post,
|
||||
"PATCH": self.client.patch}
|
||||
try:
|
||||
resp, body = methods[method](url, body=body,
|
||||
**kwargs)
|
||||
except KeyError:
|
||||
raise ksc_exceptions.ClientException(_("Invalid update method: %s")
|
||||
% method)
|
||||
# PUT requests may not return a body
|
||||
if body:
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ManagerWithFind(Manager):
|
||||
"""Manager with additional `find()`/`findall()` methods."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list(self):
|
||||
pass # pragma: no cover
|
||||
|
||||
def find(self, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
rl = self.findall(**kwargs)
|
||||
num = len(rl)
|
||||
|
||||
if num == 0:
|
||||
msg = _("No %(name)s matching %(kwargs)s.") % {
|
||||
'name': self.resource_class.__name__, 'kwargs': kwargs}
|
||||
raise ksa_exceptions.NotFound(404, msg)
|
||||
elif num > 1:
|
||||
raise ksc_exceptions.NoUniqueMatch
|
||||
else:
|
||||
return rl[0]
|
||||
|
||||
def findall(self, **kwargs):
|
||||
"""Find all items with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
found = []
|
||||
searches = kwargs.items()
|
||||
|
||||
for obj in self.list():
|
||||
try:
|
||||
if all(getattr(obj, attr) == value
|
||||
for (attr, value) in searches):
|
||||
found.append(obj)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
return found
|
||||
|
||||
|
||||
class CrudManager(Manager):
|
||||
"""Base manager class for manipulating Keystone entities.
|
||||
|
||||
Children of this class are expected to define a `collection_key` and `key`.
|
||||
|
||||
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
|
||||
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
|
||||
objects containing a list of member resources (e.g. `{'entities': [{},
|
||||
{}, {}]}`).
|
||||
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
|
||||
refer to an individual member of the collection.
|
||||
|
||||
"""
|
||||
|
||||
collection_key = None
|
||||
key = None
|
||||
base_url = None
|
||||
|
||||
def build_url(self, dict_args_in_out=None):
|
||||
"""Build a resource URL for the given kwargs.
|
||||
|
||||
Given an example collection where `collection_key = 'entities'` and
|
||||
`key = 'entity'`, the following URL's could be generated.
|
||||
|
||||
By default, the URL will represent a collection of entities, e.g.::
|
||||
|
||||
/entities
|
||||
|
||||
If kwargs contains an `entity_id`, then the URL will represent a
|
||||
specific member, e.g.::
|
||||
|
||||
/entities/{entity_id}
|
||||
|
||||
If a `base_url` is provided, the generated URL will be appended to it.
|
||||
|
||||
If a 'tail' is provided, it will be appended to the end of the URL.
|
||||
|
||||
"""
|
||||
if dict_args_in_out is None:
|
||||
dict_args_in_out = {}
|
||||
|
||||
url = dict_args_in_out.pop('base_url', None) or self.base_url or ''
|
||||
url += '/%s' % self.collection_key
|
||||
|
||||
# do we have a specific entity?
|
||||
entity_id = dict_args_in_out.pop('%s_id' % self.key, None)
|
||||
if entity_id is not None:
|
||||
url += '/%s' % entity_id
|
||||
|
||||
if dict_args_in_out.get('tail'):
|
||||
url += dict_args_in_out['tail']
|
||||
|
||||
return url
|
||||
|
||||
@filter_kwargs
|
||||
def create(self, **kwargs):
|
||||
url = self.build_url(dict_args_in_out=kwargs)
|
||||
return self._post(
|
||||
url,
|
||||
{self.key: kwargs},
|
||||
self.key)
|
||||
|
||||
@filter_kwargs
|
||||
def get(self, **kwargs):
|
||||
return self._get(
|
||||
self.build_url(dict_args_in_out=kwargs),
|
||||
self.key)
|
||||
|
||||
@filter_kwargs
|
||||
def head(self, **kwargs):
|
||||
return self._head(self.build_url(dict_args_in_out=kwargs))
|
||||
|
||||
def _build_query(self, params):
|
||||
if params is None:
|
||||
return ''
|
||||
else:
|
||||
return '?%s' % urllib.parse.urlencode(params, doseq=True)
|
||||
|
||||
def build_key_only_query(self, params_list):
|
||||
"""Build a query that does not include values, just keys.
|
||||
|
||||
The Identity API has some calls that define queries without values,
|
||||
this can not be accomplished by using urllib.parse.urlencode(). This
|
||||
method builds a query using only the keys.
|
||||
"""
|
||||
return '?%s' % '&'.join(params_list) if params_list else ''
|
||||
|
||||
@filter_kwargs
|
||||
def list(self, fallback_to_auth=False, **kwargs):
|
||||
if 'id' in kwargs.keys():
|
||||
# Ensure that users are not trying to call things like
|
||||
# ``domains.list(id='default')`` when they should have used
|
||||
# ``[domains.get(domain_id='default')]`` instead. Keystone supports
|
||||
# ``GET /v3/domains/{domain_id}``, not ``GET
|
||||
# /v3/domains?id={domain_id}``.
|
||||
raise TypeError(
|
||||
_("list() got an unexpected keyword argument 'id'. To "
|
||||
"retrieve a single object using a globally unique "
|
||||
"identifier, try using get() instead."))
|
||||
|
||||
url = self.build_url(dict_args_in_out=kwargs)
|
||||
|
||||
try:
|
||||
query = self._build_query(kwargs)
|
||||
url_query = '%(url)s%(query)s' % {'url': url, 'query': query}
|
||||
return self._list(
|
||||
url_query,
|
||||
self.collection_key)
|
||||
except ksa_exceptions.EmptyCatalog:
|
||||
if fallback_to_auth:
|
||||
return self._list(
|
||||
url_query,
|
||||
self.collection_key,
|
||||
endpoint_filter={'interface': plugin.AUTH_INTERFACE})
|
||||
else:
|
||||
raise
|
||||
|
||||
@filter_kwargs
|
||||
def put(self, **kwargs):
|
||||
return self._update(
|
||||
self.build_url(dict_args_in_out=kwargs),
|
||||
method='PUT')
|
||||
|
||||
@filter_kwargs
|
||||
def update(self, **kwargs):
|
||||
url = self.build_url(dict_args_in_out=kwargs)
|
||||
|
||||
return self._update(
|
||||
url,
|
||||
{self.key: kwargs},
|
||||
self.key,
|
||||
method='PATCH')
|
||||
|
||||
@filter_kwargs
|
||||
def delete(self, **kwargs):
|
||||
return self._delete(
|
||||
self.build_url(dict_args_in_out=kwargs))
|
||||
|
||||
@filter_kwargs
|
||||
def find(self, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``."""
|
||||
url = self.build_url(dict_args_in_out=kwargs)
|
||||
|
||||
query = self._build_query(kwargs)
|
||||
url_query = '%(url)s%(query)s' % {
|
||||
'url': url,
|
||||
'query': query
|
||||
}
|
||||
elements = self._list(
|
||||
url_query,
|
||||
self.collection_key)
|
||||
|
||||
if not elements:
|
||||
msg = _("No %(name)s matching %(kwargs)s.") % {
|
||||
'name': self.resource_class.__name__, 'kwargs': kwargs}
|
||||
raise ksa_exceptions.NotFound(404, msg)
|
||||
elif len(elements) > 1:
|
||||
raise ksc_exceptions.NoUniqueMatch
|
||||
else:
|
||||
return elements[0]
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""Base class for OpenStack resources (tenant, user, etc.).
|
||||
|
||||
This is pretty much just a bag for attributes.
|
||||
"""
|
||||
|
||||
HUMAN_ID = False
|
||||
NAME_ATTR = 'name'
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
"""Populate and bind to a manager.
|
||||
|
||||
:param manager: BaseManager object
|
||||
:param info: dictionary representing resource attributes
|
||||
:param loaded: prevent lazy-loading if set to True
|
||||
"""
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def __repr__(self):
|
||||
"""Return string representation of resource attributes."""
|
||||
reprkeys = sorted(k
|
||||
for k in self.__dict__.keys()
|
||||
if k[0] != '_' and k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
||||
|
||||
@property
|
||||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion."""
|
||||
if self.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
return strutils.to_slug(name)
|
||||
return None
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in info.items():
|
||||
try:
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
except UnicodeEncodeError:
|
||||
# This happens when we're running with Python version that
|
||||
# does not support Unicode identifiers (e.g. Python 2.7).
|
||||
# In that case we can't help but not set this attrubute;
|
||||
# it'll be available in a dict representation though
|
||||
pass
|
||||
self._info[k] = v
|
||||
except AttributeError: # nosec(cjschaef): we already defined the
|
||||
# attribute on the class
|
||||
pass
|
||||
|
||||
def __getattr__(self, k):
|
||||
"""Checking attrbiute existence."""
|
||||
if k not in self.__dict__:
|
||||
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
|
||||
if not self.is_loaded():
|
||||
self.get()
|
||||
return self.__getattr__(k)
|
||||
|
||||
raise AttributeError(k)
|
||||
else:
|
||||
return self.__dict__[k]
|
||||
|
||||
def get(self):
|
||||
"""Support for lazy loading details.
|
||||
|
||||
Some clients, such as novaclient have the option to lazy load the
|
||||
details, details which can be loaded with this function.
|
||||
"""
|
||||
# set_loaded() first ... so if we have to bail, we know we tried.
|
||||
self.set_loaded(True)
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
|
||||
new = self.manager.get(self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Define equality for resources."""
|
||||
if not isinstance(other, Resource):
|
||||
return NotImplemented
|
||||
# two resources of different types are not equal
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self._info == other._info
|
||||
|
||||
def __ne__(self, other):
|
||||
"""Define inequality for resources."""
|
||||
return not self == other
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def set_loaded(self, val):
|
||||
self._loaded = val
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
||||
|
||||
def delete(self):
|
||||
return self.manager.delete(self)
|
@@ -1,46 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, session):
|
||||
warnings.warn(
|
||||
'keystoneclient.baseclient.Client is deprecated as of the 2.1.0 '
|
||||
'release. It will be removed in future releases.',
|
||||
DeprecationWarning)
|
||||
|
||||
self.session = session
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('authenticated', True)
|
||||
return self.session.request(url, method, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.request(url, 'GET', **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.request(url, 'HEAD', **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.request(url, 'POST', **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.request(url, 'PUT', **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.request(url, 'PATCH', **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.request(url, 'DELETE', **kwargs)
|
@@ -1,63 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from debtcollector import removals
|
||||
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import httpclient
|
||||
from keystoneclient import session as client_session
|
||||
|
||||
|
||||
@removals.remove(message='Use keystoneclient.httpclient.HTTPClient instead',
|
||||
version='1.7.0', removal_version='2.0.0')
|
||||
class HTTPClient(httpclient.HTTPClient):
|
||||
"""Deprecated alias for httpclient.HTTPClient.
|
||||
|
||||
This class is deprecated as of the 1.7.0 release in favor of
|
||||
:class:`keystoneclient.httpclient.HTTPClient` and may be removed in the
|
||||
2.0.0 release.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def Client(version=None, unstable=False, session=None, **kwargs):
|
||||
"""Factory function to create a new identity service client.
|
||||
|
||||
The returned client will be either a V3 or V2 client. Check the version
|
||||
using the :py:attr:`~keystoneclient.v3.client.Client.version` property or
|
||||
the instance's class (with instanceof).
|
||||
|
||||
:param tuple version: The required version of the identity API. If
|
||||
specified the client will be selected such that the
|
||||
major version is equivalent and an endpoint provides
|
||||
at least the specified minor version. For example to
|
||||
specify the 3.1 API use ``(3, 1)``. (optional)
|
||||
:param bool unstable: Accept endpoints not marked as 'stable'. (optional)
|
||||
:param session: A session object to be used for communication. If one is
|
||||
not provided it will be constructed from the provided
|
||||
kwargs. (optional)
|
||||
:type session: keystoneclient.session.Session
|
||||
:param kwargs: Additional arguments are passed through to the client
|
||||
that is being created.
|
||||
:returns: New keystone client object.
|
||||
:rtype: :py:class:`keystoneclient.v3.client.Client` or
|
||||
:py:class:`keystoneclient.v2_0.client.Client`
|
||||
:raises keystoneclient.exceptions.DiscoveryFailure: if the server's
|
||||
response is invalid.
|
||||
:raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client
|
||||
cannot be found.
|
||||
"""
|
||||
if not session:
|
||||
session = client_session.Session._construct(kwargs)
|
||||
|
||||
d = discover.Discover(session=session, **kwargs)
|
||||
return d.create_client(version=version, unstable=unstable)
|
@@ -1,444 +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.
|
||||
|
||||
"""Certificate signing functions.
|
||||
|
||||
Call set_subprocess() with the subprocess module. Either Python's
|
||||
subprocess or eventlet.green.subprocess can be used.
|
||||
|
||||
If set_subprocess() is not called, this module will pick Python's subprocess
|
||||
or eventlet.green.subprocess based on if os module is patched by eventlet.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import errno
|
||||
import hashlib
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
from debtcollector import removals
|
||||
import six
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
subprocess = None
|
||||
LOG = logging.getLogger(__name__)
|
||||
PKI_ASN1_PREFIX = 'MII'
|
||||
PKIZ_PREFIX = 'PKIZ_'
|
||||
PKIZ_CMS_FORM = 'DER'
|
||||
PKI_ASN1_FORM = 'PEM'
|
||||
DEFAULT_TOKEN_DIGEST_ALGORITHM = 'sha256'
|
||||
|
||||
|
||||
# The openssl cms command exits with these status codes.
|
||||
# See https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES
|
||||
class OpensslCmsExitStatus(object):
|
||||
SUCCESS = 0
|
||||
COMMAND_OPTIONS_PARSING_ERROR = 1
|
||||
INPUT_FILE_READ_ERROR = 2
|
||||
CREATE_CMS_READ_MIME_ERROR = 3
|
||||
|
||||
|
||||
def _ensure_subprocess():
|
||||
# NOTE(vish): late loading subprocess so we can
|
||||
# use the green version if we are in
|
||||
# eventlet.
|
||||
global subprocess
|
||||
if not subprocess:
|
||||
try:
|
||||
from eventlet import patcher
|
||||
if patcher.already_patched:
|
||||
from eventlet.green import subprocess
|
||||
else:
|
||||
import subprocess # nosec(cjschaef): we must be careful when
|
||||
# using subprocess.Popen with possibly untrusted data,
|
||||
# assumption is that the certificate/key files provided are
|
||||
# trustworthy
|
||||
except ImportError:
|
||||
import subprocess # noqa # nosec(cjschaef): we must be careful
|
||||
# when using subprocess.Popen with possibly untrusted data,
|
||||
# assumption is that the certificate/key files provided are
|
||||
# trustworthy
|
||||
|
||||
|
||||
def set_subprocess(_subprocess=None):
|
||||
"""Set subprocess module to use.
|
||||
|
||||
The subprocess could be eventlet.green.subprocess if using eventlet,
|
||||
or Python's subprocess otherwise.
|
||||
"""
|
||||
global subprocess
|
||||
subprocess = _subprocess
|
||||
|
||||
|
||||
def _check_files_accessible(files):
|
||||
err = None
|
||||
retcode = -1
|
||||
try:
|
||||
for try_file in files:
|
||||
with open(try_file, 'r'):
|
||||
pass
|
||||
except IOError as e:
|
||||
# Catching IOError means there is an issue with
|
||||
# the given file.
|
||||
err = try_file, e.strerror
|
||||
# Emulate openssl behavior, which returns with code 2 when
|
||||
# access to a file failed.
|
||||
retcode = OpensslCmsExitStatus.INPUT_FILE_READ_ERROR
|
||||
|
||||
return retcode, err
|
||||
|
||||
|
||||
def _process_communicate_handle_oserror(process, data, files):
|
||||
"""Wrapper around process.communicate that checks for OSError."""
|
||||
try:
|
||||
output, err = process.communicate(data)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EPIPE:
|
||||
raise
|
||||
# OSError with EPIPE only occurs with old Python 2.7.x versions
|
||||
# http://bugs.python.org/issue10963
|
||||
|
||||
# The quick exit is typically caused by the openssl command not being
|
||||
# able to read an input file, so check ourselves if can't read a file.
|
||||
retcode, err = _check_files_accessible(files)
|
||||
if process.stderr:
|
||||
msg = process.stderr.read()
|
||||
if isinstance(msg, six.binary_type):
|
||||
msg = msg.decode('utf-8')
|
||||
if err:
|
||||
err = (_('Hit OSError in '
|
||||
'_process_communicate_handle_oserror(): '
|
||||
'%(stderr)s\nLikely due to %(file)s: %(error)s') %
|
||||
{'stderr': msg,
|
||||
'file': err[0],
|
||||
'error': err[1]})
|
||||
else:
|
||||
err = (_('Hit OSError in '
|
||||
'_process_communicate_handle_oserror(): %s') % msg)
|
||||
|
||||
output = ''
|
||||
else:
|
||||
retcode = process.poll()
|
||||
if err is not None:
|
||||
if isinstance(err, six.binary_type):
|
||||
err = err.decode('utf-8')
|
||||
|
||||
return output, err, retcode
|
||||
|
||||
|
||||
def _encoding_for_form(inform):
|
||||
if inform == PKI_ASN1_FORM:
|
||||
encoding = 'UTF-8'
|
||||
elif inform == PKIZ_CMS_FORM:
|
||||
encoding = 'hex'
|
||||
else:
|
||||
raise ValueError(
|
||||
_('"inform" must be one of: %s') % ','.join((PKI_ASN1_FORM,
|
||||
PKIZ_CMS_FORM)))
|
||||
|
||||
return encoding
|
||||
|
||||
|
||||
def cms_verify(formatted, signing_cert_file_name, ca_file_name,
|
||||
inform=PKI_ASN1_FORM):
|
||||
"""Verify the signature of the contents IAW CMS syntax.
|
||||
|
||||
:raises subprocess.CalledProcessError:
|
||||
:raises keystoneclient.exceptions.CertificateConfigError: if certificate
|
||||
is not configured
|
||||
properly.
|
||||
"""
|
||||
_ensure_subprocess()
|
||||
if isinstance(formatted, six.string_types):
|
||||
data = bytearray(formatted, _encoding_for_form(inform))
|
||||
else:
|
||||
data = formatted
|
||||
process = subprocess.Popen(['openssl', 'cms', '-verify',
|
||||
'-certfile', signing_cert_file_name,
|
||||
'-CAfile', ca_file_name,
|
||||
'-inform', 'PEM',
|
||||
'-nosmimecap', '-nodetach',
|
||||
'-nocerts', '-noattr'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
output, err, retcode = _process_communicate_handle_oserror(
|
||||
process, data, (signing_cert_file_name, ca_file_name))
|
||||
|
||||
# Do not log errors, as some happen in the positive thread
|
||||
# instead, catch them in the calling code and log them there.
|
||||
|
||||
# When invoke the openssl >= 1.1.0 with not exist file, return code should
|
||||
# be 2 instead of 1 and error msg will be returned.
|
||||
# You can get more from
|
||||
# https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES
|
||||
#
|
||||
# $ openssl cms -verify -certfile not_exist_file -CAfile
|
||||
# not_exist_file -inform PEM -nosmimecap -nodetach
|
||||
# -nocerts -noattr
|
||||
# openssl < 1.1.0 returns
|
||||
# Error opening certificate file not_exist_file
|
||||
# openssl >= 1.1.0 returns
|
||||
# cms: Cannot open input file not_exist_file, No such file or directory
|
||||
#
|
||||
if retcode == OpensslCmsExitStatus.INPUT_FILE_READ_ERROR:
|
||||
if err.startswith('Error reading S/MIME message'):
|
||||
raise exceptions.CMSError(err)
|
||||
else:
|
||||
raise exceptions.CertificateConfigError(err)
|
||||
# workaround for OpenSSL >= 1.1.0,
|
||||
# should return OpensslCmsExitStatus.INPUT_FILE_READ_ERROR
|
||||
elif retcode == OpensslCmsExitStatus.COMMAND_OPTIONS_PARSING_ERROR:
|
||||
if err.startswith('cms: Cannot open input file'):
|
||||
raise exceptions.CertificateConfigError(err)
|
||||
else:
|
||||
raise subprocess.CalledProcessError(retcode, 'openssl', output=err)
|
||||
elif retcode != OpensslCmsExitStatus.SUCCESS:
|
||||
raise subprocess.CalledProcessError(retcode, 'openssl', output=err)
|
||||
return output
|
||||
|
||||
|
||||
def is_pkiz(token_text):
|
||||
"""Determine if a token is PKIZ.
|
||||
|
||||
Checks if the string has the prefix that indicates it is a
|
||||
Crypto Message Syntax, Z compressed token.
|
||||
"""
|
||||
return token_text.startswith(PKIZ_PREFIX)
|
||||
|
||||
|
||||
def pkiz_sign(text,
|
||||
signing_cert_file_name,
|
||||
signing_key_file_name,
|
||||
compression_level=6,
|
||||
message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM):
|
||||
signed = cms_sign_data(text,
|
||||
signing_cert_file_name,
|
||||
signing_key_file_name,
|
||||
PKIZ_CMS_FORM,
|
||||
message_digest=message_digest)
|
||||
|
||||
compressed = zlib.compress(signed, compression_level)
|
||||
encoded = PKIZ_PREFIX + base64.urlsafe_b64encode(
|
||||
compressed).decode('utf-8')
|
||||
return encoded
|
||||
|
||||
|
||||
def pkiz_uncompress(signed_text):
|
||||
text = signed_text[len(PKIZ_PREFIX):].encode('utf-8')
|
||||
unencoded = base64.urlsafe_b64decode(text)
|
||||
uncompressed = zlib.decompress(unencoded)
|
||||
return uncompressed
|
||||
|
||||
|
||||
def pkiz_verify(signed_text, signing_cert_file_name, ca_file_name):
|
||||
uncompressed = pkiz_uncompress(signed_text)
|
||||
return cms_verify(uncompressed, signing_cert_file_name, ca_file_name,
|
||||
inform=PKIZ_CMS_FORM)
|
||||
|
||||
|
||||
def token_to_cms(signed_text):
|
||||
"""Convert a custom formatted token to a PEM-formatted token.
|
||||
|
||||
See documentation for cms_to_token() for details on the custom formatting.
|
||||
"""
|
||||
copy_of_text = signed_text.replace('-', '/')
|
||||
|
||||
lines = ['-----BEGIN CMS-----']
|
||||
lines += [copy_of_text[n:n + 64] for n in range(0, len(copy_of_text), 64)]
|
||||
lines.append('-----END CMS-----\n')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def verify_token(token, signing_cert_file_name, ca_file_name):
|
||||
return cms_verify(token_to_cms(token),
|
||||
signing_cert_file_name,
|
||||
ca_file_name)
|
||||
|
||||
|
||||
def is_asn1_token(token):
|
||||
"""Determine if a token appears to be PKI-based.
|
||||
|
||||
thx to ayoung for sorting this out.
|
||||
|
||||
base64 decoded hex representation of MII is 3082::
|
||||
|
||||
In [3]: binascii.hexlify(base64.b64decode('MII='))
|
||||
Out[3]: '3082'
|
||||
|
||||
re: http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
||||
|
||||
::
|
||||
|
||||
pg4: For tags from 0 to 30 the first octet is the identfier
|
||||
pg10: Hex 30 means sequence, followed by the length of that sequence.
|
||||
pg5: Second octet is the length octet
|
||||
first bit indicates short or long form, next 7 bits encode the
|
||||
number of subsequent octets that make up the content length octets
|
||||
as an unsigned binary int
|
||||
|
||||
82 = 10000010 (first bit indicates long form)
|
||||
0000010 = 2 octets of content length
|
||||
so read the next 2 octets to get the length of the content.
|
||||
|
||||
In the case of a very large content length there could be a requirement to
|
||||
have more than 2 octets to designate the content length, therefore
|
||||
requiring us to check for MIM, MIQ, etc.
|
||||
|
||||
::
|
||||
|
||||
In [4]: base64.b64encode(binascii.a2b_hex('3083'))
|
||||
Out[4]: 'MIM='
|
||||
In [5]: base64.b64encode(binascii.a2b_hex('3084'))
|
||||
Out[5]: 'MIQ='
|
||||
Checking for MI would become invalid at 16 octets of content length
|
||||
10010000 = 90
|
||||
In [6]: base64.b64encode(binascii.a2b_hex('3090'))
|
||||
Out[6]: 'MJA='
|
||||
Checking for just M is insufficient
|
||||
|
||||
But we will only check for MII:
|
||||
Max length of the content using 2 octets is 3FFF or 16383.
|
||||
|
||||
It's not practical to support a token of this length or greater in http
|
||||
therefore, we will check for MII only and ignore the case of larger tokens
|
||||
"""
|
||||
return token[:3] == PKI_ASN1_PREFIX
|
||||
|
||||
|
||||
@removals.remove(message='Use is_asn1_token() instead.', version='1.7.0',
|
||||
removal_version='2.0.0')
|
||||
def is_ans1_token(token):
|
||||
"""Deprecated.
|
||||
|
||||
This function is deprecated as of the 1.7.0 release in favor of
|
||||
:func:`is_asn1_token` and may be removed in the 2.0.0 release.
|
||||
"""
|
||||
return is_asn1_token(token)
|
||||
|
||||
|
||||
def cms_sign_text(data_to_sign, signing_cert_file_name, signing_key_file_name,
|
||||
message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM):
|
||||
return cms_sign_data(data_to_sign, signing_cert_file_name,
|
||||
signing_key_file_name, message_digest=message_digest)
|
||||
|
||||
|
||||
def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name,
|
||||
outform=PKI_ASN1_FORM,
|
||||
message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM):
|
||||
"""Use OpenSSL to sign a document.
|
||||
|
||||
Produces a Base64 encoding of a DER formatted CMS Document
|
||||
http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax
|
||||
|
||||
:param data_to_sign: data to sign
|
||||
:param signing_cert_file_name: path to the X509 certificate containing
|
||||
the public key associated with the private key used to sign the data
|
||||
:param signing_key_file_name: path to the private key used to sign
|
||||
the data
|
||||
:param outform: Format for the signed document PKIZ_CMS_FORM or
|
||||
PKI_ASN1_FORM
|
||||
:param message_digest: Digest algorithm to use when signing or resigning
|
||||
|
||||
"""
|
||||
_ensure_subprocess()
|
||||
if isinstance(data_to_sign, six.string_types):
|
||||
data = bytearray(data_to_sign, encoding='utf-8')
|
||||
else:
|
||||
data = data_to_sign
|
||||
process = subprocess.Popen(['openssl', 'cms', '-sign',
|
||||
'-signer', signing_cert_file_name,
|
||||
'-inkey', signing_key_file_name,
|
||||
'-outform', 'PEM',
|
||||
'-nosmimecap', '-nodetach',
|
||||
'-nocerts', '-noattr',
|
||||
'-md', message_digest, ],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
|
||||
output, err, retcode = _process_communicate_handle_oserror(
|
||||
process, data, (signing_cert_file_name, signing_key_file_name))
|
||||
|
||||
if retcode != OpensslCmsExitStatus.SUCCESS or ('Error' in err):
|
||||
if retcode == OpensslCmsExitStatus.CREATE_CMS_READ_MIME_ERROR:
|
||||
LOG.error('Signing error: Unable to load certificate - '
|
||||
'ensure you have configured PKI with '
|
||||
'"keystone-manage pki_setup"')
|
||||
else:
|
||||
LOG.error('Signing error: %s', err)
|
||||
raise subprocess.CalledProcessError(retcode, 'openssl')
|
||||
if outform == PKI_ASN1_FORM:
|
||||
return output.decode('utf-8')
|
||||
else:
|
||||
return output
|
||||
|
||||
|
||||
def cms_sign_token(text, signing_cert_file_name, signing_key_file_name,
|
||||
message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM):
|
||||
output = cms_sign_data(text, signing_cert_file_name, signing_key_file_name,
|
||||
message_digest=message_digest)
|
||||
return cms_to_token(output)
|
||||
|
||||
|
||||
def cms_to_token(cms_text):
|
||||
"""Convert a CMS-signed token in PEM format to a custom URL-safe format.
|
||||
|
||||
The conversion consists of replacing '/' char in the PEM-formatted token
|
||||
with the '-' char and doing other such textual replacements to make the
|
||||
result marshallable via HTTP. The return value can thus be used as the
|
||||
value of a HTTP header such as "X-Auth-Token".
|
||||
|
||||
This ad-hoc conversion is an unfortunate oversight since the returned
|
||||
value now does not conform to any of the standard variants of base64
|
||||
encoding. It would have been better to use base64url encoding (either on
|
||||
the PEM formatted text or, perhaps even better, on the inner CMS-signed
|
||||
binary value without any PEM formatting). In any case, the same conversion
|
||||
is done in reverse in the other direction (for token verification), so
|
||||
there are no correctness issues here. Note that the non-standard encoding
|
||||
of the token will be preserved so as to not break backward compatibility.
|
||||
|
||||
The conversion issue is detailed by the code author in a blog post at
|
||||
http://adam.younglogic.com/2014/02/compressed-tokens/.
|
||||
"""
|
||||
start_delim = '-----BEGIN CMS-----'
|
||||
end_delim = '-----END CMS-----'
|
||||
signed_text = cms_text
|
||||
signed_text = signed_text.replace('/', '-')
|
||||
signed_text = signed_text.replace(start_delim, '')
|
||||
signed_text = signed_text.replace(end_delim, '')
|
||||
signed_text = signed_text.replace('\n', '')
|
||||
|
||||
return signed_text
|
||||
|
||||
|
||||
def cms_hash_token(token_id, mode='md5'):
|
||||
"""Hash PKI tokens.
|
||||
|
||||
return: for asn1 or pkiz tokens, returns the hash of the passed in token
|
||||
otherwise, returns what it was passed in.
|
||||
"""
|
||||
if token_id is None:
|
||||
return None
|
||||
if is_asn1_token(token_id) or is_pkiz(token_id):
|
||||
hasher = hashlib.new(mode)
|
||||
if isinstance(token_id, six.text_type):
|
||||
token_id = token_id.encode('utf-8')
|
||||
hasher.update(token_id)
|
||||
return hasher.hexdigest()
|
||||
else:
|
||||
return token_id
|
@@ -1,211 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity.v3 import federated
|
||||
|
||||
|
||||
class OidcPassword(federated.FederatedBaseAuth):
|
||||
"""Implement authentication plugin for OpenID Connect protocol.
|
||||
|
||||
OIDC or OpenID Connect is a protocol for federated authentication.
|
||||
|
||||
The OpenID Connect specification can be found at::
|
||||
``http://openid.net/specs/openid-connect-core-1_0.html``
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(OidcPassword, cls).get_options()
|
||||
options.extend([
|
||||
cfg.StrOpt('username', help='Username'),
|
||||
cfg.StrOpt('password', secret=True, help='Password'),
|
||||
cfg.StrOpt('client-id', help='OAuth 2.0 Client ID'),
|
||||
cfg.StrOpt('client-secret', secret=True,
|
||||
help='OAuth 2.0 Client Secret'),
|
||||
cfg.StrOpt('access-token-endpoint',
|
||||
help='OpenID Connect Provider Token Endpoint'),
|
||||
cfg.StrOpt('scope', default="profile",
|
||||
help='OpenID Connect scope that is requested from OP')
|
||||
])
|
||||
return options
|
||||
|
||||
@positional(4)
|
||||
def __init__(self, auth_url, identity_provider, protocol,
|
||||
username, password, client_id, client_secret,
|
||||
access_token_endpoint, scope='profile',
|
||||
grant_type='password'):
|
||||
"""The OpenID Connect plugin.
|
||||
|
||||
It expects the following:
|
||||
|
||||
:param auth_url: URL of the Identity Service
|
||||
:type auth_url: string
|
||||
|
||||
:param identity_provider: Name of the Identity Provider the client
|
||||
will authenticate against
|
||||
:type identity_provider: string
|
||||
|
||||
:param protocol: Protocol name as configured in keystone
|
||||
:type protocol: string
|
||||
|
||||
:param username: Username used to authenticate
|
||||
:type username: string
|
||||
|
||||
:param password: Password used to authenticate
|
||||
:type password: string
|
||||
|
||||
:param client_id: OAuth 2.0 Client ID
|
||||
:type client_id: string
|
||||
|
||||
:param client_secret: OAuth 2.0 Client Secret
|
||||
:type client_secret: string
|
||||
|
||||
:param access_token_endpoint: OpenID Connect Provider Token Endpoint,
|
||||
for example:
|
||||
https://localhost:8020/oidc/OP/token
|
||||
:type access_token_endpoint: string
|
||||
|
||||
:param scope: OpenID Connect scope that is requested from OP,
|
||||
defaults to "profile", for example: "profile email"
|
||||
:type scope: string
|
||||
|
||||
:param grant_type: OpenID Connect grant type, it represents the flow
|
||||
that is used to talk to the OP. Valid values are:
|
||||
"authorization_code", "refresh_token", or
|
||||
"password".
|
||||
:type grant_type: string
|
||||
"""
|
||||
super(OidcPassword, self).__init__(auth_url, identity_provider,
|
||||
protocol)
|
||||
self._username = username
|
||||
self._password = password
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.access_token_endpoint = access_token_endpoint
|
||||
self.scope = scope
|
||||
self.grant_type = grant_type
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
# Override to remove deprecation.
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._username = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
# Override to remove deprecation.
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._password = value
|
||||
|
||||
def get_unscoped_auth_ref(self, session):
|
||||
"""Authenticate with OpenID Connect and get back claims.
|
||||
|
||||
This is a multi-step process. First an access token must be retrieved,
|
||||
to do this, the username and password, the OpenID Connect client ID
|
||||
and secret, and the access token endpoint must be known.
|
||||
|
||||
Secondly, we then exchange the access token upon accessing the
|
||||
protected Keystone endpoint (federated auth URL). This will trigger
|
||||
the OpenID Connect Provider to perform a user introspection and
|
||||
retrieve information (specified in the scope) about the user in
|
||||
the form of an OpenID Connect Claim. These claims will be sent
|
||||
to Keystone in the form of environment variables.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: a token data representation
|
||||
:rtype: :py:class:`keystoneclient.access.AccessInfo`
|
||||
"""
|
||||
# get an access token
|
||||
client_auth = (self.client_id, self.client_secret)
|
||||
payload = {'grant_type': self.grant_type, 'username': self.username,
|
||||
'password': self.password, 'scope': self.scope}
|
||||
response = self._get_access_token(session, client_auth, payload,
|
||||
self.access_token_endpoint)
|
||||
access_token = response.json()['access_token']
|
||||
|
||||
# use access token against protected URL
|
||||
headers = {'Authorization': 'Bearer ' + access_token}
|
||||
response = self._get_keystone_token(session, headers,
|
||||
self.federated_token_url)
|
||||
|
||||
# grab the unscoped token
|
||||
token = response.headers['X-Subject-Token']
|
||||
token_json = response.json()['token']
|
||||
return access.AccessInfoV3(token, **token_json)
|
||||
|
||||
def _get_access_token(self, session, client_auth, payload,
|
||||
access_token_endpoint):
|
||||
"""Exchange a variety of user supplied values for an access token.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:param client_auth: a tuple representing client id and secret
|
||||
:type client_auth: tuple
|
||||
|
||||
:param payload: a dict containing various OpenID Connect values, for
|
||||
example::
|
||||
{'grant_type': 'password', 'username': self.username,
|
||||
'password': self.password, 'scope': self.scope}
|
||||
:type payload: dict
|
||||
|
||||
:param access_token_endpoint: URL to use to get an access token, for
|
||||
example: https://localhost/oidc/token
|
||||
:type access_token_endpoint: string
|
||||
"""
|
||||
op_response = session.post(self.access_token_endpoint,
|
||||
requests_auth=client_auth,
|
||||
data=payload,
|
||||
authenticated=False)
|
||||
return op_response
|
||||
|
||||
def _get_keystone_token(self, session, headers, federated_token_url):
|
||||
r"""Exchange an acess token for a keystone token.
|
||||
|
||||
By Sending the access token in an `Authorization: Bearer` header, to
|
||||
an OpenID Connect protected endpoint (Federated Token URL). The
|
||||
OpenID Connect server will use the access token to look up information
|
||||
about the authenticated user (this technique is called instrospection).
|
||||
The output of the instrospection will be an OpenID Connect Claim, that
|
||||
will be used against the mapping engine. Should the mapping engine
|
||||
succeed, a Keystone token will be presented to the user.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:param headers: an Authorization header containing the access token.
|
||||
:type headers_: dict
|
||||
|
||||
:param federated_auth_url: Protected URL for federated authentication,
|
||||
for example: https://localhost:5000/v3/\
|
||||
OS-FEDERATION/identity_providers/bluepages/\
|
||||
protocols/oidc/auth
|
||||
:type federated_auth_url: string
|
||||
"""
|
||||
auth_response = session.post(self.federated_token_url,
|
||||
headers=headers,
|
||||
authenticated=False)
|
||||
return auth_response
|
@@ -1,920 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from lxml import etree # nosec(cjschaef): used to create xml, not parse it
|
||||
from oslo_config import cfg
|
||||
from six.moves import urllib
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
class _BaseSAMLPlugin(v3.AuthConstructor):
|
||||
|
||||
HTTP_MOVED_TEMPORARILY = 302
|
||||
HTTP_SEE_OTHER = 303
|
||||
|
||||
PROTOCOL = 'saml2'
|
||||
|
||||
@staticmethod
|
||||
def _first(_list):
|
||||
if len(_list) != 1:
|
||||
raise IndexError(_("Only single element list is acceptable"))
|
||||
return _list[0]
|
||||
|
||||
@staticmethod
|
||||
def str_to_xml(content, msg=None, include_exc=True):
|
||||
try:
|
||||
return etree.XML(content)
|
||||
except etree.XMLSyntaxError as e:
|
||||
if not msg:
|
||||
msg = str(e)
|
||||
else:
|
||||
msg = msg % e if include_exc else msg
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
|
||||
@staticmethod
|
||||
def xml_to_str(content, **kwargs):
|
||||
return etree.tostring(content, **kwargs)
|
||||
|
||||
@property
|
||||
def token_url(self):
|
||||
"""Return full URL where authorization data is sent."""
|
||||
values = {
|
||||
'host': self.auth_url.rstrip('/'),
|
||||
'identity_provider': self.identity_provider,
|
||||
'protocol': self.PROTOCOL
|
||||
}
|
||||
url = ("%(host)s/OS-FEDERATION/identity_providers/"
|
||||
"%(identity_provider)s/protocols/%(protocol)s/auth")
|
||||
url = url % values
|
||||
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(_BaseSAMLPlugin, cls).get_options()
|
||||
options.extend([
|
||||
cfg.StrOpt('identity-provider', help="Identity Provider's name"),
|
||||
cfg.StrOpt('identity-provider-url',
|
||||
help="Identity Provider's URL"),
|
||||
cfg.StrOpt('username', dest='username', help='Username',
|
||||
deprecated_name='user-name'),
|
||||
cfg.StrOpt('password', secret=True, help='Password')
|
||||
])
|
||||
return options
|
||||
|
||||
|
||||
class Saml2UnscopedTokenAuthMethod(v3.AuthMethod):
|
||||
_method_parameters = []
|
||||
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
raise exceptions.MethodNotImplemented(_('This method should never '
|
||||
'be called'))
|
||||
|
||||
|
||||
class Saml2UnscopedToken(_BaseSAMLPlugin):
|
||||
r"""Implement authentication plugin for SAML2 protocol.
|
||||
|
||||
ECP stands for `Enhanced Client or Proxy` and is a SAML2 extension
|
||||
for federated authentication where a transportation layer consists of
|
||||
HTTP protocol and XML SOAP messages.
|
||||
|
||||
`Read for more information
|
||||
<https://wiki.shibboleth.net/confluence/display/SHIB2/ECP>`_ on ECP.
|
||||
|
||||
Reference the `SAML2 ECP specification <https://www.oasis-open.org/\
|
||||
committees/download.php/49979/saml-ecp-v2.0-wd09.pdf>`_.
|
||||
|
||||
Currently only HTTPBasicAuth mechanism is available for the IdP
|
||||
authenication.
|
||||
|
||||
:param auth_url: URL of the Identity Service
|
||||
:type auth_url: string
|
||||
|
||||
:param identity_provider: name of the Identity Provider the client will
|
||||
authenticate against. This parameter will be used
|
||||
to build a dynamic URL used to obtain unscoped
|
||||
OpenStack token.
|
||||
:type identity_provider: string
|
||||
|
||||
:param identity_provider_url: An Identity Provider URL, where the SAML2
|
||||
authn request will be sent.
|
||||
:type identity_provider_url: string
|
||||
|
||||
:param username: User's login
|
||||
:type username: string
|
||||
|
||||
:param password: User's password
|
||||
:type password: string
|
||||
|
||||
"""
|
||||
|
||||
_auth_method_class = Saml2UnscopedTokenAuthMethod
|
||||
|
||||
SAML2_HEADER_INDEX = 0
|
||||
ECP_SP_EMPTY_REQUEST_HEADERS = {
|
||||
'Accept': 'text/html, application/vnd.paos+xml',
|
||||
'PAOS': ('ver="urn:liberty:paos:2003-08";"urn:oasis:names:tc:'
|
||||
'SAML:2.0:profiles:SSO:ecp"')
|
||||
}
|
||||
|
||||
ECP_SP_SAML2_REQUEST_HEADERS = {
|
||||
'Content-Type': 'application/vnd.paos+xml'
|
||||
}
|
||||
|
||||
ECP_SAML2_NAMESPACES = {
|
||||
'ecp': 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp',
|
||||
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
'paos': 'urn:liberty:paos:2003-08'
|
||||
}
|
||||
|
||||
ECP_RELAY_STATE = '//ecp:RelayState'
|
||||
|
||||
ECP_SERVICE_PROVIDER_CONSUMER_URL = ('/S:Envelope/S:Header/paos:Request/'
|
||||
'@responseConsumerURL')
|
||||
|
||||
ECP_IDP_CONSUMER_URL = ('/S:Envelope/S:Header/ecp:Response/'
|
||||
'@AssertionConsumerServiceURL')
|
||||
|
||||
SOAP_FAULT = """
|
||||
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<S:Body>
|
||||
<S:Fault>
|
||||
<faultcode>S:Server</faultcode>
|
||||
<faultstring>responseConsumerURL from SP and
|
||||
assertionConsumerServiceURL from IdP do not match
|
||||
</faultstring>
|
||||
</S:Fault>
|
||||
</S:Body>
|
||||
</S:Envelope>
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url,
|
||||
identity_provider,
|
||||
identity_provider_url,
|
||||
username, password,
|
||||
**kwargs):
|
||||
super(Saml2UnscopedToken, self).__init__(auth_url=auth_url, **kwargs)
|
||||
self.identity_provider = identity_provider
|
||||
self.identity_provider_url = identity_provider_url
|
||||
self._username, self._password = username, password
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
# Override to remove deprecation.
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._username = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
# Override to remove deprecation.
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._password = value
|
||||
|
||||
def _handle_http_ecp_redirect(self, session, response, method, **kwargs):
|
||||
if response.status_code not in (self.HTTP_MOVED_TEMPORARILY,
|
||||
self.HTTP_SEE_OTHER):
|
||||
return response
|
||||
|
||||
location = response.headers['location']
|
||||
return session.request(location, method, authenticated=False,
|
||||
**kwargs)
|
||||
|
||||
def _prepare_idp_saml2_request(self, saml2_authn_request):
|
||||
header = saml2_authn_request[self.SAML2_HEADER_INDEX]
|
||||
saml2_authn_request.remove(header)
|
||||
|
||||
def _check_consumer_urls(self, session, sp_response_consumer_url,
|
||||
idp_sp_response_consumer_url):
|
||||
"""Check if consumer URLs issued by SP and IdP are equal.
|
||||
|
||||
In the initial SAML2 authn Request issued by a Service Provider
|
||||
there is a url called ``consumer url``. A trusted Identity Provider
|
||||
should issue identical url. If the URLs are not equal the federated
|
||||
authn process should be interrupted and the user should be warned.
|
||||
|
||||
:param session: session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param sp_response_consumer_url: consumer URL issued by a SP
|
||||
:type sp_response_consumer_url: string
|
||||
:param idp_sp_response_consumer_url: consumer URL issued by an IdP
|
||||
:type idp_sp_response_consumer_url: string
|
||||
|
||||
"""
|
||||
if sp_response_consumer_url != idp_sp_response_consumer_url:
|
||||
# send fault message to the SP, discard the response
|
||||
session.post(sp_response_consumer_url, data=self.SOAP_FAULT,
|
||||
headers=self.ECP_SP_SAML2_REQUEST_HEADERS,
|
||||
authenticated=False)
|
||||
|
||||
# prepare error message and raise an exception.
|
||||
msg = _("Consumer URLs from Service Provider %(service_provider)s "
|
||||
"%(sp_consumer_url)s and Identity Provider "
|
||||
"%(identity_provider)s %(idp_consumer_url)s are not equal")
|
||||
msg = msg % {
|
||||
'service_provider': self.token_url,
|
||||
'sp_consumer_url': sp_response_consumer_url,
|
||||
'identity_provider': self.identity_provider,
|
||||
'idp_consumer_url': idp_sp_response_consumer_url
|
||||
}
|
||||
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def _send_service_provider_request(self, session):
|
||||
"""Initial HTTP GET request to the SAML2 protected endpoint.
|
||||
|
||||
It's crucial to include HTTP headers indicating that the client is
|
||||
willing to take advantage of the ECP SAML2 extension and receive data
|
||||
as the SOAP.
|
||||
Unlike standard authentication methods in the OpenStack Identity,
|
||||
the client accesses::
|
||||
``/v3/OS-FEDERATION/identity_providers/{identity_providers}/
|
||||
protocols/{protocol}/auth``
|
||||
|
||||
After a successful HTTP call the HTTP response should include SAML2
|
||||
authn request in the XML format.
|
||||
|
||||
If a HTTP response contains ``X-Subject-Token`` in the headers and
|
||||
the response body is a valid JSON assume the user was already
|
||||
authenticated and Keystone returned a valid unscoped token.
|
||||
Return True indicating the user was already authenticated.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
"""
|
||||
sp_response = session.get(self.token_url,
|
||||
headers=self.ECP_SP_EMPTY_REQUEST_HEADERS,
|
||||
authenticated=False)
|
||||
|
||||
if 'X-Subject-Token' in sp_response.headers:
|
||||
self.authenticated_response = sp_response
|
||||
return True
|
||||
|
||||
try:
|
||||
self.saml2_authn_request = etree.XML(sp_response.content)
|
||||
except etree.XMLSyntaxError as e:
|
||||
msg = _("SAML2: Error parsing XML returned "
|
||||
"from Service Provider, reason: %s") % e
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
|
||||
relay_state = self.saml2_authn_request.xpath(
|
||||
self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES)
|
||||
self.relay_state = self._first(relay_state)
|
||||
|
||||
sp_response_consumer_url = self.saml2_authn_request.xpath(
|
||||
self.ECP_SERVICE_PROVIDER_CONSUMER_URL,
|
||||
namespaces=self.ECP_SAML2_NAMESPACES)
|
||||
self.sp_response_consumer_url = self._first(sp_response_consumer_url)
|
||||
return False
|
||||
|
||||
def _send_idp_saml2_authn_request(self, session):
|
||||
"""Present modified SAML2 authn assertion from the Service Provider."""
|
||||
self._prepare_idp_saml2_request(self.saml2_authn_request)
|
||||
idp_saml2_authn_request = self.saml2_authn_request
|
||||
|
||||
# Currently HTTPBasicAuth method is hardcoded into the plugin
|
||||
idp_response = session.post(
|
||||
self.identity_provider_url,
|
||||
headers={'Content-type': 'text/xml'},
|
||||
data=etree.tostring(idp_saml2_authn_request),
|
||||
requests_auth=(self.username, self.password),
|
||||
authenticated=False, log=False)
|
||||
|
||||
try:
|
||||
self.saml2_idp_authn_response = etree.XML(idp_response.content)
|
||||
except etree.XMLSyntaxError as e:
|
||||
msg = _("SAML2: Error parsing XML returned "
|
||||
"from Identity Provider, reason: %s") % e
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
|
||||
idp_response_consumer_url = self.saml2_idp_authn_response.xpath(
|
||||
self.ECP_IDP_CONSUMER_URL,
|
||||
namespaces=self.ECP_SAML2_NAMESPACES)
|
||||
|
||||
self.idp_response_consumer_url = self._first(idp_response_consumer_url)
|
||||
|
||||
self._check_consumer_urls(session, self.idp_response_consumer_url,
|
||||
self.sp_response_consumer_url)
|
||||
|
||||
def _send_service_provider_saml2_authn_response(self, session):
|
||||
"""Present SAML2 assertion to the Service Provider.
|
||||
|
||||
The assertion is issued by a trusted Identity Provider for the
|
||||
authenticated user. This function directs the HTTP request to SP
|
||||
managed URL, for instance: ``https://<host>:<port>/Shibboleth.sso/
|
||||
SAML2/ECP``.
|
||||
Upon success the 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://<host>:<port>/v3/
|
||||
OS-FEDERATION/identity_providers/{identity_provider}/protocols/
|
||||
{protocol_id}/auth`` in this case). Saml2 plugin should point to that
|
||||
URL again, with HTTP GET method, expecting an unscoped token.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
|
||||
"""
|
||||
self.saml2_idp_authn_response[0][0] = self.relay_state
|
||||
|
||||
response = session.post(
|
||||
self.idp_response_consumer_url,
|
||||
headers=self.ECP_SP_SAML2_REQUEST_HEADERS,
|
||||
data=etree.tostring(self.saml2_idp_authn_response),
|
||||
authenticated=False, redirect=False)
|
||||
|
||||
# Don't follow HTTP specs - after the HTTP 302/303 response don't
|
||||
# repeat the call directed to the Location URL. In this case, this is
|
||||
# an indication that saml2 session is now active and protected resource
|
||||
# can be accessed.
|
||||
response = self._handle_http_ecp_redirect(
|
||||
session, response, method='GET',
|
||||
headers=self.ECP_SP_SAML2_REQUEST_HEADERS)
|
||||
|
||||
self.authenticated_response = response
|
||||
|
||||
def _get_unscoped_token(self, session):
|
||||
"""Get unscoped OpenStack token after federated authentication.
|
||||
|
||||
This is a multi-step process including multiple HTTP requests.
|
||||
|
||||
The federated authentication consists of::
|
||||
* HTTP GET request to the Identity Service (acting as a Service
|
||||
Provider). Client utilizes URL::
|
||||
``/v3/OS-FEDERATION/identity_providers/{identity_provider}/
|
||||
protocols/saml2/auth``.
|
||||
It's crucial to include HTTP headers indicating we are expecting
|
||||
SOAP message in return.
|
||||
Service Provider should respond with such SOAP message.
|
||||
This step is handed by a method
|
||||
``Saml2UnscopedToken_send_service_provider_request()``
|
||||
|
||||
* HTTP POST request to the external Identity Provider service with
|
||||
ECP extension enabled. The content sent is a header removed SOAP
|
||||
message returned from the Service Provider. It's also worth noting
|
||||
that ECP extension to the SAML2 doesn't define authentication method.
|
||||
The most popular is HttpBasicAuth with just user and password.
|
||||
Other possibilities could be X509 certificates or Kerberos.
|
||||
Upon successful authentication the user should receive a SAML2
|
||||
assertion.
|
||||
This step is handed by a method
|
||||
``Saml2UnscopedToken_send_idp_saml2_authn_request(session)``
|
||||
|
||||
* HTTP POST request again to the Service Provider. The body of the
|
||||
request includes SAML2 assertion issued by a trusted Identity
|
||||
Provider. The request should be sent to the Service Provider
|
||||
consumer url specified in the SAML2 assertion.
|
||||
Providing the authentication was successful and both Service Provider
|
||||
and Identity Providers are trusted to each other, the Service
|
||||
Provider will issue an unscoped token with a list of groups the
|
||||
federated user is a member of.
|
||||
This step is handed by a method
|
||||
``Saml2UnscopedToken_send_service_provider_saml2_authn_response()``
|
||||
|
||||
Unscoped token example::
|
||||
|
||||
{
|
||||
"token": {
|
||||
"methods": [
|
||||
"saml2"
|
||||
],
|
||||
"user": {
|
||||
"id": "username%40example.com",
|
||||
"name": "username@example.com",
|
||||
"OS-FEDERATION": {
|
||||
"identity_provider": "ACME",
|
||||
"protocol": "saml2",
|
||||
"groups": [
|
||||
{"id": "abc123"},
|
||||
{"id": "bcd234"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: (token, token_json)
|
||||
|
||||
"""
|
||||
saml_authenticated = self._send_service_provider_request(session)
|
||||
if not saml_authenticated:
|
||||
self._send_idp_saml2_authn_request(session)
|
||||
self._send_service_provider_saml2_authn_response(session)
|
||||
return (self.authenticated_response.headers['X-Subject-Token'],
|
||||
self.authenticated_response.json()['token'])
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
"""Authenticate via SAML2 protocol and retrieve unscoped token.
|
||||
|
||||
This is a multi-step process where a client does federated authn
|
||||
receives an unscoped token.
|
||||
|
||||
Federated authentication utilizing SAML2 Enhanced Client or Proxy
|
||||
extension. See ``Saml2UnscopedToken_get_unscoped_token()``
|
||||
for more information on that step.
|
||||
Upon successful authentication and assertion mapping an
|
||||
unscoped token is returned and stored within the plugin object for
|
||||
further use.
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:return: an object with scoped token's id and unscoped token json
|
||||
included.
|
||||
:rtype: :py:class:`keystoneclient.access.AccessInfoV3`
|
||||
|
||||
"""
|
||||
token, token_json = self._get_unscoped_token(session)
|
||||
return access.AccessInfoV3(token,
|
||||
**token_json)
|
||||
|
||||
|
||||
class ADFSUnscopedToken(_BaseSAMLPlugin):
|
||||
"""Authentication plugin for Microsoft ADFS2.0 IdPs.
|
||||
|
||||
:param auth_url: URL of the Identity Service
|
||||
:type auth_url: string
|
||||
|
||||
:param identity_provider: name of the Identity Provider the client will
|
||||
authenticate against. This parameter will be used
|
||||
to build a dynamic URL used to obtain unscoped
|
||||
OpenStack token.
|
||||
:type identity_provider: string
|
||||
|
||||
:param identity_provider_url: An Identity Provider URL, where the SAML2
|
||||
authentication request will be sent.
|
||||
:type identity_provider_url: string
|
||||
|
||||
:param service_provider_endpoint: Endpoint where an assertion is being
|
||||
sent, for instance: ``https://host.domain/Shibboleth.sso/ADFS``
|
||||
:type service_provider_endpoint: string
|
||||
|
||||
:param username: User's login
|
||||
:type username: string
|
||||
|
||||
:param password: User's password
|
||||
:type password: string
|
||||
|
||||
"""
|
||||
|
||||
_auth_method_class = Saml2UnscopedTokenAuthMethod
|
||||
|
||||
DEFAULT_ADFS_TOKEN_EXPIRATION = 120
|
||||
|
||||
HEADER_SOAP = {"Content-Type": "application/soap+xml; charset=utf-8"}
|
||||
HEADER_X_FORM = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
NAMESPACES = {
|
||||
's': 'http://www.w3.org/2003/05/soap-envelope',
|
||||
'a': 'http://www.w3.org/2005/08/addressing',
|
||||
'u': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-utility-1.0.xsd')
|
||||
}
|
||||
|
||||
ADFS_TOKEN_NAMESPACES = {
|
||||
's': 'http://www.w3.org/2003/05/soap-envelope',
|
||||
't': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512'
|
||||
}
|
||||
ADFS_ASSERTION_XPATH = ('/s:Envelope/s:Body'
|
||||
'/t:RequestSecurityTokenResponseCollection'
|
||||
'/t:RequestSecurityTokenResponse')
|
||||
|
||||
def __init__(self, auth_url, identity_provider, identity_provider_url,
|
||||
service_provider_endpoint, username, password, **kwargs):
|
||||
super(ADFSUnscopedToken, self).__init__(auth_url=auth_url, **kwargs)
|
||||
self.identity_provider = identity_provider
|
||||
self.identity_provider_url = identity_provider_url
|
||||
self.service_provider_endpoint = service_provider_endpoint
|
||||
self._username, self._password = username, password
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
# Override to remove deprecation.
|
||||
return self._username
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._username = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
# Override to remove deprecation.
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
# Override to remove deprecation.
|
||||
self._password = value
|
||||
|
||||
@classmethod
|
||||
def get_options(cls):
|
||||
options = super(ADFSUnscopedToken, cls).get_options()
|
||||
|
||||
options.extend([
|
||||
cfg.StrOpt('service-provider-endpoint',
|
||||
help="Service Provider's Endpoint")
|
||||
])
|
||||
return options
|
||||
|
||||
def _cookies(self, session):
|
||||
"""Check if cookie jar is not empty.
|
||||
|
||||
keystoneclient.session.Session object doesn't have a cookies attribute.
|
||||
We should then try fetching cookies from the underlying
|
||||
requests.Session object. If that fails too, there is something wrong
|
||||
and let Python raise the AttributeError.
|
||||
|
||||
:param session
|
||||
:returns: True if cookie jar is nonempty, False otherwise
|
||||
:raises AttributeError: in case cookies are not find anywhere
|
||||
|
||||
"""
|
||||
try:
|
||||
return bool(session.cookies)
|
||||
except AttributeError: # nosec(cjschaef): fetch cookies from
|
||||
# underylying requests.Session object, or fail trying
|
||||
pass
|
||||
|
||||
return bool(session.session.cookies)
|
||||
|
||||
def _token_dates(self, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
|
||||
"""Calculate created and expires datetime objects.
|
||||
|
||||
The method is going to be used for building ADFS Request Security
|
||||
Token message. Time interval between ``created`` and ``expires``
|
||||
dates is now static and equals to 120 seconds. ADFS security tokens
|
||||
should not be live too long, as currently ``keystoneclient``
|
||||
doesn't have mechanisms for reusing such tokens (every time ADFS authn
|
||||
method is called, keystoneclient will login with the ADFS instance).
|
||||
|
||||
:param fmt: Datetime format for specifying string format of a date.
|
||||
It should not be changed if the method is going to be used
|
||||
for building the ADFS security token request.
|
||||
:type fmt: string
|
||||
|
||||
"""
|
||||
date_created = datetime.datetime.utcnow()
|
||||
date_expires = date_created + datetime.timedelta(
|
||||
seconds=self.DEFAULT_ADFS_TOKEN_EXPIRATION)
|
||||
return [_time.strftime(fmt) for _time in (date_created, date_expires)]
|
||||
|
||||
def _prepare_adfs_request(self):
|
||||
"""Build the ADFS Request Security Token SOAP message.
|
||||
|
||||
Some values like username or password are inserted in the request.
|
||||
|
||||
"""
|
||||
WSS_SECURITY_NAMESPACE = {
|
||||
'o': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-secext-1.0.xsd')
|
||||
}
|
||||
|
||||
TRUST_NAMESPACE = {
|
||||
'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512'
|
||||
}
|
||||
|
||||
WSP_NAMESPACE = {
|
||||
'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy'
|
||||
}
|
||||
|
||||
WSA_NAMESPACE = {
|
||||
'wsa': 'http://www.w3.org/2005/08/addressing'
|
||||
}
|
||||
|
||||
root = etree.Element(
|
||||
'{http://www.w3.org/2003/05/soap-envelope}Envelope',
|
||||
nsmap=self.NAMESPACES)
|
||||
|
||||
header = etree.SubElement(
|
||||
root, '{http://www.w3.org/2003/05/soap-envelope}Header')
|
||||
action = etree.SubElement(
|
||||
header, "{http://www.w3.org/2005/08/addressing}Action")
|
||||
action.set(
|
||||
"{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
|
||||
action.text = ('http://docs.oasis-open.org/ws-sx/ws-trust/200512'
|
||||
'/RST/Issue')
|
||||
|
||||
messageID = etree.SubElement(
|
||||
header, '{http://www.w3.org/2005/08/addressing}MessageID')
|
||||
messageID.text = 'urn:uuid:' + uuid.uuid4().hex
|
||||
replyID = etree.SubElement(
|
||||
header, '{http://www.w3.org/2005/08/addressing}ReplyTo')
|
||||
address = etree.SubElement(
|
||||
replyID, '{http://www.w3.org/2005/08/addressing}Address')
|
||||
address.text = 'http://www.w3.org/2005/08/addressing/anonymous'
|
||||
|
||||
to = etree.SubElement(
|
||||
header, '{http://www.w3.org/2005/08/addressing}To')
|
||||
to.set("{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
|
||||
|
||||
security = etree.SubElement(
|
||||
header, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-secext-1.0.xsd}Security',
|
||||
nsmap=WSS_SECURITY_NAMESPACE)
|
||||
|
||||
security.set(
|
||||
"{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
|
||||
|
||||
timestamp = etree.SubElement(
|
||||
security, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-utility-1.0.xsd}Timestamp'))
|
||||
timestamp.set(
|
||||
('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-utility-1.0.xsd}Id'), '_0')
|
||||
|
||||
created = etree.SubElement(
|
||||
timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-utility-1.0.xsd}Created'))
|
||||
|
||||
expires = etree.SubElement(
|
||||
timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-utility-1.0.xsd}Expires'))
|
||||
|
||||
created.text, expires.text = self._token_dates()
|
||||
|
||||
usernametoken = etree.SubElement(
|
||||
security, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
|
||||
'wss-wssecurity-secext-1.0.xsd}UsernameToken')
|
||||
usernametoken.set(
|
||||
('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-'
|
||||
'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % uuid.uuid4().hex)
|
||||
|
||||
username = etree.SubElement(
|
||||
usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-'
|
||||
'200401-wss-wssecurity-secext-1.0.xsd}Username'))
|
||||
password = etree.SubElement(
|
||||
usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-'
|
||||
'200401-wss-wssecurity-secext-1.0.xsd}Password'),
|
||||
Type=('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-'
|
||||
'username-token-profile-1.0#PasswordText'))
|
||||
|
||||
body = etree.SubElement(
|
||||
root, "{http://www.w3.org/2003/05/soap-envelope}Body")
|
||||
|
||||
request_security_token = etree.SubElement(
|
||||
body, ('{http://docs.oasis-open.org/ws-sx/ws-trust/200512}'
|
||||
'RequestSecurityToken'), nsmap=TRUST_NAMESPACE)
|
||||
|
||||
applies_to = etree.SubElement(
|
||||
request_security_token,
|
||||
'{http://schemas.xmlsoap.org/ws/2004/09/policy}AppliesTo',
|
||||
nsmap=WSP_NAMESPACE)
|
||||
|
||||
endpoint_reference = etree.SubElement(
|
||||
applies_to,
|
||||
'{http://www.w3.org/2005/08/addressing}EndpointReference',
|
||||
nsmap=WSA_NAMESPACE)
|
||||
|
||||
wsa_address = etree.SubElement(
|
||||
endpoint_reference,
|
||||
'{http://www.w3.org/2005/08/addressing}Address')
|
||||
|
||||
keytype = etree.SubElement(
|
||||
request_security_token,
|
||||
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}KeyType')
|
||||
keytype.text = ('http://docs.oasis-open.org/ws-sx/'
|
||||
'ws-trust/200512/Bearer')
|
||||
|
||||
request_type = etree.SubElement(
|
||||
request_security_token,
|
||||
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}RequestType')
|
||||
request_type.text = ('http://docs.oasis-open.org/ws-sx/'
|
||||
'ws-trust/200512/Issue')
|
||||
token_type = etree.SubElement(
|
||||
request_security_token,
|
||||
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}TokenType')
|
||||
token_type.text = 'urn:oasis:names:tc:SAML:1.0:assertion'
|
||||
|
||||
# After constructing the request, let's plug in some values
|
||||
username.text = self.username
|
||||
password.text = self.password
|
||||
to.text = self.identity_provider_url
|
||||
wsa_address.text = self.service_provider_endpoint
|
||||
|
||||
self.prepared_request = root
|
||||
|
||||
def _get_adfs_security_token(self, session):
|
||||
"""Send ADFS Security token to the ADFS server.
|
||||
|
||||
Store the result in the instance attribute and raise an exception in
|
||||
case the response is not valid XML data.
|
||||
|
||||
If a user cannot authenticate due to providing bad credentials, the
|
||||
ADFS2.0 server will return a HTTP 500 response and a XML Fault message.
|
||||
If ``exceptions.InternalServerError`` is caught, the method tries to
|
||||
parse the XML response.
|
||||
If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is
|
||||
raised with a reason from the XML fault. Otherwise an original
|
||||
``exceptions.InternalServerError`` is re-raised.
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure: when HTTP
|
||||
response from the ADFS server is not a valid XML ADFS security
|
||||
token.
|
||||
:raises keystoneclient.exceptions.InternalServerError: If response
|
||||
status code is HTTP 500 and the response XML cannot be
|
||||
recognized.
|
||||
|
||||
"""
|
||||
def _get_failure(e):
|
||||
xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value'
|
||||
content = e.response.content
|
||||
try:
|
||||
obj = self.str_to_xml(content).xpath(
|
||||
xpath, namespaces=self.NAMESPACES)
|
||||
obj = self._first(obj)
|
||||
return obj.text
|
||||
# NOTE(marek-denis): etree.Element.xpath() doesn't raise an
|
||||
# exception, it just returns an empty list. In that case, _first()
|
||||
# will raise IndexError and we should treat it as an indication XML
|
||||
# is not valid. exceptions.AuthorizationFailure can be raised from
|
||||
# str_to_xml(), however since server returned HTTP 500 we should
|
||||
# re-raise exceptions.InternalServerError.
|
||||
except (IndexError, exceptions.AuthorizationFailure):
|
||||
raise e
|
||||
|
||||
request_security_token = self.xml_to_str(self.prepared_request)
|
||||
try:
|
||||
response = session.post(
|
||||
url=self.identity_provider_url, headers=self.HEADER_SOAP,
|
||||
data=request_security_token, authenticated=False)
|
||||
except exceptions.InternalServerError as e:
|
||||
reason = _get_failure(e)
|
||||
raise exceptions.AuthorizationFailure(reason)
|
||||
msg = _("Error parsing XML returned from "
|
||||
"the ADFS Identity Provider, reason: %s")
|
||||
self.adfs_token = self.str_to_xml(response.content, msg)
|
||||
|
||||
def _prepare_sp_request(self):
|
||||
"""Prepare ADFS Security Token to be sent to the Service Provider.
|
||||
|
||||
The method works as follows:
|
||||
* Extract SAML2 assertion from the ADFS Security Token.
|
||||
* Replace namespaces
|
||||
* urlencode assertion
|
||||
* concatenate static string with the encoded assertion
|
||||
|
||||
"""
|
||||
assertion = self.adfs_token.xpath(
|
||||
self.ADFS_ASSERTION_XPATH, namespaces=self.ADFS_TOKEN_NAMESPACES)
|
||||
assertion = self._first(assertion)
|
||||
assertion = self.xml_to_str(assertion)
|
||||
# TODO(marek-denis): Ideally no string replacement should occur.
|
||||
# Unfortunately lxml doesn't allow for namespaces changing in-place and
|
||||
# probably the only solution good for now is to build the assertion
|
||||
# from scratch and reuse values from the adfs security token.
|
||||
assertion = assertion.replace(
|
||||
b'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
|
||||
b'http://schemas.xmlsoap.org/ws/2005/02/trust')
|
||||
|
||||
encoded_assertion = urllib.parse.quote(assertion)
|
||||
self.encoded_assertion = 'wa=wsignin1.0&wresult=' + encoded_assertion
|
||||
|
||||
def _send_assertion_to_service_provider(self, session):
|
||||
"""Send prepared assertion to a service provider.
|
||||
|
||||
As the assertion doesn't contain a protected resource, the value from
|
||||
the ``location`` header is not valid and we should not let the Session
|
||||
object get redirected there. The aim of this call is to get a cookie in
|
||||
the response which is required for entering a protected endpoint.
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises: Corresponding HTTP error exception
|
||||
|
||||
"""
|
||||
session.post(
|
||||
url=self.service_provider_endpoint, data=self.encoded_assertion,
|
||||
headers=self.HEADER_X_FORM, redirect=False, authenticated=False)
|
||||
|
||||
def _access_service_provider(self, session):
|
||||
"""Access protected endpoint and fetch unscoped token.
|
||||
|
||||
After federated authentication workflow a protected endpoint should be
|
||||
accessible with the session object. The access is granted basing on the
|
||||
cookies stored within the session object. If, for some reason no
|
||||
cookies are present (quantity test) it means something went wrong and
|
||||
user will not be able to fetch an unscoped token. In that case an
|
||||
``exceptions.AuthorizationFailure` exception is raised and no HTTP call
|
||||
is even made.
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure: in case session
|
||||
object has empty cookie jar.
|
||||
|
||||
"""
|
||||
if self._cookies(session) is False:
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Session object doesn't contain a cookie, therefore you are "
|
||||
"not allowed to enter the Identity Provider's protected "
|
||||
"area."))
|
||||
self.authenticated_response = session.get(self.token_url,
|
||||
authenticated=False)
|
||||
|
||||
def _get_unscoped_token(self, session, *kwargs):
|
||||
"""Retrieve unscoped token after authentcation with ADFS server.
|
||||
|
||||
This is a multistep process::
|
||||
|
||||
* Prepare ADFS Request Securty Token -
|
||||
build an etree.XML object filling certain attributes with proper user
|
||||
credentials, created/expires dates (ticket is be valid for 120 seconds
|
||||
as currently we don't handle reusing ADFS issued security tokens) .
|
||||
Step handled by ``ADFSUnscopedToken._prepare_adfs_request()`` method.
|
||||
|
||||
* Send ADFS Security token to the ADFS server. Step handled by
|
||||
``ADFSUnscopedToken._get_adfs_security_token()`` method.
|
||||
|
||||
* Receive and parse security token, extract actual SAML assertion and
|
||||
prepare a request addressed for the Service Provider endpoint.
|
||||
This also includes changing namespaces in the XML document. Step
|
||||
handled by ``ADFSUnscopedToken._prepare_sp_request()`` method.
|
||||
|
||||
* Send prepared assertion to the Service Provider endpoint. Usually
|
||||
the server will respond with HTTP 301 code which should be ignored as
|
||||
the 'location' header doesn't contain protected area. The goal of this
|
||||
operation is fetching the session cookie which later allows for
|
||||
accessing protected URL endpoints. Step handed by
|
||||
``ADFSUnscopedToken._send_assertion_to_service_provider()`` method.
|
||||
|
||||
* Once the session cookie is issued, the protected endpoint can be
|
||||
accessed and an unscoped token can be retrieved. Step handled by
|
||||
``ADFSUnscopedToken._access_service_provider()`` method.
|
||||
|
||||
:param session : a session object to send out HTTP requests.
|
||||
:type session: keystoneclient.session.Session
|
||||
|
||||
:returns: (Unscoped federated token, token JSON body)
|
||||
|
||||
"""
|
||||
self._prepare_adfs_request()
|
||||
self._get_adfs_security_token(session)
|
||||
self._prepare_sp_request()
|
||||
self._send_assertion_to_service_provider(session)
|
||||
self._access_service_provider(session)
|
||||
|
||||
try:
|
||||
return (self.authenticated_response.headers['X-Subject-Token'],
|
||||
self.authenticated_response.json()['token'])
|
||||
except (KeyError, ValueError):
|
||||
raise exceptions.InvalidResponse(
|
||||
response=self.authenticated_response)
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
token, token_json = self._get_unscoped_token(session)
|
||||
return access.AccessInfoV3(token, **token_json)
|
||||
|
||||
|
||||
class Saml2ScopedTokenMethod(v3.TokenMethod):
|
||||
_method_name = 'saml2'
|
||||
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
"""Build and return request body for token scoping step."""
|
||||
t = super(Saml2ScopedTokenMethod, self).get_auth_data(
|
||||
session, auth, headers, **kwargs)
|
||||
_token_method, token = t
|
||||
return self._method_name, token
|
||||
|
||||
|
||||
class Saml2ScopedToken(v3.Token):
|
||||
"""Class for scoping unscoped saml2 token."""
|
||||
|
||||
_auth_method_class = Saml2ScopedTokenMethod
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
super(Saml2ScopedToken, self).__init__(auth_url, token, **kwargs)
|
||||
if not (self.project_id or self.domain_id):
|
||||
raise exceptions.ValidationError(
|
||||
_('Neither project nor domain specified'))
|
@@ -1,287 +0,0 @@
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# Copyright 2011 - 2012 Justin Santa Barbara
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import re
|
||||
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
class Ec2Signer(object):
|
||||
"""Utility class for EC2 signing of request.
|
||||
|
||||
This allows a request to be signed with an AWS style signature,
|
||||
which can then be used for authentication via the keystone ec2
|
||||
authentication extension.
|
||||
"""
|
||||
|
||||
def __init__(self, secret_key):
|
||||
self.secret_key = secret_key.encode()
|
||||
self.hmac = hmac.new(self.secret_key, digestmod=hashlib.sha1)
|
||||
if hashlib.sha256:
|
||||
self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256)
|
||||
|
||||
def _v4_creds(self, credentials):
|
||||
"""Detect if the credentials are for a v4 signed request.
|
||||
|
||||
Check is needed since AWS removed the SignatureVersion field from
|
||||
the v4 request spec...
|
||||
|
||||
This expects a dict of the request headers to be passed in the
|
||||
credentials dict, since the recommended way to pass v4 creds is
|
||||
via the 'Authorization' header
|
||||
see http://docs.aws.amazon.com/general/latest/gr/
|
||||
sigv4-signed-request-examples.html
|
||||
|
||||
Alternatively X-Amz-Algorithm can be specified as a query parameter,
|
||||
and the authentication data can also passed as query parameters.
|
||||
|
||||
Note a hash of the request body is also required in the credentials
|
||||
for v4 auth to work in the body_hash key, calculated via:
|
||||
hashlib.sha256(req.body).hexdigest()
|
||||
"""
|
||||
try:
|
||||
auth_str = credentials['headers']['Authorization']
|
||||
if auth_str.startswith('AWS4-HMAC-SHA256'):
|
||||
return True
|
||||
except KeyError:
|
||||
# Alternatively the Authorization data can be passed via
|
||||
# the query params list, check X-Amz-Algorithm=AWS4-HMAC-SHA256
|
||||
try:
|
||||
if (credentials['params']['X-Amz-Algorithm'] ==
|
||||
'AWS4-HMAC-SHA256'):
|
||||
return True
|
||||
except KeyError: # nosec(cjschaef): in cases of not finding
|
||||
# entries, simply return False
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def generate(self, credentials):
|
||||
"""Generate auth string according to what SignatureVersion is given."""
|
||||
signature_version = credentials['params'].get('SignatureVersion')
|
||||
if signature_version == '0':
|
||||
return self._calc_signature_0(credentials['params'])
|
||||
if signature_version == '1':
|
||||
return self._calc_signature_1(credentials['params'])
|
||||
if signature_version == '2':
|
||||
return self._calc_signature_2(credentials['params'],
|
||||
credentials['verb'],
|
||||
credentials['host'],
|
||||
credentials['path'])
|
||||
if self._v4_creds(credentials):
|
||||
return self._calc_signature_4(credentials['params'],
|
||||
credentials['verb'],
|
||||
credentials['host'],
|
||||
credentials['path'],
|
||||
credentials['headers'],
|
||||
credentials['body_hash'])
|
||||
|
||||
if signature_version is not None:
|
||||
raise Exception(_('Unknown signature version: %s') %
|
||||
signature_version)
|
||||
else:
|
||||
raise Exception(_('Unexpected signature format'))
|
||||
|
||||
@staticmethod
|
||||
def _get_utf8_value(value):
|
||||
"""Get the UTF8-encoded version of a value."""
|
||||
if not isinstance(value, (six.binary_type, six.text_type)):
|
||||
value = str(value)
|
||||
if isinstance(value, six.text_type):
|
||||
return value.encode('utf-8')
|
||||
else:
|
||||
return value
|
||||
|
||||
def _calc_signature_0(self, params):
|
||||
"""Generate AWS signature version 0 string."""
|
||||
s = (params['Action'] + params['Timestamp']).encode('utf-8')
|
||||
self.hmac.update(s)
|
||||
return base64.b64encode(self.hmac.digest()).decode('utf-8')
|
||||
|
||||
def _calc_signature_1(self, params):
|
||||
"""Generate AWS signature version 1 string."""
|
||||
keys = list(params)
|
||||
keys.sort(key=six.text_type.lower)
|
||||
for key in keys:
|
||||
self.hmac.update(key.encode('utf-8'))
|
||||
val = self._get_utf8_value(params[key])
|
||||
self.hmac.update(val)
|
||||
return base64.b64encode(self.hmac.digest()).decode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def _canonical_qs(params):
|
||||
"""Construct a sorted, correctly encoded query string.
|
||||
|
||||
This is required for _calc_signature_2 and _calc_signature_4.
|
||||
"""
|
||||
keys = list(params)
|
||||
keys.sort()
|
||||
pairs = []
|
||||
for key in keys:
|
||||
val = Ec2Signer._get_utf8_value(params[key])
|
||||
val = urllib.parse.quote(val, safe='-_~')
|
||||
pairs.append(urllib.parse.quote(key, safe='') + '=' + val)
|
||||
qs = '&'.join(pairs)
|
||||
return qs
|
||||
|
||||
def _calc_signature_2(self, params, verb, server_string, path):
|
||||
"""Generate AWS signature version 2 string."""
|
||||
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
|
||||
if self.hmac_256:
|
||||
current_hmac = self.hmac_256
|
||||
params['SignatureMethod'] = 'HmacSHA256'
|
||||
else:
|
||||
current_hmac = self.hmac
|
||||
params['SignatureMethod'] = 'HmacSHA1'
|
||||
string_to_sign += self._canonical_qs(params)
|
||||
current_hmac.update(string_to_sign.encode('utf-8'))
|
||||
b64 = base64.b64encode(current_hmac.digest()).decode('utf-8')
|
||||
return b64
|
||||
|
||||
def _calc_signature_4(self, params, verb, server_string, path, headers,
|
||||
body_hash):
|
||||
"""Generate AWS signature version 4 string."""
|
||||
def sign(key, msg):
|
||||
return hmac.new(key, self._get_utf8_value(msg),
|
||||
hashlib.sha256).digest()
|
||||
|
||||
def signature_key(datestamp, region_name, service_name):
|
||||
"""Signature key derivation.
|
||||
|
||||
See http://docs.aws.amazon.com/general/latest/gr/
|
||||
signature-v4-examples.html#signature-v4-examples-python
|
||||
"""
|
||||
k_date = sign(self._get_utf8_value(b"AWS4" + self.secret_key),
|
||||
datestamp)
|
||||
k_region = sign(k_date, region_name)
|
||||
k_service = sign(k_region, service_name)
|
||||
k_signing = sign(k_service, "aws4_request")
|
||||
return k_signing
|
||||
|
||||
def auth_param(param_name):
|
||||
"""Get specified auth parameter.
|
||||
|
||||
Provided via one of:
|
||||
- the Authorization header
|
||||
- the X-Amz-* query parameters
|
||||
"""
|
||||
try:
|
||||
auth_str = headers['Authorization']
|
||||
param_str = auth_str.partition(
|
||||
'%s=' % param_name)[2].split(',')[0]
|
||||
except KeyError:
|
||||
param_str = params.get('X-Amz-%s' % param_name)
|
||||
return param_str
|
||||
|
||||
def date_param():
|
||||
"""Get the X-Amz-Date' value.
|
||||
|
||||
The value can be either a header or parameter.
|
||||
|
||||
Note AWS supports parsing the Date header also, but this is not
|
||||
currently supported here as it will require some format mangling
|
||||
So the X-Amz-Date value must be YYYYMMDDTHHMMSSZ format, then it
|
||||
can be used to match against the YYYYMMDD format provided in the
|
||||
credential scope.
|
||||
see:
|
||||
http://docs.aws.amazon.com/general/latest/gr/
|
||||
sigv4-date-handling.html
|
||||
"""
|
||||
try:
|
||||
return headers['X-Amz-Date']
|
||||
except KeyError:
|
||||
return params.get('X-Amz-Date')
|
||||
|
||||
def canonical_header_str():
|
||||
# Get the list of headers to include, from either
|
||||
# - the Authorization header (SignedHeaders key)
|
||||
# - the X-Amz-SignedHeaders query parameter
|
||||
headers_lower = dict((k.lower().strip(), v.strip())
|
||||
for (k, v) in headers.items())
|
||||
|
||||
# Boto versions < 2.9.3 strip the port component of the host:port
|
||||
# header, so detect the user-agent via the header and strip the
|
||||
# port if we detect an old boto version. FIXME: remove when all
|
||||
# distros package boto >= 2.9.3, this is a transitional workaround
|
||||
user_agent = headers_lower.get('user-agent', '')
|
||||
strip_port = re.match('Boto/2\.[0-9]\.[0-2]', user_agent)
|
||||
|
||||
header_list = []
|
||||
sh_str = auth_param('SignedHeaders')
|
||||
for h in sh_str.split(';'):
|
||||
if h not in headers_lower:
|
||||
continue
|
||||
|
||||
if h == 'host' and strip_port:
|
||||
header_list.append('%s:%s' %
|
||||
(h, headers_lower[h].split(':')[0]))
|
||||
continue
|
||||
|
||||
header_list.append('%s:%s' % (h, headers_lower[h]))
|
||||
return '\n'.join(header_list) + '\n'
|
||||
|
||||
def canonical_query_str(verb, params):
|
||||
# POST requests pass parameters in through the request body
|
||||
canonical_qs = ''
|
||||
if verb.upper() != 'POST':
|
||||
canonical_qs = self._canonical_qs(params)
|
||||
return canonical_qs
|
||||
|
||||
# Create canonical request:
|
||||
# http://docs.aws.amazon.com/general/latest/gr/
|
||||
# sigv4-create-canonical-request.html
|
||||
# Get parameters and headers in expected string format
|
||||
cr = "\n".join((verb.upper(), path,
|
||||
canonical_query_str(verb, params),
|
||||
canonical_header_str(),
|
||||
auth_param('SignedHeaders'),
|
||||
body_hash))
|
||||
|
||||
# Check the date, reject any request where the X-Amz-Date doesn't
|
||||
# match the credential scope
|
||||
credential = auth_param('Credential')
|
||||
credential_split = credential.split('/')
|
||||
credential_scope = '/'.join(credential_split[1:])
|
||||
credential_date = credential_split[1]
|
||||
param_date = date_param()
|
||||
if not param_date.startswith(credential_date):
|
||||
raise Exception(_('Request date mismatch error'))
|
||||
|
||||
# Create the string to sign
|
||||
# http://docs.aws.amazon.com/general/latest/gr/
|
||||
# sigv4-create-string-to-sign.html
|
||||
cr = cr.encode('utf-8')
|
||||
string_to_sign = '\n'.join(('AWS4-HMAC-SHA256',
|
||||
param_date,
|
||||
credential_scope,
|
||||
hashlib.sha256(cr).hexdigest()))
|
||||
|
||||
# Calculate the derived key, this requires a datestamp, region
|
||||
# and service, which can be extracted from the credential scope
|
||||
(req_region, req_service) = credential_split[2:4]
|
||||
s_key = signature_key(credential_date, req_region, req_service)
|
||||
# Finally calculate the signature!
|
||||
signature = hmac.new(s_key, self._get_utf8_value(string_to_sign),
|
||||
hashlib.sha256).hexdigest()
|
||||
return signature
|
@@ -1,364 +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 warnings
|
||||
|
||||
from debtcollector import removals
|
||||
from keystoneauth1 import plugin
|
||||
from positional import positional
|
||||
|
||||
from keystoneclient import _discover
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient import session as client_session
|
||||
from keystoneclient.v2_0 import client as v2_client
|
||||
from keystoneclient.v3 import client as v3_client
|
||||
|
||||
|
||||
_CLIENT_VERSIONS = {2: v2_client.Client,
|
||||
3: v3_client.Client}
|
||||
|
||||
|
||||
# functions needed from the private file that can be made public
|
||||
|
||||
def normalize_version_number(version):
|
||||
"""Turn a version representation into a tuple.
|
||||
|
||||
Takes a string, tuple or float which represent version formats we can
|
||||
handle and converts them into a (major, minor) version tuple that we can
|
||||
actually use for discovery.
|
||||
|
||||
e.g. 'v3.3' gives (3, 3)
|
||||
3.1 gives (3, 1)
|
||||
|
||||
:param version: Inputted version number to try and convert.
|
||||
|
||||
:returns: A usable version tuple
|
||||
:rtype: tuple
|
||||
|
||||
:raises TypeError: if the inputted version cannot be converted to tuple.
|
||||
"""
|
||||
return _discover.normalize_version_number(version)
|
||||
|
||||
|
||||
def version_match(required, candidate):
|
||||
"""Test that an available version satisfies the required version.
|
||||
|
||||
To be suitable a version must be of the same major version as required
|
||||
and be at least a match in minor/patch level.
|
||||
|
||||
eg. 3.3 is a match for a required 3.1 but 4.1 is not.
|
||||
|
||||
:param tuple required: the version that must be met.
|
||||
:param tuple candidate: the version to test against required.
|
||||
|
||||
:returns: True if candidate is suitable False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
return _discover.version_match(required, candidate)
|
||||
|
||||
|
||||
def available_versions(url, session=None, **kwargs):
|
||||
"""Retrieve raw version data from a url."""
|
||||
if not session:
|
||||
session = client_session.Session._construct(kwargs)
|
||||
|
||||
return _discover.get_version_data(session, url)
|
||||
|
||||
|
||||
class Discover(_discover.Discover):
|
||||
"""A means to discover and create clients.
|
||||
|
||||
Clients are created depending on the supported API versions on the server.
|
||||
|
||||
Querying the server is done on object creation and every subsequent method
|
||||
operates upon the data that was retrieved.
|
||||
|
||||
The connection parameters associated with this method are the same format
|
||||
and name as those used by a client (see
|
||||
:py:class:`keystoneclient.v2_0.client.Client` and
|
||||
:py:class:`keystoneclient.v3.client.Client`). If not overridden in
|
||||
subsequent methods they will also be what is passed to the constructed
|
||||
client.
|
||||
|
||||
In the event that auth_url and endpoint is provided then auth_url will be
|
||||
used in accordance with how the client operates.
|
||||
|
||||
.. warning::
|
||||
|
||||
Creating an instance of this class without using the session argument
|
||||
is deprecated as of the 1.7.0 release and may be removed in the 2.0.0
|
||||
release.
|
||||
|
||||
:param session: A session object that will be used for communication.
|
||||
Clients will also be constructed with this session.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
(optional)
|
||||
:param string endpoint: A user-supplied endpoint URL for the identity
|
||||
service. (optional)
|
||||
:param string original_ip: The original IP of the requesting user which
|
||||
will be sent to identity service in a
|
||||
'Forwarded' header. (optional) This is ignored
|
||||
if a session is provided. Deprecated as of the
|
||||
1.7.0 release and may be removed in the 2.0.0
|
||||
release.
|
||||
:param boolean debug: Enables debug logging of all request and responses to
|
||||
the identity service. default False (optional)
|
||||
This is ignored if a session is provided. Deprecated
|
||||
as of the 1.7.0 release and may be removed in the
|
||||
2.0.0 release.
|
||||
:param string cacert: Path to the Privacy Enhanced Mail (PEM) file which
|
||||
contains the trusted authority X.509 certificates
|
||||
needed to established SSL connection with the
|
||||
identity service. (optional) This is ignored if a
|
||||
session is provided. Deprecated as of the 1.7.0
|
||||
release and may be removed in the 2.0.0 release.
|
||||
:param string key: Path to the Privacy Enhanced Mail (PEM) file which
|
||||
contains the unencrypted client private key needed to
|
||||
established two-way SSL connection with the identity
|
||||
service. (optional) This is ignored if a session is
|
||||
provided. Deprecated as of the 1.7.0 release and may be
|
||||
removed in the 2.0.0 release.
|
||||
:param string cert: Path to the Privacy Enhanced Mail (PEM) file which
|
||||
contains the corresponding X.509 client certificate
|
||||
needed to established two-way SSL connection with the
|
||||
identity service. (optional) This is ignored if a
|
||||
session is provided. Deprecated as of the 1.7.0 release
|
||||
and may be removed in the 2.0.0 release.
|
||||
:param boolean insecure: Does not perform X.509 certificate validation when
|
||||
establishing SSL connection with identity service.
|
||||
default: False (optional) This is ignored if a
|
||||
session is provided. Deprecated as of the 1.7.0
|
||||
release and may be removed in the 2.0.0 release.
|
||||
:param bool authenticated: Should a token be used to perform the initial
|
||||
discovery operations. default: None (attach a
|
||||
token if an auth plugin is available).
|
||||
|
||||
"""
|
||||
|
||||
@positional(2)
|
||||
def __init__(self, session=None, authenticated=None, **kwargs):
|
||||
if not session:
|
||||
warnings.warn(
|
||||
'Constructing a Discover instance without using a session is '
|
||||
'deprecated as of the 1.7.0 release and may be removed in the '
|
||||
'2.0.0 release.', DeprecationWarning)
|
||||
session = client_session.Session._construct(kwargs)
|
||||
kwargs['session'] = session
|
||||
|
||||
url = None
|
||||
endpoint = kwargs.pop('endpoint', None)
|
||||
auth_url = kwargs.pop('auth_url', None)
|
||||
|
||||
if endpoint:
|
||||
self._use_endpoint = True
|
||||
url = endpoint
|
||||
elif auth_url:
|
||||
self._use_endpoint = False
|
||||
url = auth_url
|
||||
elif session.auth:
|
||||
self._use_endpoint = False
|
||||
url = session.get_endpoint(interface=plugin.AUTH_INTERFACE)
|
||||
|
||||
if not url:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
_('Not enough information to determine URL. Provide'
|
||||
' either a Session, or auth_url or endpoint'))
|
||||
|
||||
self._client_kwargs = kwargs
|
||||
super(Discover, self).__init__(session, url,
|
||||
authenticated=authenticated)
|
||||
|
||||
@removals.remove(message='Use raw_version_data instead.', version='1.7.0',
|
||||
removal_version='2.0.0')
|
||||
def available_versions(self, **kwargs):
|
||||
"""Return a list of identity APIs available on the server.
|
||||
|
||||
The list returned includes the data associated with them.
|
||||
|
||||
.. warning::
|
||||
|
||||
This method is deprecated as of the 1.7.0 release in favor of
|
||||
:meth:`raw_version_data` and may be removed in the 2.0.0 release.
|
||||
|
||||
:param bool unstable: Accept endpoints not marked 'stable'. (optional)
|
||||
Equates to setting allow_experimental
|
||||
and allow_unknown to True.
|
||||
:param bool allow_experimental: Allow experimental version endpoints.
|
||||
:param bool allow_deprecated: Allow deprecated version endpoints.
|
||||
:param bool allow_unknown: Allow endpoints with an unrecognised status.
|
||||
|
||||
:returns: A List of dictionaries as presented by the server. Each dict
|
||||
will contain the version and the URL to use for the version.
|
||||
It is a direct representation of the layout presented by the
|
||||
identity API.
|
||||
"""
|
||||
return self.raw_version_data(**kwargs)
|
||||
|
||||
@removals.removed_kwarg(
|
||||
'unstable',
|
||||
message='Use allow_experimental and allow_unknown instead.',
|
||||
version='1.7.0', removal_version='2.0.0')
|
||||
def raw_version_data(self, unstable=False, **kwargs):
|
||||
"""Get raw version information from URL.
|
||||
|
||||
Raw data indicates that only minimal validation processing is performed
|
||||
on the data, so what is returned here will be the data in the same
|
||||
format it was received from the endpoint.
|
||||
|
||||
:param bool unstable: equates to setting allow_experimental and
|
||||
allow_unknown. This argument is deprecated as of
|
||||
the 1.7.0 release and may be removed in the 2.0.0
|
||||
release.
|
||||
:param bool allow_experimental: Allow experimental version endpoints.
|
||||
:param bool allow_deprecated: Allow deprecated version endpoints.
|
||||
:param bool allow_unknown: Allow endpoints with an unrecognised status.
|
||||
|
||||
:returns: The endpoints returned from the server that match the
|
||||
criteria.
|
||||
:rtype: List
|
||||
|
||||
Example::
|
||||
|
||||
>>> from keystoneclient import discover
|
||||
>>> disc = discover.Discovery(auth_url='http://localhost:5000')
|
||||
>>> disc.raw_version_data()
|
||||
[{'id': 'v3.0',
|
||||
'links': [{'href': u'http://127.0.0.1:5000/v3/',
|
||||
'rel': u'self'}],
|
||||
'media-types': [
|
||||
{'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.identity-v3+json'},
|
||||
{'base': 'application/xml',
|
||||
'type': 'application/vnd.openstack.identity-v3+xml'}],
|
||||
'status': 'stable',
|
||||
'updated': '2013-03-06T00:00:00Z'},
|
||||
{'id': 'v2.0',
|
||||
'links': [{'href': u'http://127.0.0.1:5000/v2.0/',
|
||||
'rel': u'self'},
|
||||
{'href': u'...',
|
||||
'rel': u'describedby',
|
||||
'type': u'application/pdf'}],
|
||||
'media-types': [
|
||||
{'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.identity-v2.0+json'},
|
||||
{'base': 'application/xml',
|
||||
'type': 'application/vnd.openstack.identity-v2.0+xml'}],
|
||||
'status': 'stable',
|
||||
'updated': '2013-03-06T00:00:00Z'}]
|
||||
"""
|
||||
if unstable:
|
||||
kwargs.setdefault('allow_experimental', True)
|
||||
kwargs.setdefault('allow_unknown', True)
|
||||
|
||||
return super(Discover, self).raw_version_data(**kwargs)
|
||||
|
||||
def _calculate_version(self, version, unstable):
|
||||
version_data = None
|
||||
|
||||
if version:
|
||||
version_data = self.data_for(version)
|
||||
else:
|
||||
# if no version specified pick the latest one
|
||||
all_versions = self.version_data(unstable=unstable)
|
||||
if all_versions:
|
||||
version_data = all_versions[-1]
|
||||
|
||||
if not version_data:
|
||||
msg = _('Could not find a suitable endpoint')
|
||||
|
||||
if version:
|
||||
msg = _('Could not find a suitable endpoint for client '
|
||||
'version: %s') % str(version)
|
||||
|
||||
raise exceptions.VersionNotAvailable(msg)
|
||||
|
||||
return version_data
|
||||
|
||||
def _create_client(self, version_data, **kwargs):
|
||||
# Get the client for the version requested that was returned
|
||||
try:
|
||||
client_class = _CLIENT_VERSIONS[version_data['version'][0]]
|
||||
except KeyError:
|
||||
version = '.'.join(str(v) for v in version_data['version'])
|
||||
msg = _('No client available for version: %s') % version
|
||||
raise exceptions.DiscoveryFailure(msg)
|
||||
|
||||
# kwargs should take priority over stored kwargs.
|
||||
for k, v in self._client_kwargs.items():
|
||||
kwargs.setdefault(k, v)
|
||||
|
||||
# restore the url to either auth_url or endpoint depending on what
|
||||
# was initially given
|
||||
if self._use_endpoint:
|
||||
kwargs['auth_url'] = None
|
||||
kwargs['endpoint'] = version_data['url']
|
||||
else:
|
||||
kwargs['auth_url'] = version_data['url']
|
||||
kwargs['endpoint'] = None
|
||||
|
||||
return client_class(**kwargs)
|
||||
|
||||
def create_client(self, version=None, unstable=False, **kwargs):
|
||||
"""Factory function to create a new identity service client.
|
||||
|
||||
:param tuple version: The required version of the identity API. If
|
||||
specified the client will be selected such that
|
||||
the major version is equivalent and an endpoint
|
||||
provides at least the specified minor version.
|
||||
For example to specify the 3.1 API use (3, 1).
|
||||
(optional)
|
||||
:param bool unstable: Accept endpoints not marked 'stable'. (optional)
|
||||
:param kwargs: Additional arguments will override those provided to
|
||||
this object's constructor.
|
||||
|
||||
:returns: An instantiated identity client object.
|
||||
|
||||
:raises keystoneclient.exceptions.DiscoveryFailure: if the server
|
||||
response is invalid
|
||||
:raises keystoneclient.exceptions.VersionNotAvailable: if a suitable
|
||||
client cannot be found.
|
||||
"""
|
||||
version_data = self._calculate_version(version, unstable)
|
||||
return self._create_client(version_data, **kwargs)
|
||||
|
||||
|
||||
def add_catalog_discover_hack(service_type, old, new):
|
||||
"""Add a version removal rule for a particular service.
|
||||
|
||||
Originally deployments of OpenStack would contain a versioned endpoint in
|
||||
the catalog for different services. E.g. an identity service might look
|
||||
like ``http://localhost:5000/v2.0``. This is a problem when we want to use
|
||||
a different version like v3.0 as there is no way to tell where it is
|
||||
located. We cannot simply change all service catalogs either so there must
|
||||
be a way to handle the older style of catalog.
|
||||
|
||||
This function adds a rule for a given service type that if part of the URL
|
||||
matches a given regular expression in *old* then it will be replaced with
|
||||
the *new* value. This will replace all instances of old with new. It should
|
||||
therefore contain a regex anchor.
|
||||
|
||||
For example the included rule states::
|
||||
|
||||
add_catalog_version_hack('identity', re.compile('/v2.0/?$'), '/')
|
||||
|
||||
so if the catalog retrieves an *identity* URL that ends with /v2.0 or
|
||||
/v2.0/ then it should replace it simply with / to fix the user's catalog.
|
||||
|
||||
:param str service_type: The service type as defined in the catalog that
|
||||
the rule will apply to.
|
||||
:param re.RegexObject old: The regular expression to search for and replace
|
||||
if found.
|
||||
:param str new: The new string to replace the pattern with.
|
||||
"""
|
||||
_discover._VERSION_HACKS.add_discover_hack(service_type, old, new)
|
@@ -1,437 +0,0 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Exception definitions."""
|
||||
|
||||
from keystoneauth1 import exceptions as _exc
|
||||
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
ClientException = _exc.ClientException
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.base.ClientException`
|
||||
"""
|
||||
|
||||
ConnectionError = _exc.ConnectionError
|
||||
"""Cannot connect to API service.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectionError`
|
||||
"""
|
||||
|
||||
ConnectionRefused = _exc.ConnectFailure
|
||||
"""Connection refused while trying to connect to API service.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectFailure`
|
||||
"""
|
||||
|
||||
SSLError = _exc.SSLError
|
||||
"""An SSL error occurred.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.connection.SSLError`
|
||||
"""
|
||||
|
||||
AuthorizationFailure = _exc.AuthorizationFailure
|
||||
"""Cannot authorize API client.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.auth.AuthorizationFailure`
|
||||
"""
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
_("Authentication failed. Missing options: %s") %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %r") % auth_system)
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
EndpointException = _exc.CatalogException
|
||||
"""Something is rotten in Service Catalog.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.catalog.CatalogException`
|
||||
"""
|
||||
|
||||
EndpointNotFound = _exc.EndpointNotFound
|
||||
"""Could not find requested endpoint in Service Catalog.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.catalog.EndpointNotFound`
|
||||
"""
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
_("AmbiguousEndpoints: %r") % endpoints)
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
HttpError = _exc.HttpError
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.HttpError`
|
||||
"""
|
||||
|
||||
HTTPClientError = _exc.HTTPClientError
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.HTTPClientError`
|
||||
"""
|
||||
|
||||
HttpServerError = _exc.HttpServerError
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.HttpServerError`
|
||||
"""
|
||||
|
||||
|
||||
class HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
class MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
BadRequest = _exc.BadRequest
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.BadRequest`
|
||||
"""
|
||||
|
||||
Unauthorized = _exc.Unauthorized
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.Unauthorized`
|
||||
"""
|
||||
|
||||
PaymentRequired = _exc.PaymentRequired
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.PaymentRequired`
|
||||
"""
|
||||
|
||||
Forbidden = _exc.Forbidden
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.Forbidden`
|
||||
"""
|
||||
|
||||
NotFound = _exc.NotFound
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.NotFound`
|
||||
"""
|
||||
|
||||
MethodNotAllowed = _exc.MethodNotAllowed
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.MethodNotAllowed`
|
||||
"""
|
||||
|
||||
NotAcceptable = _exc.NotAcceptable
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.NotAcceptable`
|
||||
"""
|
||||
|
||||
ProxyAuthenticationRequired = _exc.ProxyAuthenticationRequired
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.ProxyAuthenticationRequired`
|
||||
"""
|
||||
|
||||
RequestTimeout = _exc.RequestTimeout
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.RequestTimeout`
|
||||
"""
|
||||
|
||||
Conflict = _exc.Conflict
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.Conflict`
|
||||
"""
|
||||
|
||||
Gone = _exc.Gone
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.Gone`
|
||||
"""
|
||||
|
||||
LengthRequired = _exc.LengthRequired
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.LengthRequired`
|
||||
"""
|
||||
|
||||
PreconditionFailed = _exc.PreconditionFailed
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.PreconditionFailed`
|
||||
"""
|
||||
|
||||
RequestEntityTooLarge = _exc.RequestEntityTooLarge
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.RequestEntityTooLarge`
|
||||
"""
|
||||
|
||||
RequestUriTooLong = _exc.RequestUriTooLong
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.RequestUriTooLong`
|
||||
"""
|
||||
|
||||
UnsupportedMediaType = _exc.UnsupportedMediaType
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.UnsupportedMediaType`
|
||||
"""
|
||||
|
||||
RequestedRangeNotSatisfiable = _exc.RequestedRangeNotSatisfiable
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
The client has asked for a portion of the file, but the server cannot
|
||||
supply that portion.
|
||||
An alias of
|
||||
:py:exc:`keystoneauth1.exceptions.http.RequestedRangeNotSatisfiable`
|
||||
"""
|
||||
|
||||
ExpectationFailed = _exc.ExpectationFailed
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.ExpectationFailed`
|
||||
"""
|
||||
|
||||
UnprocessableEntity = _exc.UnprocessableEntity
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.UnprocessableEntity`
|
||||
"""
|
||||
|
||||
InternalServerError = _exc.InternalServerError
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.InternalServerError`
|
||||
"""
|
||||
|
||||
HttpNotImplemented = _exc.HttpNotImplemented
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.HttpNotImplemented`
|
||||
"""
|
||||
|
||||
BadGateway = _exc.BadGateway
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
The server was acting as a gateway or proxy and received an invalid
|
||||
response from the upstream server.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.BadGateway`
|
||||
"""
|
||||
|
||||
ServiceUnavailable = _exc.ServiceUnavailable
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.ServiceUnavailable`
|
||||
"""
|
||||
|
||||
GatewayTimeout = _exc.GatewayTimeout
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
The server was acting as a gateway or proxy and did not receive a timely
|
||||
response from the upstream server.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.GatewayTimeout`
|
||||
"""
|
||||
|
||||
HttpVersionNotSupported = _exc.HttpVersionNotSupported
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.http.HttpVersionNotSupported`
|
||||
"""
|
||||
|
||||
from_response = _exc.from_response
|
||||
"""Return an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
An alias of :py:func:`keystoneauth1.exceptions.http.from_response`
|
||||
"""
|
||||
|
||||
|
||||
# NOTE(akurilin): This alias should be left here to support backwards
|
||||
# compatibility until we are sure that usage of these exceptions in
|
||||
# projects is correct.
|
||||
HTTPNotImplemented = HttpNotImplemented
|
||||
Timeout = RequestTimeout
|
||||
HTTPError = HttpError
|
||||
|
||||
|
||||
class CertificateConfigError(Exception):
|
||||
"""Error reading the certificate."""
|
||||
|
||||
def __init__(self, output):
|
||||
self.output = output
|
||||
msg = _('Unable to load certificate.')
|
||||
super(CertificateConfigError, self).__init__(msg)
|
||||
|
||||
|
||||
class CMSError(Exception):
|
||||
"""Error reading the certificate."""
|
||||
|
||||
def __init__(self, output):
|
||||
self.output = output
|
||||
msg = _('Unable to sign or verify data.')
|
||||
super(CMSError, self).__init__(msg)
|
||||
|
||||
EmptyCatalog = _exc.EmptyCatalog
|
||||
"""The service catalog is empty.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.catalog.EmptyCatalog`
|
||||
"""
|
||||
|
||||
DiscoveryFailure = _exc.DiscoveryFailure
|
||||
"""Discovery of client versions failed.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.discovery.DiscoveryFailure`
|
||||
"""
|
||||
|
||||
VersionNotAvailable = _exc.VersionNotAvailable
|
||||
"""Discovery failed as the version you requested is not available.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.discovery.VersionNotAvailable`
|
||||
"""
|
||||
|
||||
|
||||
class MethodNotImplemented(ClientException):
|
||||
"""Method not implemented by the keystoneclient API."""
|
||||
|
||||
MissingAuthPlugin = _exc.MissingAuthPlugin
|
||||
"""An authenticated request is required but no plugin available.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin`
|
||||
"""
|
||||
|
||||
NoMatchingPlugin = _exc.NoMatchingPlugin
|
||||
"""There were no auth plugins that could be created from the parameters
|
||||
provided.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin`
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedParameters(ClientException):
|
||||
"""A parameter that was provided or returned is not supported.
|
||||
|
||||
:param List(str) names: Names of the unsupported parameters.
|
||||
|
||||
.. py:attribute:: names
|
||||
|
||||
Names of the unsupported parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, names):
|
||||
self.names = names
|
||||
|
||||
m = _('The following parameters were given that are unsupported: %s')
|
||||
super(UnsupportedParameters, self).__init__(m % ', '.join(self.names))
|
||||
|
||||
|
||||
class InvalidResponse(ClientException):
|
||||
"""The response from the server is not valid for this request."""
|
||||
|
||||
def __init__(self, response):
|
||||
super(InvalidResponse, self).__init__()
|
||||
self.response = response
|
@@ -1,56 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Produce keystone compliant structures for testing.
|
||||
|
||||
The generators in this directory produce keystone compliant structures for
|
||||
use in testing.
|
||||
They should be considered part of the public API because they may be relied
|
||||
upon to generate test tokens for other clients. However they should never be
|
||||
imported into the main client (keystoneclient or other). Because of this there
|
||||
may be dependencies from this module on libraries that are only available in
|
||||
testing.
|
||||
|
||||
.. warning::
|
||||
|
||||
The keystoneclient.fixture package is deprecated in favor of
|
||||
keystoneauth1.fixture and will not be supported.
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from keystoneclient.fixture.discovery import * # noqa
|
||||
from keystoneclient.fixture import exception
|
||||
from keystoneclient.fixture import v2
|
||||
from keystoneclient.fixture import v3
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"The keystoneclient.fixture package is deprecated in favor of "
|
||||
"keystoneauth1.fixture and will not be supported.", DeprecationWarning)
|
||||
|
||||
|
||||
FixtureValidationError = exception.FixtureValidationError
|
||||
V2Token = v2.Token
|
||||
V3Token = v3.Token
|
||||
V3FederationToken = v3.V3FederationToken
|
||||
|
||||
__all__ = ('DiscoveryList',
|
||||
'FixtureValidationError',
|
||||
'V2Discovery',
|
||||
'V3Discovery',
|
||||
'V2Token',
|
||||
'V3Token',
|
||||
'V3FederationToken',
|
||||
)
|
@@ -1,38 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1.fixture import discovery
|
||||
|
||||
|
||||
__all__ = ('DiscoveryList',
|
||||
'V2Discovery',
|
||||
'V3Discovery',
|
||||
)
|
||||
|
||||
|
||||
V2Discovery = discovery.V2Discovery
|
||||
"""A Version element for a V2 identity service endpoint.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.discovery.V2Discovery`
|
||||
"""
|
||||
|
||||
V3Discovery = discovery.V3Discovery
|
||||
"""A Version element for a V3 identity service endpoint.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.discovery.V3Discovery`
|
||||
"""
|
||||
|
||||
DiscoveryList = discovery.DiscoveryList
|
||||
"""A List of version elements.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.discovery.DiscoveryList`
|
||||
"""
|
@@ -1,20 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1.fixture import exception
|
||||
|
||||
|
||||
FixtureValidationError = exception.FixtureValidationError
|
||||
"""The token you created is not legitimate.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.exception.FixtureValidationError``
|
||||
"""
|
@@ -1,20 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1.fixture import v2
|
||||
|
||||
|
||||
Token = v2.Token
|
||||
"""A V2 Keystone token that can be used for testing.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.v2.Token`
|
||||
"""
|
@@ -1,26 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1.fixture import v3
|
||||
|
||||
|
||||
Token = v3.Token
|
||||
"""A V3 Keystone token that can be used for testing.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.v3.Token`
|
||||
"""
|
||||
|
||||
V3FederationToken = v3.V3FederationToken
|
||||
"""A V3 Keystone Federation token that can be used for testing.
|
||||
|
||||
An alias of :py:exc:`keystoneauth1.fixture.v3.V3FederationToken`
|
||||
"""
|
@@ -1,4 +0,0 @@
|
||||
|
||||
__all__ = (
|
||||
'client',
|
||||
)
|
@@ -1,208 +0,0 @@
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from debtcollector import removals
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient import httpclient
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE(jamielennox): To be removed after Pike.
|
||||
@removals.removed_class('keystoneclient.generic.client.Client',
|
||||
message='Use keystoneauth discovery',
|
||||
version='3.9.0',
|
||||
removal_version='4.0.0')
|
||||
class Client(httpclient.HTTPClient):
|
||||
"""Client for the OpenStack Keystone pre-version calls API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the keystone
|
||||
service.
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
|
||||
Example::
|
||||
|
||||
>>> from keystoneclient.generic import client
|
||||
>>> root = client.Client(auth_url=KEYSTONE_URL)
|
||||
>>> versions = root.discover()
|
||||
...
|
||||
>>> from keystoneclient.v2_0 import client as v2client
|
||||
>>> keystone = v2client.Client(auth_url=versions['v2.0']['url'])
|
||||
...
|
||||
>>> user = keystone.users.get(USER_ID)
|
||||
>>> user.delete()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint=None, **kwargs):
|
||||
"""Initialize a new client for the Keystone v2.0 API."""
|
||||
super(Client, self).__init__(endpoint=endpoint, **kwargs)
|
||||
self.endpoint = endpoint
|
||||
|
||||
def discover(self, url=None):
|
||||
"""Discover Keystone servers and return API versions supported.
|
||||
|
||||
:param url: optional url to test (without version)
|
||||
|
||||
Returns::
|
||||
|
||||
{
|
||||
'message': 'Keystone found at http://127.0.0.1:5000/',
|
||||
'v2.0': {
|
||||
'status': 'beta',
|
||||
'url': 'http://127.0.0.1:5000/v2.0/',
|
||||
'id': 'v2.0'
|
||||
},
|
||||
}
|
||||
|
||||
"""
|
||||
if url:
|
||||
return self._check_keystone_versions(url)
|
||||
else:
|
||||
return self._local_keystone_exists()
|
||||
|
||||
def _local_keystone_exists(self):
|
||||
"""Check if Keystone is available on default local port 35357."""
|
||||
results = self._check_keystone_versions("http://localhost:35357")
|
||||
if results is None:
|
||||
results = self._check_keystone_versions("https://localhost:35357")
|
||||
return results
|
||||
|
||||
def _check_keystone_versions(self, url):
|
||||
"""Call Keystone URL and detects the available API versions."""
|
||||
try:
|
||||
resp, body = self._request(url, "GET",
|
||||
headers={'Accept':
|
||||
'application/json'})
|
||||
# Multiple Choices status code is returned by the root
|
||||
# identity endpoint, with references to one or more
|
||||
# Identity API versions -- v3 spec
|
||||
# some cases we get No Content
|
||||
if resp.status_code in (200, 204, 300):
|
||||
try:
|
||||
results = {}
|
||||
if 'version' in body:
|
||||
results['message'] = _("Keystone found at %s") % url
|
||||
version = body['version']
|
||||
# Stable/diablo incorrect format
|
||||
id, status, version_url = (
|
||||
self._get_version_info(version, url))
|
||||
results[str(id)] = {"id": id,
|
||||
"status": status,
|
||||
"url": version_url}
|
||||
return results
|
||||
elif 'versions' in body:
|
||||
# Correct format
|
||||
results['message'] = _("Keystone found at %s") % url
|
||||
for version in body['versions']['values']:
|
||||
id, status, version_url = (
|
||||
self._get_version_info(version, url))
|
||||
results[str(id)] = {"id": id,
|
||||
"status": status,
|
||||
"url": version_url}
|
||||
return results
|
||||
else:
|
||||
results['message'] = (
|
||||
_("Unrecognized response from %s") % url)
|
||||
return results
|
||||
except KeyError:
|
||||
raise exceptions.AuthorizationFailure()
|
||||
elif resp.status_code == 305:
|
||||
return self._check_keystone_versions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(resp, "GET", url)
|
||||
except Exception:
|
||||
_logger.exception('Failed to detect available versions.')
|
||||
|
||||
def discover_extensions(self, url=None):
|
||||
"""Discover Keystone extensions supported.
|
||||
|
||||
:param url: optional url to test (should have a version in it)
|
||||
|
||||
Returns::
|
||||
|
||||
{
|
||||
'message': 'Keystone extensions at http://127.0.0.1:35357/v2',
|
||||
'OS-KSEC2': 'OpenStack EC2 Credentials Extension',
|
||||
}
|
||||
|
||||
"""
|
||||
if url:
|
||||
return self._check_keystone_extensions(url)
|
||||
|
||||
def _check_keystone_extensions(self, url):
|
||||
"""Call Keystone URL and detects the available extensions."""
|
||||
try:
|
||||
if not url.endswith("/"):
|
||||
url += '/'
|
||||
resp, body = self._request("%sextensions" % url, "GET",
|
||||
headers={'Accept':
|
||||
'application/json'})
|
||||
if resp.status_code in (200, 204): # some cases we get No Content
|
||||
if 'extensions' in body and 'values' in body['extensions']:
|
||||
# Parse correct format (per contract)
|
||||
extensions = body['extensions']['values']
|
||||
elif 'extensions' in body:
|
||||
# Support incorrect, but prevalent format
|
||||
extensions = body['extensions']
|
||||
else:
|
||||
return dict(message=(
|
||||
_('Unrecognized extensions response from %s') % url))
|
||||
|
||||
return dict(self._get_extension_info(e) for e in extensions)
|
||||
elif resp.status_code == 305:
|
||||
return self._check_keystone_extensions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(
|
||||
resp, "GET", "%sextensions" % url)
|
||||
except Exception:
|
||||
_logger.exception('Failed to check keystone extensions.')
|
||||
|
||||
@staticmethod
|
||||
def _get_version_info(version, root_url):
|
||||
"""Parse version information.
|
||||
|
||||
:param version: a dict of a Keystone version response
|
||||
:param root_url: string url used to construct
|
||||
the version if no URL is provided.
|
||||
:returns: tuple - (verionId, versionStatus, versionUrl)
|
||||
"""
|
||||
id = version['id']
|
||||
status = version['status']
|
||||
ref = urlparse.urljoin(root_url, id)
|
||||
if 'links' in version:
|
||||
for link in version['links']:
|
||||
if link['rel'] == 'self':
|
||||
ref = link['href']
|
||||
break
|
||||
return (id, status, ref)
|
||||
|
||||
@staticmethod
|
||||
def _get_extension_info(extension):
|
||||
"""Parse extension information.
|
||||
|
||||
:param extension: a dict of a Keystone extension response
|
||||
:returns: tuple - (alias, name)
|
||||
"""
|
||||
alias = extension['alias']
|
||||
name = extension['name']
|
||||
return (alias, name)
|
@@ -1,919 +0,0 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""OpenStack Client interface. Handles the REST calls and responses."""
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from debtcollector import removals
|
||||
from debtcollector import renames
|
||||
from keystoneauth1 import adapter
|
||||
from oslo_serialization import jsonutils
|
||||
import pkg_resources
|
||||
from positional import positional
|
||||
import requests
|
||||
|
||||
try:
|
||||
import pickle # nosec(cjschaef): see bug 1534288 for details
|
||||
|
||||
# NOTE(sdague): The conditional keyring import needs to only
|
||||
# trigger if it's a version of keyring that's supported in global
|
||||
# requirements. Update _min and _bad when that changes.
|
||||
keyring_v = pkg_resources.parse_version(
|
||||
pkg_resources.get_distribution("keyring").version)
|
||||
keyring_min = pkg_resources.parse_version('5.5.1')
|
||||
# This is a list of versions, e.g., pkg_resources.parse_version('3.3')
|
||||
keyring_bad = []
|
||||
|
||||
if keyring_v >= keyring_min and keyring_v not in keyring_bad:
|
||||
import keyring
|
||||
else:
|
||||
keyring = None
|
||||
except (ImportError, pkg_resources.DistributionNotFound):
|
||||
keyring = None
|
||||
pickle = None
|
||||
|
||||
|
||||
from keystoneclient import _discover
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth import base
|
||||
from keystoneclient import baseclient
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient import session as client_session
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
USER_AGENT = client_session.USER_AGENT
|
||||
"""Default user agent string.
|
||||
|
||||
This property is deprecated as of the 1.7.0 release in favor of
|
||||
:data:`keystoneclient.session.USER_AGENT` and may be removed in the 2.0.0
|
||||
release.
|
||||
"""
|
||||
|
||||
|
||||
@removals.remove(message='Use keystoneclient.session.request instead.',
|
||||
version='1.7.0', removal_version='2.0.0')
|
||||
def request(*args, **kwargs):
|
||||
"""Make a request.
|
||||
|
||||
This function is deprecated as of the 1.7.0 release in favor of
|
||||
:func:`keystoneclient.session.request` and may be removed in the
|
||||
2.0.0 release.
|
||||
"""
|
||||
return client_session.request(*args, **kwargs)
|
||||
|
||||
|
||||
class _FakeRequestSession(object):
|
||||
"""This object is a temporary hack that should be removed later.
|
||||
|
||||
Keystoneclient has a cyclical dependency with its managers which is
|
||||
preventing it from being cleaned up correctly. This is always bad but when
|
||||
we switched to doing connection pooling this object wasn't getting cleaned
|
||||
either and so we had left over TCP connections hanging around.
|
||||
|
||||
Until we can fix the client cleanup we rollback the use of a requests
|
||||
session and do individual connections like we used to.
|
||||
"""
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
return requests.request(*args, **kwargs)
|
||||
|
||||
|
||||
class _KeystoneAdapter(adapter.LegacyJsonAdapter):
|
||||
"""A wrapper layer to interface keystoneclient with a session.
|
||||
|
||||
An adapter provides a generic interface between a client and the session to
|
||||
provide client specific defaults. This object is passed to the managers.
|
||||
Keystoneclient managers have some additional requirements of variables that
|
||||
they expect to be present on the passed object.
|
||||
|
||||
Subclass the existing adapter to provide those values that keystoneclient
|
||||
managers expect.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Best effort to retrieve the user_id from the plugin.
|
||||
|
||||
Some managers rely on being able to get the currently authenticated
|
||||
user id. This is a problem when we are trying to abstract away the
|
||||
details of an auth plugin.
|
||||
|
||||
For example changing a user's password can require access to the
|
||||
currently authenticated user_id.
|
||||
|
||||
Perform a best attempt to fetch this data. It will work in the legacy
|
||||
case and with identity plugins and be None otherwise which is the same
|
||||
as the historical behavior.
|
||||
"""
|
||||
# the identity plugin case
|
||||
try:
|
||||
return self.session.auth.get_access(self.session).user_id
|
||||
except AttributeError: # nosec(cjschaef): attempt legacy retrival, or
|
||||
# return None
|
||||
pass
|
||||
|
||||
# there is a case that we explicitly allow (tested by our unit tests)
|
||||
# that says you should be able to set the user_id on a legacy client
|
||||
# and it should overwrite the one retrieved via authentication. If it's
|
||||
# a legacy then self.session.auth is a client and we retrieve user_id.
|
||||
try:
|
||||
return self.session.auth.user_id
|
||||
except AttributeError: # nosec(cjschaef): retrivals failed, return
|
||||
# None
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
"""HTTP client.
|
||||
|
||||
.. warning::
|
||||
|
||||
Creating an instance of this class without using the session argument
|
||||
is deprecated as of the 1.7.0 release and may be removed in the 2.0.0
|
||||
release.
|
||||
|
||||
:param string user_id: User ID for authentication. (optional)
|
||||
:param string username: Username for authentication. (optional)
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
(optional)
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
(optional)
|
||||
:param string password: Password for authentication. (optional)
|
||||
:param string domain_id: Domain ID for domain scoping. (optional)
|
||||
:param string domain_name: Domain name for domain scoping. (optional)
|
||||
:param string project_id: Project ID for project scoping. (optional)
|
||||
:param string project_name: Project name for project scoping. (optional)
|
||||
:param string project_domain_id: Project's domain ID for project scoping.
|
||||
(optional)
|
||||
:param string project_domain_name: Project's domain name for project
|
||||
scoping. (optional)
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string region_name: Name of a region to select when choosing an
|
||||
endpoint from the service catalog.
|
||||
:param integer timeout: This argument is deprecated as of the 1.7.0 release
|
||||
in favor of session and may be removed in the 2.0.0
|
||||
release. (optional)
|
||||
:param string endpoint: A user-supplied endpoint URL for the identity
|
||||
service. Lazy-authentication is possible for API
|
||||
service calls if endpoint is set at instantiation.
|
||||
(optional)
|
||||
:param string token: Token for authentication. (optional)
|
||||
:param string cacert: This argument is deprecated as of the 1.7.0 release
|
||||
in favor of session and may be removed in the 2.0.0
|
||||
release. (optional)
|
||||
:param string key: This argument is deprecated as of the 1.7.0 release
|
||||
in favor of session and may be removed in the 2.0.0
|
||||
release. (optional)
|
||||
:param string cert: This argument is deprecated as of the 1.7.0 release
|
||||
in favor of session and may be removed in the 2.0.0
|
||||
release. (optional)
|
||||
:param boolean insecure: This argument is deprecated as of the 1.7.0
|
||||
release in favor of session and may be removed in
|
||||
the 2.0.0 release. (optional)
|
||||
:param string original_ip: This argument is deprecated as of the 1.7.0
|
||||
release in favor of session and may be removed
|
||||
in the 2.0.0 release. (optional)
|
||||
:param dict auth_ref: To allow for consumers of the client to manage their
|
||||
own caching strategy, you may initialize a client
|
||||
with a previously captured auth_reference (token). If
|
||||
there are keyword arguments passed that also exist in
|
||||
auth_ref, the value from the argument will take
|
||||
precedence.
|
||||
:param boolean use_keyring: Enables caching auth_ref into keyring.
|
||||
default: False (optional)
|
||||
:param boolean force_new_token: Keyring related parameter, forces request
|
||||
for new token. default: False (optional)
|
||||
:param integer stale_duration: Gap in seconds to determine if token from
|
||||
keyring is about to expire. default: 30
|
||||
(optional)
|
||||
:param string tenant_name: Tenant name. (optional) The tenant_name keyword
|
||||
argument is deprecated as of the 1.7.0 release
|
||||
in favor of project_name and may be removed in
|
||||
the 2.0.0 release.
|
||||
:param string tenant_id: Tenant id. (optional) The tenant_id keyword
|
||||
argument is deprecated as of the 1.7.0 release in
|
||||
favor of project_id and may be removed in the
|
||||
2.0.0 release.
|
||||
:param string trust_id: Trust ID for trust scoping. (optional)
|
||||
:param object session: A Session object to be used for
|
||||
communicating with the identity service.
|
||||
:type session: keystoneclient.session.Session
|
||||
:param string service_name: The default service_name for URL discovery.
|
||||
default: None (optional)
|
||||
:param string interface: The default interface for URL discovery.
|
||||
default: admin (optional)
|
||||
:param string endpoint_override: Always use this endpoint URL for requests
|
||||
for this client. (optional)
|
||||
:param auth: An auth plugin to use instead of the session one. (optional)
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
:param string user_agent: The User-Agent string to set.
|
||||
default: python-keystoneclient (optional)
|
||||
:param int connect_retries: the maximum number of retries that should
|
||||
be attempted for connection errors.
|
||||
Default None - use session default which
|
||||
is don't retry. (optional)
|
||||
"""
|
||||
|
||||
version = None
|
||||
|
||||
@renames.renamed_kwarg('tenant_name', 'project_name', version='1.7.0',
|
||||
removal_version='2.0.0')
|
||||
@renames.renamed_kwarg('tenant_id', 'project_id', version='1.7.0',
|
||||
removal_version='2.0.0')
|
||||
@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,
|
||||
force_new_token=False, stale_duration=None, user_id=None,
|
||||
user_domain_id=None, user_domain_name=None, domain_id=None,
|
||||
domain_name=None, project_id=None, project_name=None,
|
||||
project_domain_id=None, project_domain_name=None,
|
||||
trust_id=None, session=None, service_name=None,
|
||||
interface='admin', endpoint_override=None, auth=None,
|
||||
user_agent=USER_AGENT, connect_retries=None, **kwargs):
|
||||
# set baseline defaults
|
||||
self.user_id = None
|
||||
self.username = None
|
||||
self.user_domain_id = None
|
||||
self.user_domain_name = None
|
||||
|
||||
self.domain_id = None
|
||||
self.domain_name = None
|
||||
|
||||
self.project_id = None
|
||||
self.project_name = None
|
||||
self.project_domain_id = None
|
||||
self.project_domain_name = None
|
||||
|
||||
self.auth_url = None
|
||||
self._endpoint = None
|
||||
self._management_url = None
|
||||
|
||||
self.trust_id = None
|
||||
|
||||
# if loading from a dictionary passed in via auth_ref,
|
||||
# load values from AccessInfo parsing that dictionary
|
||||
if auth_ref:
|
||||
self.auth_ref = access.AccessInfo.factory(**auth_ref)
|
||||
self.version = self.auth_ref.version
|
||||
self.user_id = self.auth_ref.user_id
|
||||
self.username = self.auth_ref.username
|
||||
self.user_domain_id = self.auth_ref.user_domain_id
|
||||
self.domain_id = self.auth_ref.domain_id
|
||||
self.domain_name = self.auth_ref.domain_name
|
||||
self.project_id = self.auth_ref.project_id
|
||||
self.project_name = self.auth_ref.project_name
|
||||
self.project_domain_id = self.auth_ref.project_domain_id
|
||||
auth_urls = self.auth_ref.service_catalog.get_urls(
|
||||
service_type='identity', endpoint_type='public',
|
||||
region_name=region_name)
|
||||
self.auth_url = auth_urls[0]
|
||||
management_urls = self.auth_ref.service_catalog.get_urls(
|
||||
service_type='identity', endpoint_type='admin',
|
||||
region_name=region_name)
|
||||
self._management_url = management_urls[0]
|
||||
self.auth_token_from_user = self.auth_ref.auth_token
|
||||
self.trust_id = self.auth_ref.trust_id
|
||||
|
||||
# TODO(blk-u): Using self.auth_ref.service_catalog._region_name is
|
||||
# deprecated and this code must be removed when the property is
|
||||
# actually removed.
|
||||
if self.auth_ref.has_service_catalog() and not region_name:
|
||||
region_name = self.auth_ref.service_catalog._region_name
|
||||
|
||||
else:
|
||||
self.auth_ref = None
|
||||
|
||||
# allow override of the auth_ref defaults from explicit
|
||||
# values provided to the client
|
||||
|
||||
# apply deprecated variables first, so modern variables override them
|
||||
if tenant_id:
|
||||
self.project_id = tenant_id
|
||||
if tenant_name:
|
||||
self.project_name = tenant_name
|
||||
|
||||
# user-related attributes
|
||||
self.password = password
|
||||
if user_id:
|
||||
self.user_id = user_id
|
||||
if username:
|
||||
self.username = username
|
||||
if user_domain_id:
|
||||
self.user_domain_id = user_domain_id
|
||||
elif not (user_id or user_domain_name):
|
||||
self.user_domain_id = 'default'
|
||||
if user_domain_name:
|
||||
self.user_domain_name = user_domain_name
|
||||
|
||||
# domain-related attributes
|
||||
if domain_id:
|
||||
self.domain_id = domain_id
|
||||
if domain_name:
|
||||
self.domain_name = domain_name
|
||||
|
||||
# project-related attributes
|
||||
if project_id:
|
||||
self.project_id = project_id
|
||||
if project_name:
|
||||
self.project_name = project_name
|
||||
if project_domain_id:
|
||||
self.project_domain_id = project_domain_id
|
||||
elif not (project_id or project_domain_name):
|
||||
self.project_domain_id = 'default'
|
||||
if project_domain_name:
|
||||
self.project_domain_name = project_domain_name
|
||||
|
||||
# trust-related attributes
|
||||
if trust_id:
|
||||
self.trust_id = trust_id
|
||||
|
||||
# endpoint selection
|
||||
if auth_url:
|
||||
self.auth_url = auth_url.rstrip('/')
|
||||
if token:
|
||||
self.auth_token_from_user = token
|
||||
else:
|
||||
self.auth_token_from_user = None
|
||||
if endpoint:
|
||||
self._endpoint = endpoint.rstrip('/')
|
||||
self._auth_token = None
|
||||
|
||||
if not session:
|
||||
|
||||
warnings.warn(
|
||||
'Constructing an HTTPClient instance without using a session '
|
||||
'is deprecated as of the 1.7.0 release and may be removed in '
|
||||
'the 2.0.0 release.', DeprecationWarning)
|
||||
|
||||
kwargs['session'] = _FakeRequestSession()
|
||||
session = client_session.Session._construct(kwargs)
|
||||
session.auth = self
|
||||
|
||||
self.session = session
|
||||
self.domain = ''
|
||||
|
||||
# NOTE(jamielennox): unfortunately we can't just use **kwargs here as
|
||||
# it would incompatibly limit the kwargs that can be passed to __init__
|
||||
# try and keep this list in sync with adapter.Adapter.__init__
|
||||
version = (
|
||||
_discover.normalize_version_number(self.version) if self.version
|
||||
else None)
|
||||
self._adapter = _KeystoneAdapter(session,
|
||||
service_type='identity',
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name,
|
||||
endpoint_override=endpoint_override,
|
||||
version=version,
|
||||
auth=auth,
|
||||
user_agent=user_agent,
|
||||
connect_retries=connect_retries)
|
||||
|
||||
# keyring setup
|
||||
if use_keyring and keyring is None:
|
||||
_logger.warning('Failed to load keyring modules.')
|
||||
self.use_keyring = use_keyring and keyring is not None
|
||||
self.force_new_token = force_new_token
|
||||
self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION
|
||||
self.stale_duration = int(self.stale_duration)
|
||||
|
||||
def get_token(self, session, **kwargs):
|
||||
return self.auth_token
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
if self._auth_token:
|
||||
return self._auth_token
|
||||
if self.auth_ref:
|
||||
if self.auth_ref.will_expire_soon(self.stale_duration):
|
||||
self.authenticate()
|
||||
return self.auth_ref.auth_token
|
||||
if self.auth_token_from_user:
|
||||
return self.auth_token_from_user
|
||||
|
||||
def get_endpoint(self, session, interface=None, **kwargs):
|
||||
if interface == 'public' or interface is base.AUTH_INTERFACE:
|
||||
return self.auth_url
|
||||
else:
|
||||
return self.management_url
|
||||
|
||||
def get_user_id(self, session, **kwargs):
|
||||
return self.auth_ref.user_id
|
||||
|
||||
def get_project_id(self, session, **kwargs):
|
||||
return self.auth_ref.project_id
|
||||
|
||||
@auth_token.setter
|
||||
def auth_token(self, value):
|
||||
"""Override the auth_token.
|
||||
|
||||
If an application sets auth_token explicitly then it will always be
|
||||
used and override any past or future retrieved token.
|
||||
"""
|
||||
self._auth_token = value
|
||||
|
||||
@auth_token.deleter
|
||||
def auth_token(self):
|
||||
self._auth_token = None
|
||||
self.auth_token_from_user = None
|
||||
|
||||
@property
|
||||
def service_catalog(self):
|
||||
"""Return this client's service catalog."""
|
||||
try:
|
||||
return self.auth_ref.service_catalog
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def has_service_catalog(self):
|
||||
"""Return True if this client provides a service catalog."""
|
||||
return self.auth_ref and self.auth_ref.has_service_catalog()
|
||||
|
||||
@property
|
||||
def tenant_id(self):
|
||||
"""Provide read-only backwards compatibility for tenant_id.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is deprecated as of the 1.7.0 release in favor of project_id
|
||||
and may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'tenant_id is deprecated as of the 1.7.0 release in favor of '
|
||||
'project_id and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
|
||||
return self.project_id
|
||||
|
||||
@property
|
||||
def tenant_name(self):
|
||||
"""Provide read-only backwards compatibility for tenant_name.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is deprecated as of the 1.7.0 release in favor of project_name
|
||||
and may be removed in the 2.0.0 release.
|
||||
"""
|
||||
warnings.warn(
|
||||
'tenant_name is deprecated as of the 1.7.0 release in favor of '
|
||||
'project_name and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
|
||||
return self.project_name
|
||||
|
||||
@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,
|
||||
project_name=None, project_id=None, user_domain_id=None,
|
||||
user_domain_name=None, project_domain_id=None,
|
||||
project_domain_name=None, trust_id=None,
|
||||
region_name=None):
|
||||
"""Authenticate user.
|
||||
|
||||
Uses the data provided at instantiation to authenticate against
|
||||
the Identity server. This may use either a username and password
|
||||
or token for authentication. If a tenant name or id was provided
|
||||
then the resulting authenticated client will be scoped to that
|
||||
tenant and contain a service catalog of available endpoints.
|
||||
|
||||
With the v2.0 API, if a tenant name or ID is not provided, the
|
||||
authentication token returned will be 'unscoped' and limited in
|
||||
capabilities until a fully-scoped token is acquired.
|
||||
|
||||
With the v3 API, if a domain name or id was provided then the resulting
|
||||
authenticated client will be scoped to that domain. If a project name
|
||||
or ID is not provided, and the authenticating user has a default
|
||||
project configured, the authentication token returned will be 'scoped'
|
||||
to the default project. Otherwise, the authentication token returned
|
||||
will be 'unscoped' and limited in capabilities until a fully-scoped
|
||||
token is acquired.
|
||||
|
||||
With the v3 API, with the OS-TRUST extension enabled, the trust_id can
|
||||
be provided to allow project-specific role delegation between users
|
||||
|
||||
If successful, sets the self.auth_ref and self.auth_token with
|
||||
the returned token. If not already set, will also set
|
||||
self.management_url from the details provided in the token.
|
||||
|
||||
:returns: ``True`` if authentication was successful.
|
||||
:raises keystoneclient.exceptions.AuthorizationFailure: if unable to
|
||||
authenticate or validate the existing authorization token
|
||||
:raises keystoneclient.exceptions.ValueError: if insufficient
|
||||
parameters are used.
|
||||
|
||||
If keyring is used, token is retrieved from keyring instead.
|
||||
Authentication will only be necessary if any of the following
|
||||
conditions are met:
|
||||
|
||||
* keyring is not used
|
||||
* if token is not found in keyring
|
||||
* if token retrieved from keyring is expired or about to
|
||||
expired (as determined by stale_duration)
|
||||
* if force_new_token is true
|
||||
|
||||
"""
|
||||
auth_url = auth_url or self.auth_url
|
||||
user_id = user_id or self.user_id
|
||||
username = username or self.username
|
||||
password = password or self.password
|
||||
|
||||
user_domain_id = user_domain_id or self.user_domain_id
|
||||
user_domain_name = user_domain_name or self.user_domain_name
|
||||
domain_id = domain_id or self.domain_id
|
||||
domain_name = domain_name or self.domain_name
|
||||
project_id = project_id or tenant_id or self.project_id
|
||||
project_name = project_name or tenant_name or self.project_name
|
||||
project_domain_id = project_domain_id or self.project_domain_id
|
||||
project_domain_name = project_domain_name or self.project_domain_name
|
||||
|
||||
trust_id = trust_id or self.trust_id
|
||||
region_name = region_name or self._adapter.region_name
|
||||
|
||||
if not token:
|
||||
token = self.auth_token_from_user
|
||||
if (not token and self.auth_ref and not
|
||||
self.auth_ref.will_expire_soon(self.stale_duration)):
|
||||
token = self.auth_ref.auth_token
|
||||
|
||||
kwargs = {
|
||||
'auth_url': auth_url,
|
||||
'user_id': user_id,
|
||||
'username': username,
|
||||
'user_domain_id': user_domain_id,
|
||||
'user_domain_name': user_domain_name,
|
||||
'domain_id': domain_id,
|
||||
'domain_name': domain_name,
|
||||
'project_id': project_id,
|
||||
'project_name': project_name,
|
||||
'project_domain_id': project_domain_id,
|
||||
'project_domain_name': project_domain_name,
|
||||
'token': token,
|
||||
'trust_id': trust_id,
|
||||
}
|
||||
(keyring_key, auth_ref) = self.get_auth_ref_from_keyring(**kwargs)
|
||||
new_token_needed = False
|
||||
if auth_ref is None or self.force_new_token:
|
||||
new_token_needed = True
|
||||
kwargs['password'] = password
|
||||
resp = self.get_raw_token_from_identity_service(**kwargs)
|
||||
|
||||
if isinstance(resp, access.AccessInfo):
|
||||
self.auth_ref = resp
|
||||
else:
|
||||
self.auth_ref = access.AccessInfo.factory(*resp)
|
||||
|
||||
# NOTE(jamielennox): The original client relies on being able to
|
||||
# push the region name into the service catalog but new auth
|
||||
# it in.
|
||||
if region_name:
|
||||
self.auth_ref.service_catalog._region_name = region_name
|
||||
else:
|
||||
self.auth_ref = auth_ref
|
||||
|
||||
self.process_token(region_name=region_name)
|
||||
if new_token_needed:
|
||||
self.store_auth_ref_into_keyring(keyring_key)
|
||||
return True
|
||||
|
||||
def _build_keyring_key(self, **kwargs):
|
||||
"""Create a unique key for keyring.
|
||||
|
||||
Used to store and retrieve auth_ref from keyring.
|
||||
|
||||
Return a slash-separated string of values ordered by key name.
|
||||
|
||||
"""
|
||||
return '/'.join([kwargs[k] or '?' for k in sorted(kwargs)])
|
||||
|
||||
def get_auth_ref_from_keyring(self, **kwargs):
|
||||
"""Retrieve auth_ref from keyring.
|
||||
|
||||
If auth_ref is found in keyring, (keyring_key, auth_ref) is returned.
|
||||
Otherwise, (keyring_key, None) is returned.
|
||||
|
||||
:returns: (keyring_key, auth_ref) or (keyring_key, None)
|
||||
:returns: or (None, None) if use_keyring is not set in the object
|
||||
|
||||
"""
|
||||
keyring_key = None
|
||||
auth_ref = None
|
||||
if self.use_keyring:
|
||||
keyring_key = self._build_keyring_key(**kwargs)
|
||||
try:
|
||||
auth_ref = keyring.get_password("keystoneclient_auth",
|
||||
keyring_key)
|
||||
if auth_ref:
|
||||
auth_ref = pickle.loads(auth_ref) # nosec(cjschaef): see
|
||||
# bug 1534288
|
||||
if auth_ref.will_expire_soon(self.stale_duration):
|
||||
# token has expired, don't use it
|
||||
auth_ref = None
|
||||
except Exception as e:
|
||||
auth_ref = None
|
||||
_logger.warning('Unable to retrieve token from keyring %s', e)
|
||||
return (keyring_key, auth_ref)
|
||||
|
||||
def store_auth_ref_into_keyring(self, keyring_key):
|
||||
"""Store auth_ref into keyring."""
|
||||
if self.use_keyring:
|
||||
try:
|
||||
keyring.set_password("keystoneclient_auth",
|
||||
keyring_key,
|
||||
pickle.dumps(self.auth_ref)) # nosec
|
||||
# (cjschaef): see bug 1534288
|
||||
except Exception as e:
|
||||
_logger.warning("Failed to store token into keyring %s", e)
|
||||
|
||||
def _process_management_url(self, region_name):
|
||||
try:
|
||||
self._management_url = self.auth_ref.service_catalog.url_for(
|
||||
service_type='identity',
|
||||
endpoint_type='admin',
|
||||
region_name=region_name)
|
||||
except exceptions.EndpointNotFound as e:
|
||||
_logger.debug("Failed to find endpoint for management url %s", e)
|
||||
|
||||
def process_token(self, region_name=None):
|
||||
"""Extract and process information from the new auth_ref.
|
||||
|
||||
And set the relevant authentication information.
|
||||
"""
|
||||
# if we got a response without a service catalog, set the local
|
||||
# list of tenants for introspection, and leave to client user
|
||||
# to determine what to do. Otherwise, load up the service catalog
|
||||
if self.auth_ref.project_scoped:
|
||||
if not self.auth_ref.tenant_id:
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Token didn't provide tenant_id"))
|
||||
self._process_management_url(region_name)
|
||||
self.project_name = self.auth_ref.tenant_name
|
||||
self.project_id = self.auth_ref.tenant_id
|
||||
|
||||
if not self.auth_ref.user_id:
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Token didn't provide user_id"))
|
||||
|
||||
self.user_id = self.auth_ref.user_id
|
||||
|
||||
self.auth_domain_id = self.auth_ref.domain_id
|
||||
self.auth_tenant_id = self.auth_ref.tenant_id
|
||||
self.auth_user_id = self.auth_ref.user_id
|
||||
|
||||
@property
|
||||
def management_url(self):
|
||||
return self._endpoint or self._management_url
|
||||
|
||||
@management_url.setter
|
||||
def management_url(self, value):
|
||||
# NOTE(jamielennox): it's debatable here whether we should set
|
||||
# _endpoint or _management_url. As historically management_url was set
|
||||
# permanently setting _endpoint would better match that behaviour.
|
||||
self._endpoint = value
|
||||
|
||||
@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,
|
||||
user_id=None, user_domain_id=None,
|
||||
user_domain_name=None,
|
||||
domain_id=None, domain_name=None,
|
||||
project_id=None, project_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
trust_id=None):
|
||||
"""Authenticate against the Identity API and get a token.
|
||||
|
||||
Not implemented here because auth protocols should be API
|
||||
version-specific.
|
||||
|
||||
Expected to authenticate or validate an existing authentication
|
||||
reference already associated with the client. Invoking this call
|
||||
*always* makes a call to the Identity service.
|
||||
|
||||
:returns: (``resp``, ``body``)
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self, entity):
|
||||
return jsonutils.dumps(entity)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def request(self, *args, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around requests.request to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used only by the managers and the managers now receive an
|
||||
adapter so this function is no longer on the standard request path.
|
||||
This may be removed in the 2.0.0 release.
|
||||
"""
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
def _request(self, *args, **kwargs):
|
||||
kwargs.setdefault('authenticated', False)
|
||||
return self._adapter.request(*args, **kwargs)
|
||||
|
||||
def _cs_request(self, url, method, management=True, **kwargs):
|
||||
"""Make an authenticated request to keystone endpoint.
|
||||
|
||||
Request are made to keystone endpoint by concatenating
|
||||
self.management_url and url and passing in method and
|
||||
any associated kwargs.
|
||||
"""
|
||||
if not management:
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', 'public')
|
||||
|
||||
kwargs.setdefault('authenticated', None)
|
||||
return self._request(url, method, **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def get(self, url, **kwargs):
|
||||
"""Perform an authenticated GET request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``GET`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'GET', **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def head(self, url, **kwargs):
|
||||
"""Perform an authenticated HEAD request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``HEAD`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'HEAD', **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def post(self, url, **kwargs):
|
||||
"""Perform an authenticate POST request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``POST`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'POST', **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def put(self, url, **kwargs):
|
||||
"""Perform an authenticate PUT request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``PUT`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'PUT', **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def patch(self, url, **kwargs):
|
||||
"""Perform an authenticate PATCH request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``PATCH`` and
|
||||
an authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'PATCH', **kwargs)
|
||||
|
||||
@removals.remove(version='1.7.0', removal_version='2.0.0')
|
||||
def delete(self, url, **kwargs):
|
||||
"""Perform an authenticate DELETE request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``DELETE`` and
|
||||
an authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
|
||||
*DEPRECATED*: This function is no longer used and is deprecated as
|
||||
of the 1.7.0 release and may be removed in the 2.0.0 release. It
|
||||
was designed to be used by the managers and the managers now
|
||||
receive an adapter so this function is no longer on the standard
|
||||
request path.
|
||||
"""
|
||||
return self._cs_request(url, 'DELETE', **kwargs)
|
||||
|
||||
deprecated_session_variables = {'original_ip': None,
|
||||
'cert': None,
|
||||
'timeout': None,
|
||||
'verify_cert': 'verify'}
|
||||
|
||||
deprecated_adapter_variables = {'region_name': None}
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Fetch deprecated session variables."""
|
||||
try:
|
||||
var_name = self.deprecated_session_variables[name]
|
||||
except KeyError: # nosec(cjschaef): try adapter variable or raise
|
||||
# an AttributeError
|
||||
pass
|
||||
else:
|
||||
warnings.warn(
|
||||
'The %s session variable is deprecated as of the 1.7.0 '
|
||||
'release and may be removed in the 2.0.0 release' % name,
|
||||
DeprecationWarning)
|
||||
return getattr(self.session, var_name or name)
|
||||
|
||||
try:
|
||||
var_name = self.deprecated_adapter_variables[name]
|
||||
except KeyError: # nosec(cjschaef): raise an AttributeError
|
||||
pass
|
||||
else:
|
||||
warnings.warn(
|
||||
'The %s adapter variable is deprecated as of the 1.7.0 '
|
||||
'release and may be removed in the 2.0.0 release' % name,
|
||||
DeprecationWarning)
|
||||
return getattr(self._adapter, var_name or name)
|
||||
|
||||
raise AttributeError(_("Unknown Attribute: %s") % name)
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
"""Assign value to deprecated seesion variables."""
|
||||
try:
|
||||
var_name = self.deprecated_session_variables[name]
|
||||
except KeyError: # nosec(cjschaef): try adapter variable or call
|
||||
# parent class's __setattr__
|
||||
pass
|
||||
else:
|
||||
warnings.warn(
|
||||
'The %s session variable is deprecated as of the 1.7.0 '
|
||||
'release and may be removed in the 2.0.0 release' % name,
|
||||
DeprecationWarning)
|
||||
return setattr(self.session, var_name or name)
|
||||
|
||||
try:
|
||||
var_name = self.deprecated_adapter_variables[name]
|
||||
except KeyError: # nosec(cjschaef): call parent class's __setattr__
|
||||
pass
|
||||
else:
|
||||
warnings.warn(
|
||||
'The %s adapter variable is deprecated as of the 1.7.0 '
|
||||
'release and may be removed in the 2.0.0 release' % name,
|
||||
DeprecationWarning)
|
||||
return setattr(self._adapter, var_name or name)
|
||||
|
||||
super(HTTPClient, self).__setattr__(name, val)
|
@@ -1,27 +0,0 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See https://docs.openstack.org/oslo.i18n/latest/user/index.html .
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='keystoneclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
@@ -1,432 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011, Piston Cloud Computing, Inc.
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
import warnings
|
||||
|
||||
from positional import positional
|
||||
import six
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServiceCatalog(object):
|
||||
"""Helper methods for dealing with a Keystone Service Catalog.
|
||||
|
||||
.. warning::
|
||||
|
||||
Setting region_name is deprecated in favor of passing the region name
|
||||
as a parameter to calls made to the service catalog as of the 1.7.0
|
||||
release and may be removed in the 2.0.0 release.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, resource_dict, token=None, region_name=None):
|
||||
"""Create ServiceCatalog object given an auth token.
|
||||
|
||||
.. warning::
|
||||
|
||||
Setting region_name is deprecated in favor of passing the region
|
||||
name as a parameter to calls made to the service catalog as of the
|
||||
1.7.0 release and may be removed in the 2.0.0 release.
|
||||
|
||||
"""
|
||||
if ServiceCatalogV3.is_valid(resource_dict):
|
||||
return ServiceCatalogV3(token, resource_dict, region_name)
|
||||
elif ServiceCatalogV2.is_valid(resource_dict):
|
||||
return ServiceCatalogV2(resource_dict, region_name)
|
||||
else:
|
||||
raise NotImplementedError(_('Unrecognized auth response'))
|
||||
|
||||
def __init__(self, region_name=None):
|
||||
if region_name:
|
||||
warnings.warn(
|
||||
'Setting region_name on the service catalog is deprecated in '
|
||||
'favor of passing the region name as a parameter to calls '
|
||||
'made to the service catalog as of the 1.7.0 release and may '
|
||||
'be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
|
||||
self._region_name = region_name
|
||||
|
||||
@property
|
||||
def region_name(self):
|
||||
"""Region name.
|
||||
|
||||
.. warning::
|
||||
|
||||
region_name is deprecated in favor of passing the region name as a
|
||||
parameter to calls made to the service catalog as of the 1.7.0
|
||||
release and may be removed in the 2.0.0 release.
|
||||
|
||||
"""
|
||||
warnings.warn(
|
||||
'region_name is deprecated in favor of passing the region name as '
|
||||
'a parameter to calls made to the service catalog as of the 1.7.0 '
|
||||
'release and may be removed in the 2.0.0 release.',
|
||||
DeprecationWarning)
|
||||
return self._region_name
|
||||
|
||||
def _get_endpoint_region(self, endpoint):
|
||||
return endpoint.get('region_id') or endpoint.get('region')
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_token(self):
|
||||
"""Fetch token details from service catalog.
|
||||
|
||||
Returns a dictionary containing the following::
|
||||
|
||||
- `id`: Token's ID
|
||||
- `expires`: Token's expiration
|
||||
- `user_id`: Authenticated user's ID
|
||||
- `tenant_id`: Authorized project's ID
|
||||
- `domain_id`: Authorized domain's ID
|
||||
|
||||
"""
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
@abc.abstractmethod
|
||||
def _is_endpoint_type_match(self, endpoint, endpoint_type):
|
||||
"""Helper function to normalize endpoint matching across v2 and v3.
|
||||
|
||||
:returns: True if the provided endpoint matches the required
|
||||
endpoint_type otherwise False.
|
||||
"""
|
||||
pass # pragma: no cover
|
||||
|
||||
@abc.abstractmethod
|
||||
def _normalize_endpoint_type(self, endpoint_type):
|
||||
"""Handle differences in the way v2 and v3 catalogs specify endpoint.
|
||||
|
||||
Both v2 and v3 must be able to handle the endpoint style of the other.
|
||||
For example v2 must be able to handle a 'public' endpoint_type and
|
||||
v3 must be able to handle a 'publicURL' endpoint_type.
|
||||
|
||||
:returns: the endpoint string in the format appropriate for this
|
||||
service catalog.
|
||||
"""
|
||||
pass # pragma: no cover
|
||||
|
||||
def get_endpoints(self, service_type=None, endpoint_type=None,
|
||||
region_name=None, service_name=None):
|
||||
"""Fetch and filter endpoints for the specified service(s).
|
||||
|
||||
Returns endpoints for the specified service (or all) containing
|
||||
the specified type (or all) and region (or all) and service name.
|
||||
|
||||
If there is no name in the service catalog the service_name check will
|
||||
be skipped. This allows compatibility with services that existed
|
||||
before the name was available in the catalog.
|
||||
"""
|
||||
endpoint_type = self._normalize_endpoint_type(endpoint_type)
|
||||
region_name = region_name or self._region_name
|
||||
|
||||
sc = {}
|
||||
|
||||
for service in (self.get_data() or []):
|
||||
try:
|
||||
st = service['type']
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if service_type and service_type != st:
|
||||
continue
|
||||
|
||||
# NOTE(jamielennox): service_name is different. It is not available
|
||||
# in API < v3.3. If it is in the catalog then we enforce it, if it
|
||||
# is not then we don't because the name could be correct we just
|
||||
# don't have that information to check against.
|
||||
if service_name:
|
||||
try:
|
||||
sn = service['name']
|
||||
except KeyError: # nosec(cjschaef)
|
||||
# assume that we're in v3.0-v3.2 and don't have the name in
|
||||
# the catalog. Skip the check.
|
||||
pass
|
||||
else:
|
||||
if service_name != sn:
|
||||
continue
|
||||
|
||||
endpoints = sc.setdefault(st, [])
|
||||
|
||||
for endpoint in service.get('endpoints', []):
|
||||
if (endpoint_type and not
|
||||
self._is_endpoint_type_match(endpoint, endpoint_type)):
|
||||
continue
|
||||
if (region_name and
|
||||
region_name != self._get_endpoint_region(endpoint)):
|
||||
continue
|
||||
endpoints.append(endpoint)
|
||||
|
||||
return sc
|
||||
|
||||
def _get_service_endpoints(self, attr, filter_value, service_type,
|
||||
endpoint_type, region_name, service_name):
|
||||
"""Fetch the endpoints of a particular service_type.
|
||||
|
||||
Endpoints returned are also filtered based on the attr and
|
||||
filter_value provided.
|
||||
"""
|
||||
sc_endpoints = self.get_endpoints(service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
region_name=region_name,
|
||||
service_name=service_name)
|
||||
|
||||
try:
|
||||
endpoints = sc_endpoints[service_type]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
if attr and not filter_value:
|
||||
warnings.warn(
|
||||
'Providing attr without filter_value to get_urls() is '
|
||||
'deprecated as of the 1.7.0 release and may be removed in the '
|
||||
'2.0.0 release. Either both should be provided or neither '
|
||||
'should be provided.')
|
||||
|
||||
if filter_value:
|
||||
return [endpoint for endpoint in endpoints
|
||||
if endpoint.get(attr) == filter_value]
|
||||
|
||||
return endpoints
|
||||
|
||||
@abc.abstractmethod
|
||||
@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):
|
||||
"""Fetch endpoint urls from the service catalog.
|
||||
|
||||
Fetch the endpoints from the service catalog for a particular
|
||||
endpoint attribute. If no attribute is given, return the first
|
||||
endpoint of the specified type.
|
||||
|
||||
:param string attr: Endpoint attribute name.
|
||||
:param string filter_value: Endpoint attribute value.
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL, admin or
|
||||
adminURL
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
|
||||
:returns: tuple of urls or None (if no match found)
|
||||
"""
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
@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):
|
||||
"""Fetch an endpoint from the service catalog.
|
||||
|
||||
Fetch the specified endpoint from the service catalog for
|
||||
a particular endpoint attribute. If no attribute is given, return
|
||||
the first endpoint of the specified type.
|
||||
|
||||
Valid endpoint types: `public` or `publicURL`,
|
||||
`internal` or `internalURL`,
|
||||
`admin` or 'adminURL`
|
||||
|
||||
:param string attr: Endpoint attribute name.
|
||||
:param string filter_value: Endpoint attribute value.
|
||||
:param string service_type: Service type of the endpoint.
|
||||
:param string endpoint_type: Type of endpoint.
|
||||
:param string region_name: Region of the endpoint.
|
||||
:param string service_name: The assigned name of the service.
|
||||
|
||||
"""
|
||||
if not self.get_data():
|
||||
raise exceptions.EmptyCatalog(_('The service catalog is empty.'))
|
||||
|
||||
urls = self.get_urls(attr=attr,
|
||||
filter_value=filter_value,
|
||||
service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
region_name=region_name,
|
||||
service_name=service_name)
|
||||
|
||||
try:
|
||||
return urls[0]
|
||||
except Exception:
|
||||
if service_name and region_name:
|
||||
msg = (_('%(endpoint_type)s endpoint for %(service_type)s '
|
||||
'service named %(service_name)s in %(region_name)s '
|
||||
'region not found') %
|
||||
{'endpoint_type': endpoint_type,
|
||||
'service_type': service_type,
|
||||
'service_name': service_name,
|
||||
'region_name': region_name})
|
||||
elif service_name:
|
||||
msg = (_('%(endpoint_type)s endpoint for %(service_type)s '
|
||||
'service named %(service_name)s not found') %
|
||||
{'endpoint_type': endpoint_type,
|
||||
'service_type': service_type,
|
||||
'service_name': service_name})
|
||||
elif region_name:
|
||||
msg = (_('%(endpoint_type)s endpoint for %(service_type)s '
|
||||
'service in %(region_name)s region not found') %
|
||||
{'endpoint_type': endpoint_type,
|
||||
'service_type': service_type,
|
||||
'region_name': region_name})
|
||||
else:
|
||||
msg = (_('%(endpoint_type)s endpoint for %(service_type)s '
|
||||
'service not found') %
|
||||
{'endpoint_type': endpoint_type,
|
||||
'service_type': service_type})
|
||||
|
||||
raise exceptions.EndpointNotFound(msg)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_data(self):
|
||||
"""Get the raw catalog structure.
|
||||
|
||||
Get the version dependent catalog structure as it is presented within
|
||||
the resource.
|
||||
|
||||
:returns: list containing raw catalog data entries or None
|
||||
"""
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
|
||||
class ServiceCatalogV2(ServiceCatalog):
|
||||
"""An object for encapsulating the v2 service catalog.
|
||||
|
||||
The object is created using raw v2 auth token from Keystone.
|
||||
"""
|
||||
|
||||
def __init__(self, resource_dict, region_name=None):
|
||||
self.catalog = resource_dict
|
||||
super(ServiceCatalogV2, self).__init__(region_name=region_name)
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, resource_dict):
|
||||
# This class is also used for reading token info of an unscoped token.
|
||||
# Unscoped token does not have 'serviceCatalog' in V2, checking this
|
||||
# will not work. Use 'token' attribute instead.
|
||||
return 'token' in resource_dict
|
||||
|
||||
def _normalize_endpoint_type(self, endpoint_type):
|
||||
if endpoint_type and 'URL' not in endpoint_type:
|
||||
endpoint_type += 'URL'
|
||||
|
||||
return endpoint_type
|
||||
|
||||
def _is_endpoint_type_match(self, endpoint, endpoint_type):
|
||||
return endpoint_type in endpoint
|
||||
|
||||
def get_data(self):
|
||||
return self.catalog.get('serviceCatalog')
|
||||
|
||||
def get_token(self):
|
||||
token = {'id': self.catalog['token']['id'],
|
||||
'expires': self.catalog['token']['expires']}
|
||||
try:
|
||||
token['user_id'] = self.catalog['user']['id']
|
||||
token['tenant_id'] = self.catalog['token']['tenant']['id']
|
||||
except KeyError: # nosec(cjschaef)
|
||||
# just leave the tenant and user out if it doesn't exist
|
||||
pass
|
||||
return token
|
||||
|
||||
@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):
|
||||
endpoint_type = self._normalize_endpoint_type(endpoint_type)
|
||||
endpoints = self._get_service_endpoints(attr=attr,
|
||||
filter_value=filter_value,
|
||||
service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
region_name=region_name,
|
||||
service_name=service_name)
|
||||
|
||||
if endpoints:
|
||||
return tuple([endpoint[endpoint_type] for endpoint in endpoints])
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ServiceCatalogV3(ServiceCatalog):
|
||||
"""An object for encapsulating the v3 service catalog.
|
||||
|
||||
The object is created using raw v3 auth token from Keystone.
|
||||
"""
|
||||
|
||||
def __init__(self, token, resource_dict, region_name=None):
|
||||
super(ServiceCatalogV3, self).__init__(region_name=region_name)
|
||||
self._auth_token = token
|
||||
self.catalog = resource_dict
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, resource_dict):
|
||||
# This class is also used for reading token info of an unscoped token.
|
||||
# Unscoped token does not have 'catalog', checking this
|
||||
# will not work. Use 'methods' attribute instead.
|
||||
return 'methods' in resource_dict
|
||||
|
||||
def _normalize_endpoint_type(self, endpoint_type):
|
||||
if endpoint_type:
|
||||
endpoint_type = endpoint_type.rstrip('URL')
|
||||
|
||||
return endpoint_type
|
||||
|
||||
def _is_endpoint_type_match(self, endpoint, endpoint_type):
|
||||
try:
|
||||
return endpoint_type == endpoint['interface']
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def get_data(self):
|
||||
return self.catalog.get('catalog')
|
||||
|
||||
def get_token(self):
|
||||
token = {'id': self._auth_token,
|
||||
'expires': self.catalog['expires_at']}
|
||||
try:
|
||||
token['user_id'] = self.catalog['user']['id']
|
||||
domain = self.catalog.get('domain')
|
||||
if domain:
|
||||
token['domain_id'] = domain['id']
|
||||
project = self.catalog.get('project')
|
||||
if project:
|
||||
token['tenant_id'] = project['id']
|
||||
except KeyError: # nosec(cjschaef)
|
||||
# just leave the domain, project and user out if it doesn't exist
|
||||
pass
|
||||
return token
|
||||
|
||||
@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):
|
||||
endpoints = self._get_service_endpoints(attr=attr,
|
||||
filter_value=filter_value,
|
||||
service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
region_name=region_name,
|
||||
service_name=service_name)
|
||||
|
||||
if endpoints:
|
||||
return tuple([endpoint['url'] for endpoint in endpoints])
|
||||
else:
|
||||
return None
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user