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: I3ca69746ffe73b7c01c3066476589fac8e16a5e9
This commit is contained in:
Tony Breeds 2017-09-12 16:00:55 -06:00
parent ccbd20ed9c
commit 1068cb6f7f
212 changed files with 14 additions and 27631 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = keystoneauth1
omit = keystoneauth1/tests/*
[report]
ignore_errors = True

25
.gitignore vendored
View File

@ -1,25 +0,0 @@
.coverage
.testrepository
subunit.log
.venv
*,cover
cover
*.pyc
.idea
*.sw?
*.egg
*~
.tox
AUTHORS
ChangeLog
build
dist
keystoneauth1.egg-info
doc/source/api
# Development environment files
.project
.pydevproject
# Temporary files created during test, but not removed
examples/pki/certs/tmp*
# Files created by releasenotes build
releasenotes/build

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/keystoneauth.git

View File

@ -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>

View File

@ -1,4 +0,0 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystoneauth1/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,18 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps documented at:
https://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works
and your OpenStack accounts are set up, you can skip to the
development workflow section of this documentation to learn how
changes to OpenStack should be submitted for review via the
Gerrit tool:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/keystoneauth

View File

@ -1,24 +0,0 @@
Keystone Style Commandments
===========================
- Step 1: Read the OpenStack Style Commandments
https://docs.openstack.org/hacking/latest/
- Step 2: Read on
Exceptions
----------
When dealing with exceptions from underlying libraries, translate those
exceptions to an instance or subclass of ClientException.
=======
Testing
=======
keystoneauth uses testtools and testr for its unittest suite
and its test runner. Basic workflow around our use of tox and testr can
be found at https://wiki.openstack.org/testr. If you'd like to learn more
in depth:
https://testtools.readthedocs.org/
https://testrepository.readthedocs.org/

209
LICENSE
View File

@ -1,209 +0,0 @@
Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1)
Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1)
Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7)
All rights reserved.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
--- License for keystoneauth versions prior to 2.1 ---
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of this project nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README Normal file
View 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.

View File

@ -1,35 +0,0 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/keystoneauth.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
============
keystoneauth
============
.. image:: https://img.shields.io/pypi/v/keystoneauth1.svg
:target: https://pypi.python.org/pypi/keystoneauth1/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/keystoneauth1.svg
:target: https://pypi.python.org/pypi/keystoneauth1/
:alt: Downloads
This package contains tools for authenticating to an OpenStack-based cloud.
These tools include:
* Authentication plugins (password, token, and federation based)
* Discovery mechanisms to determine API version support
* A session that is used to maintain client settings across requests (based on
the requests Python library)
Further information:
* Free software: Apache license
* Documentation: https://docs.openstack.org/keystoneauth/latest/
* Source: https://git.openstack.org/cgit/openstack/keystoneauth
* Bugs: https://bugs.launchpad.net/keystoneauth

View File

@ -1,8 +0,0 @@
# This is a cross-platform list tracking distribution packages needed for install and tests;
# see https://docs.openstack.org/infra/bindep/ for additional information.
build-essential [platform:dpkg test]
python-dev [platform:dpkg test]
python-devel [platform:rpm test]
libkrb5-dev [platform:dpkg test]
krb5-devel [platform:rpm test]

1
doc/.gitignore vendored
View File

@ -1 +0,0 @@
build/

View File

@ -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/keystoneauth.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystoneauth.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

View File

@ -1,93 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import inspect
from docutils import nodes
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from sphinx.util.nodes import nested_parse_with_titles
from stevedore import extension
class ListAuthPluginsDirective(rst.Directive):
"""Present a simple list of the plugins in a namespace."""
option_spec = {
'class': directives.class_option,
'overline-style': directives.single_char_or_unicode,
'underline-style': directives.single_char_or_unicode,
}
has_content = True
@property
def app(self):
return self.state.document.settings.env.app
def report_load_failure(mgr, ep, err):
self.app.warn(u'Failed to load %s: %s' % (ep.module_name, err))
def display_plugin(self, ext):
overline_style = self.options.get('overline-style', '')
underline_style = self.options.get('underline-style', '=')
if overline_style:
yield overline_style * len(ext.name)
yield ext.name
if underline_style:
yield underline_style * len(ext.name)
yield "\n"
doc = inspect.getdoc(ext.obj)
if doc:
yield doc
yield "\n"
yield "------"
yield "\n"
for opt in ext.obj.get_options():
yield ":%s: %s" % (opt.name, opt.help)
yield "\n"
def run(self):
mgr = extension.ExtensionManager(
'keystoneauth1.plugin',
on_load_failure_callback=self.report_load_failure,
invoke_on_load=True,
)
result = ViewList()
for name in sorted(mgr.names()):
for line in self.display_plugin(mgr[name]):
for l in line.splitlines():
result.append(l, mgr[name].entry_point.module_name)
# Parse what we have into a new section.
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, result, node)
return node.children
def setup(app):
app.info('loading keystoneauth1 plugins')
app.add_directive('list-auth-plugins', ListAuthPluginsDirective)

View File

@ -1,303 +0,0 @@
======================
Authentication Plugins
======================
Introduction
============
Authentication plugins provide a generic means by which to extend the
authentication mechanisms known to OpenStack clients.
In the vast majority of cases the authentication plugins used will be those
written for use with the OpenStack Identity Service (Keystone), however this is
not the only possible case, and the mechanisms by which authentication plugins
are used and implemented should be generic enough to cover completely
customized authentication solutions.
The subset of authentication plugins intended for use with an OpenStack
Identity server (such as Keystone) are called Identity Plugins.
Available Plugins
=================
Keystoneauth ships with a number of plugins and particularly Identity
Plugins.
V2 Identity Plugins
-------------------
Standard V2 identity plugins are defined in the module:
:py:mod:`keystoneauth1.identity.v2`
They include:
- :py:class:`~keystoneauth1.identity.v2.Password`: Authenticate against
a V2 identity service using a username and password.
- :py:class:`~keystoneauth1.identity.v2.Token`: Authenticate against a
V2 identity service using an existing token.
V2 identity plugins must use an `auth_url` that points to the root of a V2
identity server URL, i.e.: ``http://hostname:5000/v2.0``.
V3 Identity Plugins
-------------------
Standard V3 identity plugins are defined in the module
:py:mod:`keystoneauth1.identity.v3`.
V3 Identity plugins are slightly different from their V2 counterparts as a V3
authentication request can contain multiple authentication methods. To handle
this V3 defines a number of different
:py:class:`~keystoneauth1.identity.v3.AuthMethod` classes:
- :py:class:`~keystoneauth1.identity.v3.PasswordMethod`: Authenticate
against a V3 identity service using a username and password.
- :py:class:`~keystoneauth1.identity.v3.TokenMethod`: Authenticate against
a V3 identity service using an existing token.
- :py:class:`~keystoneauth1.identity.v3.TOTPMethod`: Authenticate against
a V3 identity service using Time-Based One-Time Password (TOTP).
- :py:class:`~keystoneauth1.identity.v3.TokenlessAuth`: Authenticate against
a V3 identity service using tokenless authentication.
- :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`: Authenticate
against a V3 identity service using Kerberos.
The :py:class:`~keystoneauth1.identity.v3.AuthMethod` objects are then
passed to the :py:class:`~keystoneauth1.identity.v3.Auth` plugin::
>>> from keystoneauth1 import session
>>> from keystoneauth1.identity import v3
>>> password = v3.PasswordMethod(username='user',
... password='password',
... user_domain_name='default')
>>> auth = v3.Auth(auth_url='http://my.keystone.com:5000/v3',
... auth_methods=[password],
... project_id='projectid')
>>> sess = session.Session(auth=auth)
As in the majority of cases you will only want to use one
:py:class:`~keystoneauth1.identity.v3.AuthMethod` there are also helper
authentication plugins for the various
:py:class:`~keystoneauth1.identity.v3.AuthMethod` which can be used more
like the V2 plugins:
- :py:class:`~keystoneauth1.identity.v3.Password`: Authenticate using
only a :py:class:`~keystoneauth1.identity.v3.PasswordMethod`.
- :py:class:`~keystoneauth1.identity.v3.Token`: Authenticate using only a
:py:class:`~keystoneauth1.identity.v3.TokenMethod`.
- :py:class:`~keystoneauth1.identity.v3.TOTP`: Authenticate using
only a :py:class:`~keystoneauth1.identity.v3.TOTPMethod`.
- :py:class:`~keystoneauth1.extras.kerberos.Kerberos`: Authenticate using
only a :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`.
::
>>> auth = v3.Password(auth_url='http://my.keystone.com:5000/v3',
... username='username',
... password='password',
... project_id='projectid',
... user_domain_name='default')
>>> sess = session.Session(auth=auth)
This will have exactly the same effect as using the single
:py:class:`~keystoneauth1.identity.v3.PasswordMethod` above.
V3 identity plugins must use an `auth_url` that points to the root of a V3
identity server URL, i.e.: ``http://hostname:5000/v3``.
Federation
==========
The following V3 plugins are provided to support federation:
- :py:class:`~keystoneauth1.extras.kerberos.MappedKerberos`: Federated (mapped)
Kerberos.
- :py:class:`~keystoneauth1.extras._saml2.v3.Password`: SAML2 password
authentication.
- :py:class:`~keystoneauth1.identity.v3.Keystone2Keystone`: Keystone to
Keystone Federation.
- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAccessToken`: Plugin to
reuse an existing OpenID Connect access token.
- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode`: OpenID
Connect Authorization Code grant type.
- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectClientCredentials`: OpenID
Connect Client Credentials grant type.
- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectPassword`: OpenID Connect
Resource Owner Password Credentials grant type.
Version Independent Identity Plugins
------------------------------------
Standard version independent identity plugins are defined in the module
:py:mod:`keystoneauth1.identity.generic`.
For the cases of plugins that exist under both the identity V2 and V3 APIs
there is an abstraction to allow the plugin to determine which of the V2 and V3
APIs are supported by the server and use the most appropriate API.
These plugins are:
- :py:class:`~keystoneauth1.identity.generic.Password`: Authenticate
using a user/password against either v2 or v3 API.
- :py:class:`~keystoneauth1.identity.generic.Token`: Authenticate using
an existing token against either v2 or v3 API.
These plugins work by first querying the identity server to determine available
versions and so the `auth_url` used with the plugins should point to the base
URL of the identity server to use. If the `auth_url` points to either a V2 or
V3 endpoint it will restrict the plugin to only working with that version of
the API.
Simple Plugins
--------------
In addition to the Identity plugins a simple plugin that will always use the
same provided token and endpoint is available. This is useful in situations
where you have an token or in testing when you specifically know the endpoint
you want to communicate with.
It can be found at :py:class:`keystoneauth1.token_endpoint.Token`.
V3 OAuth 1.0a Plugins
---------------------
There also exists a plugin for OAuth 1.0a authentication. We provide a helper
authentication plugin at:
:py:class:`~keystoneauth1.extras.oauth1.V3OAuth1`.
The plugin requires the OAuth consumer's key and secret, as well as the OAuth
access token's key and secret. For example::
>>> from keystoneauth1.extras import oauth1
>>> from keystoneauth1 import session
>>> a = oauth1.V3OAuth1('http://my.keystone.com:5000/v3',
... consumer_key=consumer_id,
... consumer_secret=consumer_secret,
... access_key=access_token_key,
... access_secret=access_token_secret)
>>> s = session.Session(auth=a)
Tokenless Auth
==============
A plugin for tokenless authentication also exists. It provides a means to
authorize client operations within the Identity server by using an X.509
TLS client certificate without having to issue a token. We provide a
tokenless authentication plugin at:
- :class:`~keystoneauth1.identity.v3.TokenlessAuth`
It is mostly used by service clients for token validation and here is
an example of how this plugin would be used in practice::
>>> from keystoneauth1 import session
>>> from keystoneauth1.identity import v3
>>> auth = v3.TokenlessAuth(auth_url='https://keystone:5000/v3',
... domain_name='my_service_domain')
>>> sess = session.Session(
... auth=auth,
... cert=('/opt/service_client.crt',
... '/opt/service_client.key'),
... verify='/opt/ca.crt')
Loading Plugins by Name
=======================
In auth_token middleware and for some service to service communication it is
possible to specify a plugin to load via name. The authentication options that
are available are then specific to the plugin that you specified. Currently the
authentication plugins that are available in `keystoneauth` are:
- password: :py:class:`keystoneauth1.identity.generic.Password`
- token: :py:class:`keystoneauth1.identity.generic.Token`
- v2password: :py:class:`keystoneauth1.identity.v2.Password`
- v2token: :py:class:`keystoneauth1.identity.v2.Token`
- v3password: :py:class:`keystoneauth1.identity.v3.Password`
- v3token: :py:class:`keystoneauth1.identity.v3.Token`
- v3fedkerb: :py:class:`keystoneauth1.extras.kerberos.MappedKerberos`
- v3kerberos: :py:class:`keystoneauth1.extras.kerberos.Kerberos`
- v3oauth1: :py:class:`keystoneauth1.extras.oauth1.v3.OAuth1`
- v3oidcaccesstoken: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAccessToken`
- v3oidcauthcode: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode`
- v3oidcclientcredentials: :py:class:`keystoneauth1.identity.v3:OpenIDConnectClientCredentials`
- v3oidcpassword: :py:class:`keystoneauth1.identity.v3:OpenIDConnectPassword`
- v3samlpassword: :py:class:`keystoneauth1.extras._saml2.v3.Password`
- v3tokenlessauth: :py:class:`keystoneauth1.identity.v3.TokenlessAuth`
- v3totp: :py:class:`keystoneauth1.identity.v3.TOTP`
Creating Authentication Plugins
===============================
Creating an Identity Plugin
---------------------------
If you have implemented a new authentication mechanism into the Identity
service then you will be able to reuse a lot of the infrastructure available
for the existing Identity mechanisms. As the V2 identity API is essentially
frozen, it is expected that new plugins are for the V3 API.
To implement a new V3 plugin that can be combined with others you should
implement the base :py:class:`keystoneauth1.identity.v3.AuthMethod` class
and implement the
:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` function.
If your Plugin cannot be used in conjunction with existing
:py:class:`keystoneauth1.identity.v3.AuthMethod` then you should just
override :py:class:`keystoneauth1.identity.v3.Auth` directly.
The new :py:class:`~keystoneauth1.identity.v3.AuthMethod` should take all
the required parameters via
:py:meth:`~keystoneauth1.identity.v3.AuthMethod.__init__` and return from
:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` a tuple
with the unique identifier of this plugin (e.g. *password*) and a dictionary
containing the payload of values to send to the authentication server. The
session, calling auth object and request headers are also passed to this
function so that the plugin may use or manipulate them.
You should also provide a class that inherits from
:py:class:`keystoneauth1.identity.v3.Auth` with an instance of your new
:py:class:`~keystoneauth1.identity.v3.AuthMethod` as the `auth_methods`
parameter to :py:class:`keystoneauth1.identity.v3.Auth`.
By convention (and like above) these are named `PluginType` and
`PluginTypeMethod` (for example
:py:class:`~keystoneauth1.identity.v3.Password` and
:py:class:`~keystoneauth1.identity.v3.PasswordMethod`).
Creating a Custom Plugin
------------------------
To implement an entirely new plugin you should implement the base class
:py:class:`keystoneauth1.plugin.BaseAuthPlugin` and provide the
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint`,
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` and
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` methods.
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` is called to retrieve
the string token from a plugin. It is intended that a plugin will cache a
received token and so if the token is still valid then it should be re-used
rather than fetching a new one. A session object is provided with which the
plugin can contact it's server. (Note: use `authenticated=False` when making
those requests or it will end up being called recursively). The return value
should be the token as a string.
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint` is called to
determine a base URL for a particular service's requests. The keyword arguments
provided to the function are those that are given by the `endpoint_filter`
variable in :py:meth:`keystoneauth1.session.Session.request`. A session object
is also provided so that the plugin may contact an external source to determine
the endpoint. Again this will be generally be called once per request and so
it is up to the plugin to cache these responses if appropriate. The return
value should be the base URL to communicate with.
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` should also be
implemented to clear the current user credentials so that on the next
:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` call a new token can
be retrieved.
The most simple example of a plugin is the
:py:class:`keystoneauth1.token_endpoint.Token` plugin.

View File

@ -1,237 +0,0 @@
# -*- coding: utf-8 -*-
#
# keystoneauth1 documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 6 14:19:25 2009.
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from __future__ import unicode_literals
import os
import sys
import pbr.version
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..')))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
'..')))
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.append(os.path.abspath('.'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.intersphinx',
'openstackdocstheme',
'ext.list_plugins',
]
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'keystoneauth1'
copyright = 'OpenStack Contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
version_info = pbr.version.VersionInfo('keystoneauth1')
# The short X.Y version.
version = version_info.version_string()
# The full version, including alpha/beta/rc tags.
release = version_info.release_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['keystoneauth1.']
# Grouping the document tree for man pages.
# List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual'
#man_pages = []
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
#html_theme_path = ["."]
#html_theme = '_theme'
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<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 = 'keystoneauthdoc'
# -- Options for LaTeX output -------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual])
# .
latex_documents = [
('index', 'keystoneauth1.tex',
'keystoneauth1 Documentation',
'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss',
'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
ksc = 'https://docs.openstack.org/python-keystoneclient/latest/'
intersphinx_mapping = {
'python': ('http://docs.python.org/', None),
'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None),
'keystoneclient': (ksc, None),
}
# -- Options for openstackdocstheme -------------------------------------------
repository_name = 'openstack/keystoneauth'
bug_project = 'keystoneauth'
bug_tag = 'doc'

View File

@ -1,73 +0,0 @@
======
Extras
======
The extensibility of keystoneauth plugins is purposefully designed to allow a
range of different authentication mechanisms that don't have to reside in the
upstream packages. There are however a number of plugins that upstream supports
that involve additional dependencies that the keystoneauth package cannot
depend upon directly.
To get around this we utilize setuptools `extras dependencies <https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies>`_ for additional
plugins. To use a plugin like the kerberos plugin that has additional
dependencies you must install the additional dependencies like::
pip install keystoneauth1[kerberos]
By convention (not a requirement) extra plugins have a module located in the
keystoneauth1.extras module with the same name as the dependency. eg::
from keystoneauth1.extras import kerberos
There is no keystoneauth specific check that the correct dependencies are
installed for accessing a module. You would expect to see standard python
ImportError when the required dependencies are not found.
Examples
========
All extras plugins follow the pattern:
1. import plugin module
2. instantiate the plugin
3. call get_token method of the plugin passing it a session object
to get a token
Kerberos
--------
Get domain-scoped token using
:py:class:`~keystoneauth1.extras.kerberos.Kerberos`::
from keystoneauth1.extras import kerberos
from keystoneauth1 import session
plugin = kerberos.Kerberos('http://example.com:5000/v3')
sess = session.Session(plugin)
token = plugin.get_token(sess)
Get unscoped federated token::
from keystoneauth1.extras import kerberos
from keystoneauth1 import session
plugin = kerberos.MappedKerberos(
auth_url='http://example.com:5000/v3', protocol='example_protocol',
identity_provider='example_identity_provider')
sess = session.Session()
token = plugin.get_token(sess)
Get project scoped federated token::
from keystoneauth1.extras import kerberos
from keystoneauth1 import session
plugin = kerberos.MappedKerberos(
auth_url='http://example.com:5000/v3', protocol='example_protocol',
identity_provider='example_identity_provider',
project_id='example_project_id')
sess = session.Session()
token = plugin.get_token(sess)
project_id = plugin.get_project_id(sess)

View File

@ -1 +0,0 @@
.. include:: ../../ChangeLog

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.27.20101213.0545 (20101213.0545)
-->
<!-- Title: AuthComp Pages: 1 -->
<svg width="510pt" height="118pt"
viewBox="0.00 0.00 510.00 118.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 114)">
<title>AuthComp</title>
<polygon fill="white" stroke="white" points="-4,5 -4,-114 507,-114 507,5 -4,5"/>
<!-- AuthComp -->
<g id="node2" class="node"><title>AuthComp</title>
<polygon fill="#fdefe3" stroke="#c00000" points="292,-65 194,-65 194,-25 292,-25 292,-65"/>
<text text-anchor="middle" x="243" y="-48.4" font-family="Helvetica,sans-Serif" font-size="14.00">Auth</text>
<text text-anchor="middle" x="243" y="-32.4" font-family="Helvetica,sans-Serif" font-size="14.00">Component</text>
</g>
<!-- Reject -->
<!-- AuthComp&#45;&gt;Reject -->
<g id="edge3" class="edge"><title>AuthComp&#45;&gt;Reject</title>
<path fill="none" stroke="black" d="M193.933,-51.2787C157.514,-55.939 108.38,-62.2263 73.8172,-66.649"/>
<polygon fill="black" stroke="black" points="73.0637,-63.2168 63.5888,-67.9578 73.9522,-70.1602 73.0637,-63.2168"/>
<text text-anchor="middle" x="129" y="-97.4" font-family="Times,serif" font-size="14.00">Reject</text>
<text text-anchor="middle" x="129" y="-82.4" font-family="Times,serif" font-size="14.00">Unauthenticated</text>
<text text-anchor="middle" x="129" y="-67.4" font-family="Times,serif" font-size="14.00">Requests</text>
</g>
<!-- Service -->
<g id="node6" class="node"><title>Service</title>
<polygon fill="#d1ebf1" stroke="#1f477d" points="502,-65 408,-65 408,-25 502,-25 502,-65"/>
<text text-anchor="middle" x="455" y="-48.4" font-family="Helvetica,sans-Serif" font-size="14.00">OpenStack</text>
<text text-anchor="middle" x="455" y="-32.4" font-family="Helvetica,sans-Serif" font-size="14.00">Service</text>
</g>
<!-- AuthComp&#45;&gt;Service -->
<g id="edge5" class="edge"><title>AuthComp&#45;&gt;Service</title>
<path fill="none" stroke="black" d="M292.17,-45C323.626,-45 364.563,-45 397.52,-45"/>
<polygon fill="black" stroke="black" points="397.917,-48.5001 407.917,-45 397.917,-41.5001 397.917,-48.5001"/>
<text text-anchor="middle" x="350" y="-77.4" font-family="Times,serif" font-size="14.00">Forward</text>
<text text-anchor="middle" x="350" y="-62.4" font-family="Times,serif" font-size="14.00">Authenticated</text>
<text text-anchor="middle" x="350" y="-47.4" font-family="Times,serif" font-size="14.00">Requests</text>
</g>
<!-- Start -->
<!-- Start&#45;&gt;AuthComp -->
<g id="edge7" class="edge"><title>Start&#45;&gt;AuthComp</title>
<path fill="none" stroke="black" d="M59.1526,-21.4745C90.4482,-25.4792 142.816,-32.1802 183.673,-37.4084"/>
<polygon fill="black" stroke="black" points="183.43,-40.9057 193.793,-38.7034 184.318,-33.9623 183.43,-40.9057"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.27.20101213.0545 (20101213.0545)
-->
<!-- Title: AuthCompDelegate Pages: 1 -->
<svg width="588pt" height="104pt"
viewBox="0.00 0.00 588.00 104.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 100)">
<title>AuthCompDelegate</title>
<polygon fill="white" stroke="white" points="-4,5 -4,-100 585,-100 585,5 -4,5"/>
<!-- AuthComp -->
<g id="node2" class="node"><title>AuthComp</title>
<polygon fill="#fdefe3" stroke="#c00000" points="338,-65 240,-65 240,-25 338,-25 338,-65"/>
<text text-anchor="middle" x="289" y="-48.4" font-family="Helvetica,sans-Serif" font-size="14.00">Auth</text>
<text text-anchor="middle" x="289" y="-32.4" font-family="Helvetica,sans-Serif" font-size="14.00">Component</text>
</g>
<!-- Reject -->
<!-- AuthComp&#45;&gt;Reject -->
<g id="edge3" class="edge"><title>AuthComp&#45;&gt;Reject</title>
<path fill="none" stroke="black" d="M239.6,-50.1899C191.406,-55.2531 118.917,-62.8686 73.5875,-67.6309"/>
<polygon fill="black" stroke="black" points="73.0928,-64.1635 63.5132,-68.6893 73.8242,-71.1252 73.0928,-64.1635"/>
<text text-anchor="middle" x="152" y="-83.4" font-family="Times,serif" font-size="14.00">Reject Requests</text>
<text text-anchor="middle" x="152" y="-68.4" font-family="Times,serif" font-size="14.00">Indicated by the Service</text>
</g>
<!-- Service -->
<g id="node6" class="node"><title>Service</title>
<polygon fill="#d1ebf1" stroke="#1f477d" points="580,-65 486,-65 486,-25 580,-25 580,-65"/>
<text text-anchor="middle" x="533" y="-48.4" font-family="Helvetica,sans-Serif" font-size="14.00">OpenStack</text>
<text text-anchor="middle" x="533" y="-32.4" font-family="Helvetica,sans-Serif" font-size="14.00">Service</text>
</g>
<!-- AuthComp&#45;&gt;Service -->
<g id="edge5" class="edge"><title>AuthComp&#45;&gt;Service</title>
<path fill="none" stroke="black" d="M338.009,-49.0804C344.065,-49.4598 350.172,-49.7828 356,-50 405.743,-51.8535 418.259,-51.9103 468,-50 470.523,-49.9031 473.101,-49.7851 475.704,-49.6504"/>
<polygon fill="black" stroke="black" points="476.03,-53.1374 485.807,-49.0576 475.62,-46.1494 476.03,-53.1374"/>
<text text-anchor="middle" x="412" y="-68.4" font-family="Times,serif" font-size="14.00">Forward Requests</text>
<text text-anchor="middle" x="412" y="-53.4" font-family="Times,serif" font-size="14.00">with Identiy Status</text>
</g>
<!-- Service&#45;&gt;AuthComp -->
<g id="edge7" class="edge"><title>Service&#45;&gt;AuthComp</title>
<path fill="none" stroke="black" d="M495.062,-24.9037C486.397,-21.2187 477.064,-17.9304 468,-16 419.314,-5.63183 404.743,-5.9037 356,-16 349.891,-17.2653 343.655,-19.116 337.566,-21.2803"/>
<polygon fill="black" stroke="black" points="336.234,-18.0426 328.158,-24.9003 338.748,-24.5757 336.234,-18.0426"/>
<text text-anchor="middle" x="412" y="-33.4" font-family="Times,serif" font-size="14.00">Send Response OR</text>
<text text-anchor="middle" x="412" y="-18.4" font-family="Times,serif" font-size="14.00">Reject Message</text>
</g>
<!-- Start -->
<!-- Start&#45;&gt;AuthComp -->
<g id="edge9" class="edge"><title>Start&#45;&gt;AuthComp</title>
<path fill="none" stroke="black" d="M59.0178,-20.8384C99.2135,-25.0613 175.782,-33.1055 229.492,-38.7482"/>
<polygon fill="black" stroke="black" points="229.265,-42.2435 239.576,-39.8076 229.997,-35.2818 229.265,-42.2435"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,49 +0,0 @@
Common Authentication Library for OpenStack Clients
===================================================
Keystoneauth provides a standard way to do authentication and service requests
within the OpenStack ecosystem. It is designed for use in conjunction with the
existing OpenStack clients and for simplifying the process of writing new
clients.
Contents:
.. toctree::
:maxdepth: 1
using-sessions
authentication-plugins
plugin-options
extras
migrating
api/modules
Release Notes
=============
.. toctree::
:maxdepth: 1
history
Contributing
============
Code is hosted `on GitHub`_. Submit bugs to the Keystone project on
`Launchpad`_. Submit code to the ``openstack/keystoneauth`` project
using `Gerrit`_.
.. _on GitHub: https://github.com/openstack/keystoneauth
.. _Launchpad: https://launchpad.net/keystoneauth
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
Run tests with ``tox``.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,102 +0,0 @@
=============================
Migrating from keystoneclient
=============================
When keystoneauth was extracted from keystoneclient the basic usage of the
session, adapter and auth plugins purposefully did not change. If you are using
them in a supported fashion from keystoneclient then the transition should be
fairly simple.
Authentication Plugins
======================
The authentication plugins themselves changed very little however there were
changes to the way plugins are loaded and some of the supporting classes.
Plugin Loading
--------------
In keystoneclient auth plugin loading is managed by the class itself. This
method proved useful in allowing the plugin to control the way it was loaded
however it linked the authentication logic with the config and CLI loading.
In keystoneauth this has been severed and the auth plugin is handled separately
from the mechanism that loads it.
Authentication plugins still implement the base authentication class
:py:class:`~keystoneauth1.plugin.BaseAuthPlugin`. To make the plugins capable
of being loaded from CLI or CONF file you should implement the base
:py:class:`~keystoneauth1.loading.BaseLoader` class which is loaded when
`--os-auth-type` is used. This class handles the options that are
presented, and then constructs the authentication plugin for use by the
application.
Largely the options that are returned will be the same as what was used in
keystoneclient however in keystoneclient the options used
:py:class:`oslo_config.cfg.Opt` objects. Due to trying to keep minimal
dependencies there is no direct dependency from keystoneauth on oslo.config and
instead options should be specified as :py:class:`~keystoneauth1.loading.Opt`
objects.
To ensure distinction between the plugins, the setuptools entrypoints that
plugins register at has been updated to reflect keystoneauth1 and should now
be: keystoneauth1.plugin
AccessInfo Objects
------------------
AccessInfo objects are a representation of the information stored within a
token. In keystoneclient these objects were dictionaries of the token data with
property accessors. In keystoneauth the dictionary interface has been removed
and just the property accessors are available.
The creation function has also changed. The
:py:meth:`keystoneclient.access.AccessInfo.factory` method has been removed
and replaced with the :py:func:`keystoneauth1.access.create`.
Step-by-step migration example
------------------------------
Add ``keystoneauth1`` to requirements.txt
In the code do the following change::
-from keystoneclient import auth
+from keystoneauth1 import plugin
consequently::
-auth.BaseAuthPlugin
+plugin.BaseAuthPlugin
To import service catalog::
-from keystoneclient import service_catalog
+from keystoneauth1.access import service_catalog
To get url using service catalog *endpoint_type* parameter was changed to
*interface*::
-service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., endpoint_type=interface)
+service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., interface=interface)
Obtaining the session::
-from keystoneclient import session
+from keystoneauth1 import loading as ks_loading
-_SESSION = session.Session.load_from_conf_options(
-auth_plugin = auth.load_from_conf_options(conf, NEUTRON_GROUP)
+_SESSION = ks_loading.load_session_from_conf_options(
+auth_plugin = ks_loading.load_auth_from_conf_options(conf, NEUTRON_GROUP)
Mocking session for test purposes::
-@mock.patch('keystoneclient.session.Session')
+@mock.patch('keystoneauth1.session.Session')
Token fixture imports haven't change much::
-from keystoneclient.fixture import V2Token
+from keystoneauth1.fixture import V2Token

View File

@ -1,92 +0,0 @@
==============
Plugin Options
==============
Using plugins via config file
-----------------------------
When using the plugins via config file you define the plugin name as
``auth_type``. The options of the plugin are then specified while replacing
``-`` with ``_`` to be valid in configuration.
For example to use the password_ plugin in a config file you would specify:
.. code-block:: ini
[section]
auth_url = http://keystone.example.com:5000/
auth_type = password
username = myuser
password = mypassword
project_name = myproject
default_domain_name = mydomain
Using plugins via CLI
---------------------
When using auth plugins via CLI via ``os-client-config`` or ``shade`` you can
specify parameters via environment configuration by using the pattern ``OS_``
followed by the uppercase parameter name replacing ``-`` with ``_``.
For example to use the password_ plugin via environment variable you specify:
.. code-block:: bash
export OS_AUTH_TYPE=password
export OS_AUTH_URL=http://keystone.example.com:5000/
export OS_USERNAME=myuser
export OS_PASSWORD=mypassword
export OS_PROJECT_NAME=myproject
export OS_DEFAULT_DOMAIN_NAME=mydomain
Specifying operations via CLI parameter will override the environment
parameter. These are specified with the pattern ``--os-`` and the parameter
name. Using the password_ example again:
.. code-block:: bash
openstack --os-auth-type password \
--os-auth-url http://keystone.example.com:5000/ \
--os-username myuser \
--os-password mypassword \
--os-project-name myproject \
--os-default-domain-name mydomain \
operation
Additional loaders
------------------
The configuration and CLI loaders are quite commonly used however similar
concepts are found in other situations such as ``os-client-config`` in which
you specify authentication and other cloud parameters in a ``clouds.yaml``
file.
Loaders such as these use the same plugin options listed below, but via their
own mechanism. In ``os-client-config`` the password_ plugin looks like:
.. code-block:: yaml
clouds:
mycloud:
auth_type: password
auth:
auth_url: http://keystone.example.com:5000/
auth_type: password
username: myuser
password: mypassword
project_name: myproject
default_domain_name: mydomain
However different services may implement loaders in their own way and you
should consult their relevant documentation. The same auth options will be
available.
Available Plugins
-----------------
This is a listing of all included plugins and the options that they accept.
Plugins are listed alphabetically and not in any order of priority.
.. list-auth-plugins::

View File

@ -1,440 +0,0 @@
==============
Using Sessions
==============
Introduction
============
The :py:class:`keystoneauth1.session.Session` class was introduced into
keystoneauth1 as an attempt to bring a unified interface to the various
OpenStack clients that share common authentication and request parameters
between a variety of services.
The model for using a Session and auth plugin as well as the general terms used
have been heavily inspired by the `requests <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 service and version discovery
Clients are not expected to have any knowledge of an identity token or any
other form of identification credential. Service, endpoint, major version
discovery and microversion support discovery are handled by the Session and
plugins. Discovery information is automatically cached in memory, so the user
need not worry about excessive use of discovery metadata.
Sessions for Users
==================
The Session object is the contact point to your OpenStack cloud services. It
stores the authentication credentials and connection information required to
communicate with OpenStack such that it can be reused to communicate with many
services. When creating services this Session object is passed to the client
so that it may use this information.
A Session will authenticate on demand. When a request that requires
authentication passes through the Session the authentication plugin will be
asked for a valid token. If a valid token is available it will be used
otherwise the authentication plugin may attempt to contact the authentication
service and fetch a new one.
An example using keystoneclient to wrap a Session::
>>> from keystoneauth1.identity import v3
>>> from keystoneauth1 import session
>>> from keystoneclient.v3 import client
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
... username='myuser',
... password='mypassword',
... project_name='proj',
... user_domain_id='default',
... project_domain_id='default')
>>> sess = session.Session(auth=auth,
... verify='/path/to/ca.cert')
>>> ks = client.Client(session=sess)
>>> users = ks.users.list()
As other OpenStack client libraries adopt this means of operating they will be
created in a similar fashion by passing the Session object to the client's
constructor.
Sharing Authentication Plugins
------------------------------
A Session can only contain one authentication plugin. However, there is
nothing that specifically binds the authentication plugin to that Session - a
new Session can be created that reuses the existing authentication plugin::
>>> new_sess = session.Session(auth=sess.auth,
verify='/path/to/different-cas.cert')
In this case we cannot know which Session object will be used when the plugin
performs the authentication call so the command must be able to succeed with
either.
Authentication plugins can also be provided on a per-request basis. This will
be beneficial in a situation where a single Session is juggling multiple
authentication credentials::
>>> sess.get('https://my.keystone.com:5000/v3',
auth=my_auth_plugin)
If an auth plugin is provided via parameter then it will override any auth
plugin on the Session.
Sessions for Client Developers
==============================
Sessions are intended to take away much of the hassle of dealing with
authentication data and token formats. Clients should be able to specify filter
parameters for selecting the endpoint and have the parsing of the catalog
managed for them.
Major Version Discovery and Microversion Support
------------------------------------------------
In OpenStack the root URLs of available services are distributed to the user
in an object called the Service Catalog, which is part of the token they
receive. Clients are expected to use the URLs from the Service Catalog rather
than have them provided. The root URL of a given service is referred to as the
`endpoint` of the service. The URL of a specific version of a service is
referred to as a `versioned endpoint`. REST requests for a service are made
against a given `versioned endpoint`.
The topic of Major API versions and microversions can be confusing. As
`keystoneauth` provides facilities for discovery of versioned endpoints
associated with a Major API Version and for fetching information about
the microversions that versioned endpoint supports, it is important to be aware
of the distinction between the two.
Conceptually the most important thing to understand is that a Major API Version
describes the URL of a discrete versioned endpoint, while a given versioned
endpoint might have properties that express that it supports a range of
microversions.
When a user wants to make a REST request against a service, the user expresses
the Major API version and the type of service so that the appropriate versioned
endpoint can be found and used. For example, a user might request
version 2 of the compute service from cloud.example.com and end up with a
versioned endpoint of ``https://compute.example.com/v2``.
Each service provides a discovery document at the root of each versioned
endpoint that contains information about that versioned endpoint. Each service
also provides a document at the root of the unversioned endpoint that contains
a list of the discovery documents for all of the available versioned endpoints.
By examining these documents, it is possible to find the versioned endpoint
that corresponds with the user's desired Major API version.
Each of those documents may also indicate that the given versioned endpoint
supports microversions by listing a minimum and maximum microversion that it
understands. As a result of having found the versioned endpoint for the
requested Major API version, the user will also know which microversions,
if any, may be used in requests to that versioned endpoint.
When a client makes REST requests to the Major API version's endpoint, the
client can, optionally, on a request-by-request basis, include a header
specifying that the individual request use the behavior defined by the given
microversion. If a client does not request a microversion, the service will
behave as if the minimum supported microversion was specified.
.. note: The changes that each microversion reflects are documented elsewhere
and are not information provided by the discovery process.
The overall transaction then has three parts:
* What is the endpoint for a given Major API version of a given service?
* What are the minimum and maximum microversions supported at that endpoint?
* Which one of that range of microversions, if any, does the user want to use
for a given request?
`keystoneauth` provides facilities for discovering the endpoint for a given
Major API of a given service, as well as reporting the available microversion
ranges that endpoint supports, if any.
More information is available in the `API-WG Specs`_ on the topics of
`Microversions`_ and `Consuming the Catalog`_.
Authentication
--------------
When making a request with a Session object you can simply pass the keyword
parameter ``authenticated`` to indicate whether the argument should contain a
token, by default a token is included if an authentication plugin is available::
>>> # In keystone this route is unprotected by default
>>> resp = sess.get('https://my.keystone.com:5000/v3',
authenticated=False)
Service Discovery
-----------------
In general a client does not need to know the full URL for the server that they
are communicating with, simply that it should send a request to a path
belonging to the correct service.
This is controlled by the ``endpoint_filter`` parameter to a request which
contains all the information an authentication plugin requires to determine the
correct URL to which to send a request. When using this mode only the path for
the request needs to be specified::
>>> resp = session.get('/users',
endpoint_filter={'service_type': 'identity',
'interface': 'admin',
'region_name': 'myregion',
'min_version': '2.0',
'max_version': '3.4',
'discover_versions': False})
.. note:: The min_version and max_version arguments in this example indicate
acceptable range for finding the endpoint for the given Major API
versions. They are in the endpoint_filter, they are not requesting
the call to ``/users`` be made at a specific microversion.
`endpoint_filter` accepts a number of arguments with which it can determine an
endpoint url:
service_type
the type of service. For example ``identity``, ``compute``, ``volume`` or
many other predefined identifiers.
interface
the network exposure the interface has. Can also be a list, in which case the
first matching interface will be used. Valid values are:
- ``public``: An endpoint that is available to the wider internet or network.
- ``internal``: An endpoint that is only accessible within the private
network.
- ``admin``: An endpoint to be used for administrative tasks.
region_name
the name of the region where the endpoint resides.
version
the minimum version, restricted to a given Major API. For instance, a
`version` of ``2.2`` will match ``2.2`` and ``2.3`` but not ``2.1`` or
``3.0``. Mutually exclusive with `min_version` and `max_version`.
min_version
the minimum version of a given API, intended to be used as the lower bound of
a range with `max_version`. See `max_version` for examples. Mutually
exclusive with `version`.
max_version
the maximum version of a given API, intended to be used as the upper bound of
a range with `min_version`. For example::
'min_version': '2.2',
'max_version': '3.3'
will match ``2.2``, ``2.10``, ``3.0``, and ``3.3``, but not ``1.42``,
``2.1``, or ``3.20``. Mutually exclusive with `version`.
.. note:: version, min_version and max_version are all used to help determine
the endpoint for a given Major API version of a service.
discover_versions
whether or not version discovery should be run, even if not strictly
necessary. It is often possible to fulfill an endpoint request purely
from the catalog, meaning the version discovery API is a potentially
wasted additional call. However, it's possible that running discovery
instead of inference is desired. Defaults to ``True``.
All version arguments (`version`, `min_version` and `max_version`) can
be given as:
* string: ``'2.0'``
* int: ``2``
* float: ``2.0``
* tuple of ints: ``(2, 0)``
`version` and `max_version` can also be given the string ``latest``, which
indicates that the highest available version should be used.
The endpoint filter is a simple key-value filter and can be provided with any
number of arguments. It is then up to the auth plugin to correctly use the
parameters it understands.
If you want to further limit your service discovery by allowing experimental
APIs or disallowing deprecated APIs, you can use the ``allow`` parameter::
>>> resp = session.get('/<project-id>/volumes',
endpoint_filter={'service_type': 'volume',
'interface': 'public',
'version': 1},
allow={'allow_deprecated': False})
The discoverable types of endpoints that `allow` can recognize are:
- `allow_deprecated`: Allow deprecated version endpoints.
- `allow_experimental`: Allow experimental version endpoints.
- `allow_unknown`: Allow endpoints with an unrecognised status.
The Session object creates a valid request by determining the URL matching the
filters and appending it to the provided path. If multiple URL matches are
found then any one may be chosen.
While authentication plugins will endeavour to maintain a consistent set of
arguments for an ``endpoint_filter`` the concept of an authentication plugin is
purposefully generic. A specific mechanism may not know how to interpret
certain arguments in which case it may ignore them. For example the
:class:`keystoneauth1.token_endpoint.Token` plugin (which is used when you want
to always use a specific endpoint and token combination) will always return the
same endpoint regardless of the parameters to ``endpoint_filter`` or a custom
OpenStack authentication mechanism may not have the concept of multiple
``interface`` options and choose to ignore that parameter.
There is some expectation on the user that they understand the limitations of
the authentication system they are using.
Using Adapters
--------------
If the developer would prefer not to provide `endpoint_filter` with every API
call, a :class:`keystoneauth1.adapter.Adapter` can be created. The `Adapter`
constructor takes the same arguments as `endpoint_filter`, as well as a
`Session`. An `Adapter` behaves much like a `Session`, with the same REST
methods, but is "mounted" on the endpoint that would be found by
`endpoint_filter`.
.. code-block:: python
adapter = keystoneauth1.adapter.Adapter(
session=session,
service_type='volume',
interface='public',
version=1)
response = adapter.get('/volumes')
As with ``endpoint_filter`` on a Session, the ``version``, ``min_version``
and ``max_version`` parameters exist to help determine the appropriate
endpoint for a Major API of a service.
Endpoint Metadata
-----------------
Both :class:`keystoneauth1.adapter.Adapter` and
:class:`keystoneauth1.session.Session` have a method for getting metadata about
the endpoint found for a given service: ``get_endpoint_data``.
On the :class:`keystoneauth1.session.Session` it takes the same arguments as
`endpoint_filter`.
On the :class:`keystoneauth1.adapter.Adapter` it does not take arguments, as
it returns the information for the Endpoint the Adapter is mounted on.
``get_endpoint_data`` returns an :class:`keystoneauth1.discovery.EndpointData`
object. This object can be used to find information about the Endpoint,
including which major `api_version` was found, or which `interface` in case
of ranges, lists of input values or ``latest`` version.
It can also be used to determine the `min_microversion` and `max_microversion`
supported by the API. If an API does not support microversions, the values for
both will be ``None``. It will also contain values for `next_min_version` and
`not_before` if they exist for the endpoint, or ``None`` if they do not. The
:class:`keystoneauth1.discovery.EndpointData` object will always contain
microversion related attributes regardless of whether the REST document does
or not.
``get_endpoint_data`` makes use of the same cache as the rest of the discovery
process, so calling it should incur no undue expense. By default it will make
at least one version discovery call so that it can fetch microversion metadata.
If the user knows a service does not support microversions and is merely
curious as to which major version was discovered, `discover_versions` can be
set to `False` to prevent fetching microversion metadata.
Requesting a Microversion
-------------------------
A user who wants to specify a microversion for a given request can pass it to
the ``microversion`` parameter of the `request` method on the
:class:`keystoneauth1.session.Session` object, or the
:class:`keystoneauth1.adapter.Adapter` object. This will cause `keystoneauth`
to pass the appropriate header to the service informing the service of the
microversion the user wants.
.. code-block:: python
resp = session.get('/volumes',
microversion='3.15',
endpoint_filter={'service_type': 'volume',
'interface': 'public',
'min_version': '3',
'max_version': 'latest'})
If the user is using a :class:`keystoneauth1.adapter.Adapter`, the
`service_type`, which is a part of the data sent in the microversion header,
will be taken from the Adapter's `service_type`.
.. code-block:: python
adapter = keystoneauth1.adapter.Adapter(
session=session,
service_type='compute',
interface='public',
min_version='2.1')
response = adapter.get('/servers', microversion='2.38')
The user can also provide a ``default_microversion`` parameter to the Adapter
constructor which will be used on all requests where an explicit microversion
is not requested.
.. code-block:: python
adapter = keystoneauth1.adapter.Adapter(
session=session,
service_type='compute',
interface='public',
min_version='2.1',
default_microversion='2.38')
response = adapter.get('/servers')
If the user is using a :class:`keystoneauth1.session.Session`, the
`service_type` will be taken from the `service_type` in `endpoint_filter`.
If the `service_type` is the incorrect value to use for the microversion header
for the service in question, the parameter `microversion_service_type` can be
given. For instance, although keystoneauth already knows about Cinder, the
`service_type` for Cinder is ``block-storage`` but the microversion header
expects ``volume``.
.. code-block:: python
# Interactions with cinder do not need to explicitly override the
# microversion_service_type - it is only being used as an example for the
# use of the parameter.
resp = session.get('/volumes',
microversion='3.15',
microversion_service_type='volume',
endpoint_filter={'service_type': 'block-storage',
'interface': 'public',
'min_version': '3',
'max_version': 'latest'})
.. _API-WG Specs: http://specs.openstack.org/openstack/api-wg/
.. _Consuming the Catalog: http://specs.openstack.org/openstack/api-wg/guidelines/consuming-catalog.html
.. _Microversions: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html#version-discovery

View File

@ -1,16 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo('keystoneauth1').version_string()

View File

@ -1,83 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import logging
import iso8601
import six
def get_logger(name):
name = name.replace(__name__.split('.')[0], 'keystoneauth')
return logging.getLogger(name)
logger = get_logger(__name__)
def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset()
if offset is None:
return timestamp
return timestamp.replace(tzinfo=None) - offset
def parse_isotime(timestr):
"""Parse time from ISO 8601 format."""
try:
return iso8601.parse_date(timestr)
except iso8601.ParseError as e:
raise ValueError(six.text_type(e))
except TypeError as e:
raise ValueError(six.text_type(e))
def from_utcnow(**timedelta_kwargs):
r"""Calculate the time in the future from utcnow.
:param \*\*timedelta_kwargs:
Passed directly to :class:`datetime.timedelta` to add to the current
time in UTC.
:returns:
The time in the future based on ``timedelta_kwargs``.
:rtype:
datetime.datetime
"""
now = datetime.datetime.utcnow()
delta = datetime.timedelta(**timedelta_kwargs)
return now + delta
def before_utcnow(**timedelta_kwargs):
r"""Calculate the time in the past from utcnow.
:param \*\*timedelta_kwargs:
Passed directly to :class:`datetime.timedelta` to subtract from the
current time in UTC.
:returns:
The time in the past based on ``timedelta_kwargs``.
:rtype:
datetime.datetime
"""
now = datetime.datetime.utcnow()
delta = datetime.timedelta(**timedelta_kwargs)
return now - delta
# Detect if running on the Windows Subsystem for Linux
try:
with open('/proc/version', 'r') as f:
is_windows_linux_subsystem = 'microsoft' in f.read().lower()
except IOError:
is_windows_linux_subsystem = False

View File

@ -1,19 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.access.access import * # noqa
__all__ = ('AccessInfo',
'AccessInfoV2',
'AccessInfoV3',
'create')

View File

@ -1,755 +0,0 @@
# Copyright 2012 Nebula, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
from positional import positional
from keystoneauth1 import _utils as utils
from keystoneauth1.access import service_catalog
from keystoneauth1.access import service_providers
# gap, in seconds, to determine whether the given token is about to expire
STALE_TOKEN_DURATION = 30
__all__ = ('AccessInfo',
'AccessInfoV2',
'AccessInfoV3',
'create')
@positional()
def create(resp=None, body=None, auth_token=None):
if resp and not body:
body = resp.json()
if 'token' in body:
if resp and not auth_token:
auth_token = resp.headers.get('X-Subject-Token')
return AccessInfoV3(body, auth_token)
elif 'access' in body:
return AccessInfoV2(body, auth_token)
raise ValueError('Unrecognized auth response')
def _missingproperty(f):
@functools.wraps(f)
def inner(self):
try:
return f(self)
except KeyError:
return None
return property(inner)
class AccessInfo(object):
"""Encapsulates a raw authentication token from keystone.
Provides helper methods for extracting useful values from that token.
"""
_service_catalog_class = None
def __init__(self, body, auth_token=None):
self._data = body
self._auth_token = auth_token
self._service_catalog = None
self._service_providers = None
@property
def service_catalog(self):
if not self._service_catalog:
self._service_catalog = self._service_catalog_class.from_token(
self._data)
return self._service_catalog
def will_expire_soon(self, stale_duration=STALE_TOKEN_DURATION):
"""Determine if expiration is about to occur.
:returns: true if expiration is within the given duration
:rtype: boolean
"""
norm_expires = utils.normalize_time(self.expires)
# (gyee) should we move auth_token.will_expire_soon() to timeutils
# instead of duplicating code here?
soon = utils.from_utcnow(seconds=stale_duration)
return norm_expires < soon
def has_service_catalog(self):
"""Return true if the auth token has a service catalog.
:returns: boolean
"""
raise NotImplementedError()
@property
def auth_token(self):
"""Return the token_id associated with the auth request.
To be used in headers for authenticating OpenStack API requests.
:returns: str
"""
return self._auth_token
@property
def expires(self):
"""Return the token expiration (as datetime object).
:returns: datetime
"""
raise NotImplementedError()
@property
def issued(self):
"""Return the token issue time (as datetime object).
:returns: datetime
"""
raise NotImplementedError()
@property
def username(self):
"""Return the username associated with the auth request.
Follows the pattern defined in the V2 API of first looking for 'name',
returning that if available, and falling back to 'username' if name
is unavailable.
:returns: str
"""
raise NotImplementedError()
@property
def user_id(self):
"""Return the user id associated with the auth request.
:returns: str
"""
raise NotImplementedError()
@property
def user_domain_id(self):
"""Return the user's domain id associated with the auth request.
:returns: str
"""
raise NotImplementedError()
@property
def user_domain_name(self):
"""Return the user's domain name associated with the auth request.
:returns: str
"""
raise NotImplementedError()
@property
def role_ids(self):
"""Return a list of user's role ids associated with the auth request.
:returns: a list of strings of role ids
"""
raise NotImplementedError()
@property
def role_names(self):
"""Return a list of user's role names associated with the auth request.
:returns: a list of strings of role names
"""
raise NotImplementedError()
@property
def domain_name(self):
"""Return the domain name associated with the auth request.
:returns: str or None (if no domain associated with the token)
"""
raise NotImplementedError()
@property
def domain_id(self):
"""Return the domain id associated with the auth request.
:returns: str or None (if no domain associated with the token)
"""
raise NotImplementedError()
@property
def project_name(self):
"""Return the project name associated with the auth request.
:returns: str or None (if no project associated with the token)
"""
raise NotImplementedError()
@property
def tenant_name(self):
"""Synonym for project_name."""
return self.project_name
@property
def scoped(self):
"""Return true if the auth token was scoped.
Returns true if scoped to a tenant(project) or domain,
and contains a populated service catalog.
This is deprecated, use project_scoped instead.
:returns: bool
"""
return self.project_scoped or self.domain_scoped
@property
def project_scoped(self):
"""Return true if the auth token was scoped to a tenant (project).
:returns: bool
"""
return bool(self.project_id)
@property
def domain_scoped(self):
"""Return true if the auth token was scoped to a domain.
:returns: bool
"""
raise NotImplementedError()
@property
def trust_id(self):
"""Return the trust id associated with the auth request.
:returns: str or None (if no trust associated with the token)
"""
raise NotImplementedError()
@property
def trust_scoped(self):
"""Return true if the auth token was scoped from a delegated trust.
The trust delegation is via the OS-TRUST v3 extension.
:returns: bool
"""
raise NotImplementedError()
@property
def trustee_user_id(self):
"""Return the trustee user id associated with a trust.
:returns: str or None (if no trust associated with the token)
"""
raise NotImplementedError()
@property
def trustor_user_id(self):
"""Return the trustor user id associated with a trust.
:returns: str or None (if no trust associated with the token)
"""
raise NotImplementedError()
@property
def project_id(self):
"""Return the project ID associated with the auth request.
This returns None if the auth token wasn't scoped to a project.
:returns: str or None (if no project associated with the token)
"""
raise NotImplementedError()
@property
def tenant_id(self):
"""Synonym for project_id."""
return self.project_id
@property
def project_domain_id(self):
"""Return the project's domain id associated with the auth request.
:returns: str
"""
raise NotImplementedError()
@property
def project_domain_name(self):
"""Return the project's domain name associated with the auth request.
:returns: str
"""
raise NotImplementedError()
@property
def oauth_access_token_id(self):
"""Return the access token ID if OAuth authentication used.
:returns: str or None.
"""
raise NotImplementedError()
@property
def oauth_consumer_id(self):
"""Return the consumer ID if OAuth authentication used.
:returns: str or None.
"""
raise NotImplementedError()
@property
def is_federated(self):
"""Return true if federation was used to get the token.
:returns: boolean
"""
raise NotImplementedError()
@property
def is_admin_project(self):
"""Return true if the current project scope is the admin project.
For backwards compatibility purposes if there is nothing specified in
the token we always assume we are in the admin project, so this will
default to True.
:returns boolean
"""
raise NotImplementedError()
@property
def audit_id(self):
"""Return the audit ID if present.
:returns: str or None.
"""
raise NotImplementedError()
@property
def audit_chain_id(self):
"""Return the audit chain ID if present.
In the event that a token was rescoped then this ID will be the
:py:attr:`audit_id` of the initial token. Returns None if no value
present.
:returns: str or None.
"""
raise NotImplementedError()
@property
def initial_audit_id(self):
"""The audit ID of the initially requested token.
This is the :py:attr:`audit_chain_id` if present or the
:py:attr:`audit_id`.
"""
return self.audit_chain_id or self.audit_id
@property
def service_providers(self):
"""Return an object representing the list of trusted service providers.
Used for Keystone2Keystone federating-out.
:returns: :py:class:`keystoneauth1.service_providers.ServiceProviders`
or None
"""
raise NotImplementedError()
@property
def bind(self):
"""Information about external mechanisms the token is bound to.
If a token is bound to an external authentication mechanism it can only
be used in conjunction with that mechanism. For example if bound to a
kerberos principal it may only be accepted if there is also kerberos
authentication performed on the request.
:returns: A dictionary or None. The key will be the bind type the value
is a dictionary that is specific to the format of the bind
type. Returns None if there is no bind information in the
token.
"""
raise NotImplementedError()
@property
def project_is_domain(self):
"""Return if a project act as a domain.
:returns: bool
"""
raise NotImplementedError()
class AccessInfoV2(AccessInfo):
"""An object for encapsulating raw v2 auth token from identity service."""
version = 'v2.0'
_service_catalog_class = service_catalog.ServiceCatalogV2
def has_service_catalog(self):
return 'serviceCatalog' in self._data.get('access', {})
@_missingproperty
def auth_token(self):
set_token = super(AccessInfoV2, self).auth_token
return set_token or self._data['access']['token']['id']
@property
def _token(self):
return self._data['access']['token']
@_missingproperty
def expires(self):
return utils.parse_isotime(self._token.get('expires'))
@_missingproperty
def issued(self):
return utils.parse_isotime(self._token['issued_at'])
@property
def _user(self):
return self._data['access']['user']
@_missingproperty
def username(self):
return self._user.get('name') or self._user.get('username')
@_missingproperty
def user_id(self):
return self._user['id']
@property
def user_domain_id(self):
return None
@property
def user_domain_name(self):
return None
@_missingproperty
def role_ids(self):
metadata = self._data.get('access', {}).get('metadata', {})
return metadata.get('roles', [])
@_missingproperty
def role_names(self):
return [r['name'] for r in self._user.get('roles', [])]
@property
def domain_name(self):
return None
@property
def domain_id(self):
return None
@property
def project_name(self):
try:
tenant_dict = self._token['tenant']
except KeyError:
pass
else:
return tenant_dict.get('name')
# pre grizzly
try:
return self._user['tenantName']
except KeyError:
pass
# pre diablo, keystone only provided a tenantId
try:
return self._token['tenantId']
except KeyError:
pass
@property
def domain_scoped(self):
return False
@property
def _trust(self):
return self._data['access']['trust']
@_missingproperty
def trust_id(self):
return self._trust['id']
@_missingproperty
def trust_scoped(self):
return bool(self._trust)
@_missingproperty
def trustee_user_id(self):
return self._trust['trustee_user_id']
@property
def trustor_user_id(self):
# this information is not available in the v2 token bug: #1331882
return None
@property
def project_id(self):
try:
tenant_dict = self._token['tenant']
except KeyError:
pass
else:
return tenant_dict.get('id')
# pre grizzly
try:
return self._user['tenantId']
except KeyError:
pass
# pre diablo
try:
return self._token['tenantId']
except KeyError:
pass
@property
def project_is_domain(self):
return False
@property
def project_domain_id(self):
return None
@property
def project_domain_name(self):
return None
@property
def oauth_access_token_id(self):
return None
@property
def oauth_consumer_id(self):
return None
@property
def is_federated(self):
return False
@property
def is_admin_project(self):
return True
@property
def audit_id(self):
try:
return self._token.get('audit_ids', [])[0]
except IndexError:
return None
@property
def audit_chain_id(self):
try:
return self._token.get('audit_ids', [])[1]
except IndexError:
return None
@property
def service_providers(self):
return None
@_missingproperty
def bind(self):
return self._token['bind']
class AccessInfoV3(AccessInfo):
"""An object encapsulating raw v3 auth token from identity service."""
version = 'v3'
_service_catalog_class = service_catalog.ServiceCatalogV3
def has_service_catalog(self):
return 'catalog' in self._data['token']
@property
def _user(self):
return self._data['token']['user']
@property
def is_federated(self):
return 'OS-FEDERATION' in self._user
@property
def is_admin_project(self):
return self._data.get('token', {}).get('is_admin_project', True)
@_missingproperty
def expires(self):
return utils.parse_isotime(self._data['token']['expires_at'])
@_missingproperty
def issued(self):
return utils.parse_isotime(self._data['token']['issued_at'])
@_missingproperty
def user_id(self):
return self._user['id']
@property
def user_domain_id(self):
try:
return self._user['domain']['id']
except KeyError:
if self.is_federated:
return None
raise
@property
def user_domain_name(self):
try:
return self._user['domain']['name']
except KeyError:
if self.is_federated:
return None
raise
@_missingproperty
def role_ids(self):
return [r['id'] for r in self._data['token'].get('roles', [])]
@_missingproperty
def role_names(self):
return [r['name'] for r in self._data['token'].get('roles', [])]
@_missingproperty
def username(self):
return self._user['name']
@property
def _domain(self):
return self._data['token']['domain']
@_missingproperty
def domain_name(self):
return self._domain['name']
@_missingproperty
def domain_id(self):
return self._domain['id']
@property
def _project(self):
return self._data['token']['project']
@_missingproperty
def project_id(self):
return self._project['id']
@_missingproperty
def project_is_domain(self):
return self._data['token']['is_domain']
@_missingproperty
def project_domain_id(self):
return self._project['domain']['id']
@_missingproperty
def project_domain_name(self):
return self._project['domain']['name']
@_missingproperty
def project_name(self):
return self._project['name']
@property
def domain_scoped(self):
try:
return bool(self._domain)
except KeyError:
return False
@property
def _trust(self):
return self._data['token']['OS-TRUST:trust']
@_missingproperty
def trust_id(self):
return self._trust['id']
@property
def trust_scoped(self):
try:
return bool(self._trust)
except KeyError:
return False
@_missingproperty
def trustee_user_id(self):
return self._trust['trustee_user']['id']
@_missingproperty
def trustor_user_id(self):
return self._trust['trustor_user']['id']
@property
def _oauth(self):
return self._data['token']['OS-OAUTH1']
@_missingproperty
def oauth_access_token_id(self):
return self._oauth['access_token_id']
@_missingproperty
def oauth_consumer_id(self):
return self._oauth['consumer_id']
@_missingproperty
def audit_id(self):
try:
return self._data['token']['audit_ids'][0]
except IndexError:
return None
@_missingproperty
def audit_chain_id(self):
try:
return self._data['token']['audit_ids'][1]
except IndexError:
return None
@property
def service_providers(self):
if not self._service_providers:
self._service_providers = (
service_providers.ServiceProviders.from_token(self._data))
return self._service_providers
@_missingproperty
def bind(self):
return self._data['token']['bind']

View File

@ -1,512 +0,0 @@
# Copyright 2011 OpenStack Foundation
# Copyright 2011, Piston Cloud Computing, Inc.
# Copyright 2011 Nebula, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import copy
from positional import positional
import six
from keystoneauth1 import discover
from keystoneauth1 import exceptions
@six.add_metaclass(abc.ABCMeta)
class ServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, catalog):
self._catalog = catalog
def _get_endpoint_region(self, endpoint):
return endpoint.get('region_id') or endpoint.get('region')
@property
def catalog(self):
"""Return the raw service catalog content, mostly useful for debugging.
Applications should avoid this and use accessor methods instead.
However, there are times when inspecting the raw catalog can be useful
for analysis and other reasons.
"""
return self._catalog
@abc.abstractmethod
def is_interface_match(self, endpoint, interface):
"""Helper function to normalize endpoint matching across v2 and v3.
:returns: True if the provided endpoint matches the required
interface otherwise False.
"""
@staticmethod
def normalize_interface(self, interface):
"""Handle differences in the way v2 and v3 catalogs specify endpoint.
Both v2 and v3 must be able to handle the endpoint style of the other.
For example v2 must be able to handle a 'public' interface and
v3 must be able to handle a 'publicURL' interface.
:returns: the endpoint string in the format appropriate for this
service catalog.
"""
return interface
def _normalize_endpoints(self, endpoints):
"""Translate endpoint description dicts into v3 form.
Takes a raw endpoint description from the catalog and changes
it to be in v3 format. It also saves a copy of the data in
raw_endpoint so that it can be returned by methods that expect the
actual original data.
:param list endpoints: List of endpoint description dicts
:returns: List of endpoint description dicts in v3 format
"""
new_endpoints = []
for endpoint in endpoints:
raw_endpoint = endpoint.copy()
new_endpoint = endpoint.copy()
new_endpoint['raw_endpoint'] = raw_endpoint
new_endpoints.append(new_endpoint)
return new_endpoints
def _denormalize_endpoints(self, endpoints):
"""Return original endpoint description dicts.
Takes a list of EndpointData objects and returns the original
dict that was returned from the catalog.
:param list endpoints: List of `keystoneauth1.discover.EndpointData`
:returns: List of endpoint description dicts in original catalog format
"""
return [endpoint.raw_endpoint for endpoint in endpoints]
def normalize_catalog(self):
"""Return the catalog normalized into v3 format."""
catalog = []
for service in copy.deepcopy(self._catalog):
if 'type' not in service:
continue
# NOTE(jamielennox): service_name is different. It is not available
# in API < v3.3. If it is in the catalog then we enforce it, if it
# is not then we don't because the name could be correct we just
# don't have that information to check against. Set to None so
# that checks will naturally work.
service.setdefault('name', None)
# NOTE(jamielennox): there is no such thing as a service_id in v2
# similarly to service_name.
service.setdefault('id', None)
service['endpoints'] = self._normalize_endpoints(
service.get('endpoints', []))
for endpoint in service['endpoints']:
endpoint['region_name'] = self._get_endpoint_region(endpoint)
endpoint.setdefault('id', None)
catalog.append(service)
return catalog
def _get_interface_list(self, interface):
if not interface:
return []
if not isinstance(interface, list):
interface = [interface]
return [self.normalize_interface(i) for i in interface]
@positional()
def get_endpoints_data(self, service_type=None, interface=None,
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch and filter endpoint data for the specified service(s).
Returns endpoints for the specified service (or all) containing
the specified type (or all) and region (or all) and service name.
If there is no name in the service catalog the service_name check will
be skipped. This allows compatibility with services that existed
before the name was available in the catalog.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param string service_type: Service type of the endpoint.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference.
:param string region_name: Region of the endpoint.
:param string service_name: The assigned name of the service.
:param string service_id: The identifier of a service.
:param string endpoint_id: The identifier of an endpoint.
:returns: a list of matching EndpointData objects
:rtype: list(`keystoneauth1.discover.EndpointData`)
:returns: a dict, keyed by service_type, of lists of EndpointData
"""
interfaces = self._get_interface_list(interface)
matching_endpoints = {}
for service in self.normalize_catalog():
if service_type and service_type != service['type']:
continue
if (service_name and service['name'] and
service_name != service['name']):
continue
if (service_id and service['id'] and
service_id != service['id']):
continue
matching_endpoints.setdefault(service['type'], [])
for endpoint in service.get('endpoints', []):
if interfaces and endpoint['interface'] not in interfaces:
continue
if region_name and region_name != endpoint['region_name']:
continue
if endpoint_id and endpoint_id != endpoint['id']:
continue
if not endpoint['url']:
continue
matching_endpoints[service['type']].append(
discover.EndpointData(
catalog_url=endpoint['url'],
service_type=service['type'],
service_name=service['name'],
service_id=service['id'],
interface=endpoint['interface'],
region_name=endpoint['region_name'],
endpoint_id=endpoint['id'],
raw_endpoint=endpoint['raw_endpoint']))
if not interfaces:
return matching_endpoints
ret = {}
for service_type, endpoints in matching_endpoints.items():
if not endpoints:
ret[service_type] = []
continue
matches_by_interface = {}
for endpoint in endpoints:
matches_by_interface.setdefault(endpoint.interface, [])
matches_by_interface[endpoint.interface].append(endpoint)
best_interface = [i for i in interfaces
if i in matches_by_interface.keys()][0]
ret[service_type] = matches_by_interface[best_interface]
return ret
@positional()
def get_endpoints(self, service_type=None, interface=None,
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch and filter endpoint data for the specified service(s).
Returns endpoints for the specified service (or all) containing
the specified type (or all) and region (or all) and service name.
If there is no name in the service catalog the service_name check will
be skipped. This allows compatibility with services that existed
before the name was available in the catalog.
Returns a dict keyed by service_type with a list of endpoint dicts
"""
endpoints_data = self.get_endpoints_data(
service_type=service_type, interface=interface,
region_name=region_name, service_name=service_name,
service_id=service_id, endpoint_id=endpoint_id)
endpoints = {}
for service_type, data in endpoints_data.items():
endpoints[service_type] = self._denormalize_endpoints(data)
return endpoints
@positional()
def get_endpoint_data_list(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch a flat list of matching EndpointData objects.
Fetch the endpoints from the service catalog for a particular
endpoint attribute. If no attribute is given, return the first
endpoint of the specified type.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param string service_type: Service type of the endpoint.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference.
:param string region_name: Region of the endpoint.
:param string service_name: The assigned name of the service.
:param string service_id: The identifier of a service.
:param string endpoint_id: The identifier of an endpoint.
:returns: a list of matching EndpointData objects
:rtype: list(`keystoneauth1.discover.EndpointData`)
"""
endpoints = self.get_endpoints_data(service_type=service_type,
interface=interface,
region_name=region_name,
service_name=service_name,
service_id=service_id,
endpoint_id=endpoint_id)
return [endpoint for data in endpoints.values() for endpoint in data]
@positional()
def get_urls(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch endpoint urls from the service catalog.
Fetch the urls of endpoints from the service catalog for a particular
endpoint attribute. If no attribute is given, return the url of the
first endpoint of the specified type.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param string service_type: Service type of the endpoint.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference.
:param string region_name: Region of the endpoint.
:param string service_name: The assigned name of the service.
:param string service_id: The identifier of a service.
:param string endpoint_id: The identifier of an endpoint.
:returns: tuple of urls
"""
endpoints = self.get_endpoint_data_list(service_type=service_type,
interface=interface,
region_name=region_name,
service_name=service_name,
service_id=service_id,
endpoint_id=endpoint_id)
return tuple([endpoint.url for endpoint in endpoints])
@positional()
def url_for(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch an endpoint from the service catalog.
Fetch the specified endpoint from the service catalog for
a particular endpoint attribute. If no attribute is given, return
the first endpoint of the specified type.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param string service_type: Service type of the endpoint.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference.
:param string region_name: Region of the endpoint.
:param string service_name: The assigned name of the service.
:param string service_id: The identifier of a service.
:param string endpoint_id: The identifier of an endpoint.
"""
return self.endpoint_data_for(service_type=service_type,
interface=interface,
region_name=region_name,
service_name=service_name,
service_id=service_id,
endpoint_id=endpoint_id).url
@positional()
def endpoint_data_for(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
"""Fetch endpoint data from the service catalog.
Fetch the specified endpoint data from the service catalog for
a particular endpoint attribute. If no attribute is given, return
the first endpoint of the specified type.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param string service_type: Service type of the endpoint.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference.
:param string region_name: Region of the endpoint.
:param string service_name: The assigned name of the service.
:param string service_id: The identifier of a service.
:param string endpoint_id: The identifier of an endpoint.
"""
if not self._catalog:
raise exceptions.EmptyCatalog('The service catalog is empty.')
endpoint_data_list = self.get_endpoint_data_list(
service_type=service_type,
interface=interface,
region_name=region_name,
service_name=service_name,
service_id=service_id,
endpoint_id=endpoint_id)
if endpoint_data_list:
return endpoint_data_list[0]
if service_name and region_name:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s in %(region_name)s region not '
'found' %
{'interface': interface,
'service_type': service_type, 'service_name': service_name,
'region_name': region_name})
elif service_name:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s not found' %
{'interface': interface,
'service_type': service_type,
'service_name': service_name})
elif region_name:
msg = ('%(interface)s endpoint for %(service_type)s service '
'in %(region_name)s region not found' %
{'interface': interface,
'service_type': service_type, 'region_name': region_name})
else:
msg = ('%(interface)s endpoint for %(service_type)s service '
'not found' %
{'interface': interface,
'service_type': service_type})
raise exceptions.EndpointNotFound(msg)
class ServiceCatalogV2(ServiceCatalog):
"""An object for encapsulating the v2 service catalog.
The object is created using raw v2 auth token from Keystone.
"""
@classmethod
def from_token(cls, token):
if 'access' not in token:
raise ValueError('Invalid token format for fetching catalog')
return cls(token['access'].get('serviceCatalog', {}))
@staticmethod
def normalize_interface(interface):
if interface and 'URL' not in interface:
interface += 'URL'
return interface
def is_interface_match(self, endpoint, interface):
return interface in endpoint
def _normalize_endpoints(self, endpoints):
"""Translate endpoint description dicts into v3 form.
Takes a raw endpoint description from the catalog and changes
it to be in v3 format. It also saves a copy of the data in
raw_endpoint so that it can be returned by methods that expect the
actual original data.
:param list endpoints: List of endpoint description dicts
:returns: List of endpoint description dicts in v3 format
"""
new_endpoints = []
for endpoint in endpoints:
raw_endpoint = endpoint.copy()
interface_urls = {}
interface_keys = [key for key in endpoint.keys()
if key.endswith('URL')]
for key in interface_keys:
interface = self.normalize_interface(key)
interface_urls[interface] = endpoint.pop(key)
for interface, url in interface_urls.items():
new_endpoint = endpoint.copy()
new_endpoint['interface'] = interface
new_endpoint['url'] = url
# Save the actual endpoint for ease of later reconstruction
new_endpoint['raw_endpoint'] = raw_endpoint
new_endpoints.append(new_endpoint)
return new_endpoints
def _denormalize_endpoints(self, endpoints):
"""Return original endpoint description dicts.
Takes a list of EndpointData objects and returns the original
dict that was returned from the catalog.
:param list endpoints: List of `keystoneauth1.discover.EndpointData`
:returns: List of endpoint description dicts in original catalog format
"""
raw_endpoints = super(ServiceCatalogV2, self)._denormalize_endpoints(
endpoints)
# The same raw endpoint content will be in the list once for each
# v2 endpoint_type entry. We only need one of them in the resulting
# list. So keep a list of the string versions.
seen = {}
endpoints = []
for endpoint in raw_endpoints:
if str(endpoint) in seen:
continue
seen[str(endpoint)] = True
endpoints.append(endpoint)
return endpoints
class ServiceCatalogV3(ServiceCatalog):
"""An object for encapsulating the v3 service catalog.
The object is created using raw v3 auth token from Keystone.
"""
@classmethod
def from_token(cls, token):
if 'token' not in token:
raise ValueError('Invalid token format for fetching catalog')
return cls(token['token'].get('catalog', {}))
@staticmethod
def normalize_interface(interface):
if interface:
interface = interface.rstrip('URL')
return interface
def is_interface_match(self, endpoint, interface):
try:
return interface == endpoint['interface']
except KeyError:
return False

View File

@ -1,44 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from keystoneauth1 import exceptions
class ServiceProviders(object):
"""Helper methods for dealing with Service Providers."""
@classmethod
def from_token(cls, token):
if 'token' not in token:
raise ValueError('Token format does not support service'
'providers.')
return cls(token['token'].get('service_providers', []))
def __init__(self, service_providers):
def normalize(service_providers_list):
return dict((sp['id'], sp) for sp in service_providers_list
if 'id' in sp)
self._service_providers = normalize(service_providers)
def _get_service_provider(self, sp_id):
try:
return self._service_providers[sp_id]
except KeyError:
raise exceptions.ServiceProviderNotFound(sp_id)
def get_sp_url(self, sp_id):
return self._get_service_provider(sp_id).get('sp_url')
def get_auth_url(self, sp_id):
return self._get_service_provider(sp_id).get('auth_url')

View File

@ -1,466 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import warnings
from positional import positional
from keystoneauth1 import session
class Adapter(object):
"""An instance of a session with local variables.
A session is a global object that is shared around amongst many clients. It
therefore contains state that is relevant to everyone. There is a lot of
state such as the service type and region_name that are only relevant to a
particular client that is using the session. An adapter provides a wrapper
of client local data around the global session object.
version, min_version, max_version and default_microversion can all be
given either as a string or a tuple.
:param session: The session object to wrap.
:type session: keystoneauth1.session.Session
:param str service_type: The default service_type for URL discovery.
:param str service_name: The default service_name for URL discovery.
:param str interface: The default interface for URL discovery.
:param str region_name: The default region_name for URL discovery.
:param str endpoint_override: Always use this endpoint URL for requests
for this client.
:param version: The minimum version restricted to a given Major API.
Mutually exclusive with min_version and max_version.
(optional)
:param auth: An auth plugin to use instead of the session one.
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:param str user_agent: The User-Agent string to set.
:param int connect_retries: the maximum number of retries that should
be attempted for connection errors.
Default None - use session default which
is don't retry.
:param logger: A logging object to use for requests that pass through this
adapter.
:type logger: logging.Logger
:param dict allow: Extra filters to pass when discovering API versions.
(optional)
:param dict additional_headers: Additional headers that should be attached
to every request passing through the
adapter. Headers of the same name specified
per request will take priority.
:param str client_name: The name of the client that created the adapter.
This will be used to create the user_agent.
:param str client_version: The version of the client that created the
adapter. This will be used to create the
user_agent.
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
URLS to support older schemes.
(optional, default True)
:param str global_request_id: A global_request_id (in the form of
``req-$uuid``) that will be passed on all
requests. Enables cross project request id
tracking.
:param min_version: The minimum major version of a given API, intended to
be used as the lower bound of a range with
max_version. Mutually exclusive with version.
If min_version is given with no max_version it is as
if max version is 'latest'. (optional)
:param max_version: The maximum major version of a given API, intended to
be used as the upper bound of a range with min_version.
Mutually exclusive with version. (optional)
:param default_microversion: The default microversion value to send
with API requests. While microversions are
a per-request feature, a user may know they
want to default to sending a specific value.
(optional)
"""
client_name = None
client_version = None
@positional()
def __init__(self, session, service_type=None, service_name=None,
interface=None, region_name=None, endpoint_override=None,
version=None, auth=None, user_agent=None,
connect_retries=None, logger=None, allow={},
additional_headers=None, client_name=None,
client_version=None, allow_version_hack=None,
global_request_id=None,
min_version=None, max_version=None,
default_microversion=None):
if version and (min_version or max_version):
raise TypeError(
"version is mutually exclusive with min_version and"
" max_version")
# NOTE(jamielennox): when adding new parameters to adapter please also
# add them to the adapter call in httpclient.HTTPClient.__init__ as
# well as to load_adapter_from_argparse below if the argument is
# intended to be something a user would reasonably expect to set on
# a command line
self.session = session
self.service_type = service_type
self.service_name = service_name
self.interface = interface
self.region_name = region_name
self.endpoint_override = endpoint_override
self.version = version
self.user_agent = user_agent
self.auth = auth
self.connect_retries = connect_retries
self.logger = logger
self.allow = allow
self.additional_headers = additional_headers or {}
self.allow_version_hack = allow_version_hack
self.min_version = min_version
self.max_version = max_version
self.default_microversion = default_microversion
self.global_request_id = global_request_id
if client_name:
self.client_name = client_name
if client_version:
self.client_version = client_version
def _set_endpoint_filter_kwargs(self, kwargs):
if self.service_type:
kwargs.setdefault('service_type', self.service_type)
if self.service_name:
kwargs.setdefault('service_name', self.service_name)
if self.interface:
kwargs.setdefault('interface', self.interface)
if self.region_name:
kwargs.setdefault('region_name', self.region_name)
if self.version:
kwargs.setdefault('version', self.version)
if self.min_version:
kwargs.setdefault('min_version', self.min_version)
if self.max_version:
kwargs.setdefault('max_version', self.max_version)
if self.allow_version_hack is not None:
kwargs.setdefault('allow_version_hack', self.allow_version_hack)
def request(self, url, method, **kwargs):
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
self._set_endpoint_filter_kwargs(endpoint_filter)
if self.endpoint_override:
kwargs.setdefault('endpoint_override', self.endpoint_override)
if self.auth:
kwargs.setdefault('auth', self.auth)
if self.user_agent:
kwargs.setdefault('user_agent', self.user_agent)
if self.connect_retries is not None:
kwargs.setdefault('connect_retries', self.connect_retries)
if self.logger:
kwargs.setdefault('logger', self.logger)
if self.allow:
kwargs.setdefault('allow', self.allow)
if self.default_microversion is not None:
kwargs.setdefault('microversion', self.default_microversion)
if isinstance(self.session, (session.Session, Adapter)):
# these things are unsupported by keystoneclient's session so be
# careful with them until everyone has transitioned to ksa.
# Allowing adapter allows adapter nesting that auth_token does.
if self.client_name:
kwargs.setdefault('client_name', self.client_name)
if self.client_version:
kwargs.setdefault('client_version', self.client_version)
else:
warnings.warn('Using keystoneclient sessions has been deprecated. '
'Please update your software to use keystoneauth1.')
for k, v in self.additional_headers.items():
kwargs.setdefault('headers', {}).setdefault(k, v)
if self.global_request_id is not None:
kwargs.setdefault('headers', {}).setdefault(
"X-OpenStack-Request-ID", self.global_request_id)
return self.session.request(url, method, **kwargs)
def get_token(self, auth=None):
"""Return a token as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin
on the session. (optional)
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:raises keystoneauth1.exceptions.auth.AuthorizationFailure: if a new
token fetch fails.
:returns: A valid token.
:rtype: :class:`str`
"""
return self.session.get_token(auth or self.auth)
def get_endpoint(self, auth=None, **kwargs):
"""Get an endpoint as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin on
the session. (optional)
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a
plugin is not available.
:returns: An endpoint if available or None.
:rtype: :class:`str`
"""
if self.endpoint_override:
return self.endpoint_override
self._set_endpoint_filter_kwargs(kwargs)
return self.session.get_endpoint(auth or self.auth, **kwargs)
def get_endpoint_data(self, auth=None):
"""Get the endpoint data for this Adapter's endpoint.
:param auth: The auth plugin to use for token. Overrides the plugin on
the session. (optional)
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a
plugin is not available.
:raises TypeError: If arguments are invalid
:returns: Endpoint data if available or None.
:rtype: keystoneauth1.discover.EndpointData
"""
kwargs = {}
self._set_endpoint_filter_kwargs(kwargs)
if self.endpoint_override:
kwargs['endpoint_override'] = self.endpoint_override
return self.session.get_endpoint_data(auth or self.auth, **kwargs)
def invalidate(self, auth=None):
"""Invalidate an authentication plugin."""
return self.session.invalidate(auth or self.auth)
def get_user_id(self, auth=None):
"""Return the authenticated user_id as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin
on the session. (optional)
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:raises keystoneauth1.exceptions.auth.AuthorizationFailure:
if a new token fetch fails.
:raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin:
if a plugin is not available.
:returns: Current `user_id` or None if not supported by plugin.
:rtype: :class:`str`
"""
return self.session.get_user_id(auth or self.auth)
def get_project_id(self, auth=None):
"""Return the authenticated project_id as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin
on the session. (optional)
:type auth: keystoneauth1.plugin.BaseAuthPlugin
:raises keystoneauth1.exceptions.auth.AuthorizationFailure:
if a new token fetch fails.
:raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin:
if a plugin is not available.
:returns: Current `project_id` or None if not supported by plugin.
:rtype: :class:`str`
"""
return self.session.get_project_id(auth or self.auth)
def get(self, url, **kwargs):
return self.request(url, 'GET', **kwargs)
def head(self, url, **kwargs):
return self.request(url, 'HEAD', **kwargs)
def post(self, url, **kwargs):
return self.request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self.request(url, 'PUT', **kwargs)
def patch(self, url, **kwargs):
return self.request(url, 'PATCH', **kwargs)
def delete(self, url, **kwargs):
return self.request(url, 'DELETE', **kwargs)
# TODO(efried): Move this to loading.adapter.Adapter
@classmethod
def register_argparse_arguments(cls, parser, service_type=None):
"""Attach arguments to a given argparse Parser for Adapters.
:param parser: The argparse parser to attach options to.
:type parser: argparse.ArgumentParser
:param str service_type: Default service_type value. (optional)
"""
adapter_group = parser.add_argument_group(
'Service Options',
'Options controlling the specialization of the API'
' Connection from information found in the catalog')
adapter_group.add_argument(
'--os-service-type',
metavar='<name>',
default=os.environ.get('OS_SERVICE_TYPE', service_type),
help='Service type to request from the catalog')
adapter_group.add_argument(
'--os-service-name',
metavar='<name>',
default=os.environ.get('OS_SERVICE_NAME', None),
help='Service name to request from the catalog')
adapter_group.add_argument(
'--os-interface',
metavar='<name>',
default=os.environ.get('OS_INTERFACE', 'public'),
help='API Interface to use [public, internal, admin]')
adapter_group.add_argument(
'--os-region-name',
metavar='<name>',
default=os.environ.get('OS_REGION_NAME', None),
help='Region of the cloud to use')
adapter_group.add_argument(
'--os-endpoint-override',
metavar='<name>',
default=os.environ.get('OS_ENDPOINT_OVERRIDE', None),
help='Endpoint to use instead of the endpoint in the catalog')
adapter_group.add_argument(
'--os-api-version',
metavar='<name>',
default=os.environ.get('OS_API_VERSION', None),
help='Which version of the service API to use')
# TODO(efried): Move this to loading.adapter.Adapter
@classmethod
def register_service_argparse_arguments(cls, parser, service_type):
"""Attach arguments to a given argparse Parser for Adapters.
:param parser: The argparse parser to attach options to.
:type parser: argparse.ArgumentParser
:param str service_type: Name of a service to generate additional
arguments for.
"""
service_env = service_type.upper().replace('-', '_')
adapter_group = parser.add_argument_group(
'{service_type} Service Options'.format(
service_type=service_type.title()),
'Options controlling the specialization of the {service_type}'
' API Connection from information found in the catalog'.format(
service_type=service_type.title()))
adapter_group.add_argument(
'--os-{service_type}-service-type'.format(
service_type=service_type),
metavar='<name>',
default=os.environ.get(
'OS_{service_type}_SERVICE_TYPE'.format(
service_type=service_env), None),
help=('Service type to request from the catalog for the'
' {service_type} service'.format(
service_type=service_type)))
adapter_group.add_argument(
'--os-{service_type}-service-name'.format(
service_type=service_type),
metavar='<name>',
default=os.environ.get(
'OS_{service_type}_SERVICE_NAME'.format(
service_type=service_env), None),
help=('Service name to request from the catalog for the'
' {service_type} service'.format(
service_type=service_type)))
adapter_group.add_argument(
'--os-{service_type}-interface'.format(
service_type=service_type),
metavar='<name>',
default=os.environ.get(
'OS_{service_type}_INTERFACE'.format(
service_type=service_env), None),
help=('API Interface to use for the {service_type} service'
' [public, internal, admin]'.format(
service_type=service_type)))
adapter_group.add_argument(
'--os-{service_type}-api-version'.format(
service_type=service_type),
metavar='<name>',
default=os.environ.get(
'OS_{service_type}_API_VERSION'.format(
service_type=service_env), None),
help=('Which version of the service API to use for'
' the {service_type} service'.format(
service_type=service_type)))
adapter_group.add_argument(
'--os-{service_type}-endpoint-override'.format(
service_type=service_type),
metavar='<name>',
default=os.environ.get(
'OS_{service_type}_ENDPOINT_OVERRIDE'.format(
service_type=service_env), None),
help=('Endpoint to use for the {service_type} service'
' instead of the endpoint in the catalog'.format(
service_type=service_type)))
class LegacyJsonAdapter(Adapter):
"""Make something that looks like an old HTTPClient.
A common case when using an adapter is that we want an interface similar to
the HTTPClients of old which returned the body as JSON as well.
You probably don't want this if you are starting from scratch.
"""
def request(self, *args, **kwargs):
headers = kwargs.setdefault('headers', {})
headers.setdefault('Accept', 'application/json')
try:
kwargs['json'] = kwargs.pop('body')
except KeyError:
pass
resp = super(LegacyJsonAdapter, self).request(*args, **kwargs)
try:
body = resp.json()
except ValueError:
body = None
return resp, body
# TODO(efried): Deprecate this in favor of
# loading.adapter.register_argparse_arguments
def register_adapter_argparse_arguments(*args, **kwargs):
return Adapter.register_argparse_arguments(*args, **kwargs)
# TODO(efried): Deprecate this in favor of
# loading.adapter.register_service_argparse_arguments
def register_service_adapter_argparse_arguments(*args, **kwargs):
return Adapter.register_service_argparse_arguments(*args, **kwargs)

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions.auth import * # noqa
from keystoneauth1.exceptions.auth_plugins import * # noqa
from keystoneauth1.exceptions.base import * # noqa
from keystoneauth1.exceptions.catalog import * # noqa
from keystoneauth1.exceptions.connection import * # noqa
from keystoneauth1.exceptions.discovery import * # noqa
from keystoneauth1.exceptions.http import * # noqa
from keystoneauth1.exceptions.oidc import * # noqa
from keystoneauth1.exceptions.response import * # noqa
from keystoneauth1.exceptions.service_providers import * # noqa

View File

@ -1,17 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
class AuthorizationFailure(base.ClientException):
message = "Cannot authorize API client."

View File

@ -1,93 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('AuthPluginException',
'MissingAuthPlugin',
'NoMatchingPlugin',
'UnsupportedParameters',
'OptionError',
'MissingRequiredOptions')
class AuthPluginException(base.ClientException):
message = "Unknown error with authentication plugins."
class MissingAuthPlugin(AuthPluginException):
message = "An authenticated request is required but no plugin available."
class NoMatchingPlugin(AuthPluginException):
"""No auth plugins could be created from the parameters provided.
:param str name: The name of the plugin that was attempted to load.
.. py:attribute:: name
The name of the plugin that was attempted to load.
"""
def __init__(self, name):
self.name = name
msg = 'The plugin %s could not be found' % name
super(NoMatchingPlugin, self).__init__(msg)
class UnsupportedParameters(AuthPluginException):
"""A parameter that was provided or returned is not supported.
:param list(str) names: Names of the unsupported parameters.
.. py:attribute:: names
Names of the unsupported parameters.
"""
def __init__(self, names):
self.names = names
m = 'The following parameters were given that are unsupported: %s'
super(UnsupportedParameters, self).__init__(m % ', '.join(self.names))
class OptionError(AuthPluginException):
"""A requirement of this plugin loader was not met.
This error can be raised by a specific plugin loader during the
load_from_options stage to indicate a parameter problem that can not be
handled by the generic options loader.
The intention here is that a plugin can do checks like if a name parameter
is provided then a domain parameter must also be provided, but that Opt
checking doesn't handle.
"""
class MissingRequiredOptions(OptionError):
"""One or more required options were not provided.
:param list(keystoneauth1.loading.Opt) options: Missing options.
.. py:attribute:: options
List of the missing options.
"""
def __init__(self, options):
self.options = options
names = ", ".join(o.dest for o in options)
m = 'Auth plugin requires parameters which were not given: %s'
super(MissingRequiredOptions, self).__init__(m % names)

View File

@ -1,24 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
__all__ = ('ClientException',)
class ClientException(Exception):
"""The base exception for everything to do with clients."""
message = "ClientException"
def __init__(self, message=None):
self.message = message or self.message
super(ClientException, self).__init__(self.message)

View File

@ -1,30 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('CatalogException',
'EmptyCatalog',
'EndpointNotFound')
class CatalogException(base.ClientException):
message = "Unknown error with service catalog."
class EndpointNotFound(CatalogException):
message = "Could not find requested endpoint in Service Catalog."
class EmptyCatalog(EndpointNotFound):
message = "The service catalog is empty."

View File

@ -1,51 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('ConnectionError',
'ConnectTimeout',
'ConnectFailure',
'SSLError',
'RetriableConnectionFailure',
'UnknownConnectionError')
class RetriableConnectionFailure(Exception):
"""A mixin class that implies you can retry the most recent request."""
pass
class ConnectionError(base.ClientException):
message = "Cannot connect to API service."
class ConnectTimeout(ConnectionError, RetriableConnectionFailure):
message = "Timed out connecting to service."
class ConnectFailure(ConnectionError, RetriableConnectionFailure):
message = "Connection failure that may be retried."
class SSLError(ConnectionError):
message = "An SSL error occurred."
class UnknownConnectionError(ConnectionError):
"""An error was encountered but we don't know what it is."""
def __init__(self, msg, original):
super(UnknownConnectionError, self).__init__(msg)
self.original = original

View File

@ -1,25 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('DiscoveryFailure',
'VersionNotAvailable')
class DiscoveryFailure(base.ClientException):
message = "Discovery of client versions failed."
class VersionNotAvailable(DiscoveryFailure):
message = "Discovery failed. Requested version is not available."

View File

@ -1,428 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 Nebula, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""HTTP Exceptions used by keystoneauth1."""
import inspect
import sys
from keystoneauth1.exceptions import base
__all__ = ('HttpError',
'HTTPClientError',
'BadRequest',
'Unauthorized',
'PaymentRequired',
'Forbidden',
'NotFound',
'MethodNotAllowed',
'NotAcceptable',
'ProxyAuthenticationRequired',
'RequestTimeout',
'Conflict',
'Gone',
'LengthRequired',
'PreconditionFailed',
'RequestEntityTooLarge',
'RequestUriTooLong',
'UnsupportedMediaType',
'RequestedRangeNotSatisfiable',
'ExpectationFailed',
'UnprocessableEntity',
'HttpServerError',
'InternalServerError',
'HttpNotImplemented',
'BadGateway',
'ServiceUnavailable',
'GatewayTimeout',
'HttpVersionNotSupported',
'from_response')
class HttpError(base.ClientException):
"""The base exception class for all HTTP exceptions."""
http_status = 0
message = "HTTP Error"
def __init__(self, message=None, details=None,
response=None, request_id=None,
url=None, method=None, http_status=None,
retry_after=0):
self.http_status = http_status or self.http_status
self.message = message or self.message
self.details = details
self.request_id = request_id
self.response = response
self.url = url
self.method = method
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
self.retry_after = retry_after
if request_id:
formatted_string += " (Request-ID: %s)" % request_id
super(HttpError, self).__init__(formatted_string)
class HTTPClientError(HttpError):
"""Client-side HTTP error.
Exception for cases in which the client seems to have erred.
"""
message = "HTTP Client Error"
class HttpServerError(HttpError):
"""Server-side HTTP error.
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
message = "HTTP Server Error"
class BadRequest(HTTPClientError):
"""HTTP 400 - Bad Request.
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
message = "Bad Request"
class Unauthorized(HTTPClientError):
"""HTTP 401 - Unauthorized.
Similar to 403 Forbidden, but specifically for use when authentication
is required and has failed or has not yet been provided.
"""
http_status = 401
message = "Unauthorized"
class PaymentRequired(HTTPClientError):
"""HTTP 402 - Payment Required.
Reserved for future use.
"""
http_status = 402
message = "Payment Required"
class Forbidden(HTTPClientError):
"""HTTP 403 - Forbidden.
The request was a valid request, but the server is refusing to respond
to it.
"""
http_status = 403
message = "Forbidden"
class NotFound(HTTPClientError):
"""HTTP 404 - Not Found.
The requested resource could not be found but may be available again
in the future.
"""
http_status = 404
message = "Not Found"
class MethodNotAllowed(HTTPClientError):
"""HTTP 405 - Method Not Allowed.
A request was made of a resource using a request method not supported
by that resource.
"""
http_status = 405
message = "Method Not Allowed"
class NotAcceptable(HTTPClientError):
"""HTTP 406 - Not Acceptable.
The requested resource is only capable of generating content not
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
message = "Not Acceptable"
class ProxyAuthenticationRequired(HTTPClientError):
"""HTTP 407 - Proxy Authentication Required.
The client must first authenticate itself with the proxy.
"""
http_status = 407
message = "Proxy Authentication Required"
class RequestTimeout(HTTPClientError):
"""HTTP 408 - Request Timeout.
The server timed out waiting for the request.
"""
http_status = 408
message = "Request Timeout"
class Conflict(HTTPClientError):
"""HTTP 409 - Conflict.
Indicates that the request could not be processed because of conflict
in the request, such as an edit conflict.
"""
http_status = 409
message = "Conflict"
class Gone(HTTPClientError):
"""HTTP 410 - Gone.
Indicates that the resource requested is no longer available and will
not be available again.
"""
http_status = 410
message = "Gone"
class LengthRequired(HTTPClientError):
"""HTTP 411 - Length Required.
The request did not specify the length of its content, which is
required by the requested resource.
"""
http_status = 411
message = "Length Required"
class PreconditionFailed(HTTPClientError):
"""HTTP 412 - Precondition Failed.
The server does not meet one of the preconditions that the requester
put on the request.
"""
http_status = 412
message = "Precondition Failed"
class RequestEntityTooLarge(HTTPClientError):
"""HTTP 413 - Request Entity Too Large.
The request is larger than the server is willing or able to process.
"""
http_status = 413
message = "Request Entity Too Large"
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
class RequestUriTooLong(HTTPClientError):
"""HTTP 414 - Request-URI Too Long.
The URI provided was too long for the server to process.
"""
http_status = 414
message = "Request-URI Too Long"
class UnsupportedMediaType(HTTPClientError):
"""HTTP 415 - Unsupported Media Type.
The request entity has a media type which the server or resource does
not support.
"""
http_status = 415
message = "Unsupported Media Type"
class RequestedRangeNotSatisfiable(HTTPClientError):
"""HTTP 416 - Requested Range Not Satisfiable.
The client has asked for a portion of the file, but the server cannot
supply that portion.
"""
http_status = 416
message = "Requested Range Not Satisfiable"
class ExpectationFailed(HTTPClientError):
"""HTTP 417 - Expectation Failed.
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
message = "Expectation Failed"
class UnprocessableEntity(HTTPClientError):
"""HTTP 422 - Unprocessable Entity.
The request was well-formed but was unable to be followed due to semantic
errors.
"""
http_status = 422
message = "Unprocessable Entity"
class InternalServerError(HttpServerError):
"""HTTP 500 - Internal Server Error.
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
message = "Internal Server Error"
# NotImplemented is a python keyword.
class HttpNotImplemented(HttpServerError):
"""HTTP 501 - Not Implemented.
The server either does not recognize the request method, or it lacks
the ability to fulfill the request.
"""
http_status = 501
message = "Not Implemented"
class BadGateway(HttpServerError):
"""HTTP 502 - Bad Gateway.
The server was acting as a gateway or proxy and received an invalid
response from the upstream server.
"""
http_status = 502
message = "Bad Gateway"
class ServiceUnavailable(HttpServerError):
"""HTTP 503 - Service Unavailable.
The server is currently unavailable.
"""
http_status = 503
message = "Service Unavailable"
class GatewayTimeout(HttpServerError):
"""HTTP 504 - Gateway Timeout.
The server was acting as a gateway or proxy and did not receive a timely
response from the upstream server.
"""
http_status = 504
message = "Gateway Timeout"
class HttpVersionNotSupported(HttpServerError):
"""HTTP 505 - HttpVersion Not Supported.
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
message = "HTTP Version Not Supported"
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in vars(sys.modules[__name__]).items()
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
def from_response(response, method, url):
"""Return an instance of :class:`HttpError` or subclass based on response.
:param response: instance of `requests.Response` class
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": req_id,
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
try:
body = response.json()
except ValueError:
pass
else:
if isinstance(body, dict) and isinstance(body.get("error"), dict):
error = body["error"]
kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details")
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = _code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = HttpServerError
elif 400 <= response.status_code < 500:
cls = HTTPClientError
else:
cls = HttpError
return cls(**kwargs)

View File

@ -1,44 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import auth_plugins
__all__ = (
'InvalidDiscoveryEndpoint', 'InvalidOidcDiscoveryDocument',
'OidcAccessTokenEndpointNotFound', 'OidcAuthorizationEndpointNotFound',
'OidcGrantTypeMissmatch', 'OidcPluginNotSupported',
)
class InvalidDiscoveryEndpoint(auth_plugins.AuthPluginException):
message = "OpenID Connect Discovery Document endpoint not set."""
class InvalidOidcDiscoveryDocument(auth_plugins.AuthPluginException):
message = "OpenID Connect Discovery Document is not valid JSON."""
class OidcAccessTokenEndpointNotFound(auth_plugins.AuthPluginException):
message = "OpenID Connect access token endpoint not provided."
class OidcAuthorizationEndpointNotFound(auth_plugins.AuthPluginException):
message = "OpenID Connect authorization endpoint not provided."
class OidcGrantTypeMissmatch(auth_plugins.AuthPluginException):
message = "Missmatch between OpenID Connect plugin and grant_type argument"
class OidcPluginNotSupported(auth_plugins.AuthPluginException):
message = "OpenID Connect grant type not supported by provider."

View File

@ -1,25 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('InvalidResponse',)
class InvalidResponse(base.ClientException):
message = "Invalid response from server."
def __init__(self, response):
super(InvalidResponse, self).__init__()
self.response = response

View File

@ -1,24 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.exceptions import base
__all__ = ('ServiceProviderNotFound',)
class ServiceProviderNotFound(base.ClientException):
"""A Service Provider cannot be found."""
def __init__(self, sp_id):
self.sp_id = sp_id
msg = 'The Service Provider %(sp)s could not be found' % {'sp': sp_id}
super(ServiceProviderNotFound, self).__init__(msg)

View File

@ -1,20 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# NOTE(jamielennox): This directory is designed to reflect the dependency
# extras in the setup.cfg file. If you create an additional dependency section
# like 'kerberos' in the setup.cfg it is expected that there be a kerberos
# package here that can be imported.
#
# e.g. from keystoneauth1.extras import kerberos
pass

View File

@ -1,22 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras._saml2 import v3
_V3_SAML2_AVAILABLE = v3._SAML2_AVAILABLE
_V3_ADFS_AVAILABLE = v3._ADFS_AVAILABLE
V3Saml2Password = v3.Saml2Password
V3ADFSPassword = v3.ADFSPassword
__all__ = ('V3Saml2Password', 'V3ADFSPassword')

View File

@ -1,66 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras import _saml2
from keystoneauth1 import loading
class Saml2Password(loading.BaseFederationLoader):
@property
def plugin_class(self):
return _saml2.V3Saml2Password
@property
def available(self):
return _saml2._V3_SAML2_AVAILABLE
def get_options(self):
options = super(Saml2Password, self).get_options()
options.extend([
loading.Opt('identity-provider-url',
help=('An Identity Provider URL, where the SAML2 '
'authentication request will be sent.')),
loading.Opt('username', help='Username'),
loading.Opt('password', secret=True, help='Password')
])
return options
class ADFSPassword(loading.BaseFederationLoader):
@property
def plugin_class(self):
return _saml2.V3ADFSPassword
@property
def available(self):
return _saml2._V3_ADFS_AVAILABLE
def get_options(self):
options = super(ADFSPassword, self).get_options()
options.extend([
loading.Opt('identity-provider-url',
help=('An Identity Provider URL, where the SAML '
'authentication request will be sent.')),
loading.Opt('service-provider-endpoint',
help="Service Provider's Endpoint"),
loading.Opt('service-provider-entity-id',
help="Service Provider's SAML Entity ID"),
loading.Opt('username', help='Username'),
loading.Opt('password', secret=True, help='Password')
])
return options

View File

@ -1,23 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras._saml2.v3 import adfs
from keystoneauth1.extras._saml2.v3 import base
from keystoneauth1.extras._saml2.v3 import saml2
_SAML2_AVAILABLE = base.etree is not None and saml2.etree is not None
_ADFS_AVAILABLE = base.etree is not None and adfs.etree is not None
Saml2Password = saml2.Password
ADFSPassword = adfs.Password
__all__ = ('Saml2Password', 'ADFSPassword')

View File

@ -1,431 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
try:
from lxml import etree
except ImportError:
etree = None
from six.moves import urllib
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.extras._saml2.v3 import base
class Password(base.BaseSAMLPlugin):
"""Authentication plugin for Microsoft ADFS2.0 IdPs."""
DEFAULT_ADFS_TOKEN_EXPIRATION = 120
HEADER_SOAP = {"Content-Type": "application/soap+xml; charset=utf-8"}
HEADER_X_FORM = {"Content-Type": "application/x-www-form-urlencoded"}
NAMESPACES = {
's': 'http://www.w3.org/2003/05/soap-envelope',
'a': 'http://www.w3.org/2005/08/addressing',
'u': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-utility-1.0.xsd')
}
ADFS_TOKEN_NAMESPACES = {
's': 'http://www.w3.org/2003/05/soap-envelope',
't': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512'
}
ADFS_ASSERTION_XPATH = ('/s:Envelope/s:Body'
'/t:RequestSecurityTokenResponseCollection'
'/t:RequestSecurityTokenResponse')
def __init__(self, auth_url, identity_provider, identity_provider_url,
service_provider_endpoint, username, password,
protocol, service_provider_entity_id=None, **kwargs):
"""Constructor for ``ADFSPassword``.
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: name of the Identity Provider the client
will authenticate against. This parameter
will be used to build a dynamic URL used to
obtain unscoped OpenStack token.
:type identity_provider: string
:param identity_provider_url: An Identity Provider URL, where the SAML2
authentication request will be sent.
:type identity_provider_url: string
:param service_provider_endpoint: Endpoint where an assertion is being
sent, for instance: ``https://host.domain/Shibboleth.sso/ADFS``
:type service_provider_endpoint: string
:param service_provider_entity_id: Service Provider SAML Entity ID
:type service_provider_entity_id: string
:param username: User's login
:type username: string
:param password: User's password
:type password: string
"""
super(Password, self).__init__(
auth_url=auth_url, identity_provider=identity_provider,
identity_provider_url=identity_provider_url,
username=username, password=password, protocol=protocol, **kwargs)
self.service_provider_endpoint = service_provider_endpoint
self.service_provider_entity_id = service_provider_entity_id
def _cookies(self, session):
"""Check if cookie jar is not empty.
keystoneauth1.session.Session object doesn't have a cookies attribute.
We should then try fetching cookies from the underlying
requests.Session object. If that fails too, there is something wrong
and let Python raise the AttributeError.
:param session
:returns: True if cookie jar is nonempty, False otherwise
:raises AttributeError: in case cookies are not find anywhere
"""
try:
return bool(session.cookies)
except AttributeError:
pass
return bool(session.session.cookies)
def _token_dates(self, fmt='%Y-%m-%dT%H:%M:%S.%fZ'):
"""Calculate created and expires datetime objects.
The method is going to be used for building ADFS Request Security
Token message. Time interval between ``created`` and ``expires``
dates is now static and equals to 120 seconds. ADFS security tokens
should not be live too long, as currently ``keystoneauth1``
doesn't have mechanisms for reusing such tokens (every time ADFS authn
method is called, keystoneauth1 will login with the ADFS instance).
:param fmt: Datetime format for specifying string format of a date.
It should not be changed if the method is going to be used
for building the ADFS security token request.
:type fmt: string
"""
date_created = datetime.datetime.utcnow()
date_expires = date_created + datetime.timedelta(
seconds=self.DEFAULT_ADFS_TOKEN_EXPIRATION)
return [_time.strftime(fmt) for _time in (date_created, date_expires)]
def _prepare_adfs_request(self):
"""Build the ADFS Request Security Token SOAP message.
Some values like username or password are inserted in the request.
"""
WSS_SECURITY_NAMESPACE = {
'o': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-secext-1.0.xsd')
}
TRUST_NAMESPACE = {
'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512'
}
WSP_NAMESPACE = {
'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy'
}
WSA_NAMESPACE = {
'wsa': 'http://www.w3.org/2005/08/addressing'
}
root = etree.Element(
'{http://www.w3.org/2003/05/soap-envelope}Envelope',
nsmap=self.NAMESPACES)
header = etree.SubElement(
root, '{http://www.w3.org/2003/05/soap-envelope}Header')
action = etree.SubElement(
header, "{http://www.w3.org/2005/08/addressing}Action")
action.set(
"{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
action.text = ('http://docs.oasis-open.org/ws-sx/ws-trust/200512'
'/RST/Issue')
messageID = etree.SubElement(
header, '{http://www.w3.org/2005/08/addressing}MessageID')
messageID.text = 'urn:uuid:' + uuid.uuid4().hex
replyID = etree.SubElement(
header, '{http://www.w3.org/2005/08/addressing}ReplyTo')
address = etree.SubElement(
replyID, '{http://www.w3.org/2005/08/addressing}Address')
address.text = 'http://www.w3.org/2005/08/addressing/anonymous'
to = etree.SubElement(
header, '{http://www.w3.org/2005/08/addressing}To')
to.set("{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
security = etree.SubElement(
header, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-secext-1.0.xsd}Security',
nsmap=WSS_SECURITY_NAMESPACE)
security.set(
"{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1")
timestamp = etree.SubElement(
security, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-utility-1.0.xsd}Timestamp'))
timestamp.set(
('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-utility-1.0.xsd}Id'), '_0')
created = etree.SubElement(
timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-utility-1.0.xsd}Created'))
expires = etree.SubElement(
timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-utility-1.0.xsd}Expires'))
created.text, expires.text = self._token_dates()
usernametoken = etree.SubElement(
security, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-'
'wss-wssecurity-secext-1.0.xsd}UsernameToken')
usernametoken.set(
('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-'
'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % uuid.uuid4().hex)
username = etree.SubElement(
usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-'
'200401-wss-wssecurity-secext-1.0.xsd}Username'))
password = etree.SubElement(
usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-'
'200401-wss-wssecurity-secext-1.0.xsd}Password'),
Type=('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-'
'username-token-profile-1.0#PasswordText'))
body = etree.SubElement(
root, "{http://www.w3.org/2003/05/soap-envelope}Body")
request_security_token = etree.SubElement(
body, ('{http://docs.oasis-open.org/ws-sx/ws-trust/200512}'
'RequestSecurityToken'), nsmap=TRUST_NAMESPACE)
applies_to = etree.SubElement(
request_security_token,
'{http://schemas.xmlsoap.org/ws/2004/09/policy}AppliesTo',
nsmap=WSP_NAMESPACE)
endpoint_reference = etree.SubElement(
applies_to,
'{http://www.w3.org/2005/08/addressing}EndpointReference',
nsmap=WSA_NAMESPACE)
wsa_address = etree.SubElement(
endpoint_reference,
'{http://www.w3.org/2005/08/addressing}Address')
keytype = etree.SubElement(
request_security_token,
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}KeyType')
keytype.text = ('http://docs.oasis-open.org/ws-sx/'
'ws-trust/200512/Bearer')
request_type = etree.SubElement(
request_security_token,
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}RequestType')
request_type.text = ('http://docs.oasis-open.org/ws-sx/'
'ws-trust/200512/Issue')
token_type = etree.SubElement(
request_security_token,
'{http://docs.oasis-open.org/ws-sx/ws-trust/200512}TokenType')
token_type.text = 'urn:oasis:names:tc:SAML:1.0:assertion'
# After constructing the request, let's plug in some values
username.text = self.username
password.text = self.password
to.text = self.identity_provider_url
wsa_address.text = (self.service_provider_entity_id or
self.service_provider_endpoint)
self.prepared_request = root
def _get_adfs_security_token(self, session):
"""Send ADFS Security token to the ADFS server.
Store the result in the instance attribute and raise an exception in
case the response is not valid XML data.
If a user cannot authenticate due to providing bad credentials, the
ADFS2.0 server will return a HTTP 500 response and a XML Fault message.
If ``exceptions.InternalServerError`` is caught, the method tries to
parse the XML response.
If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is
raised with a reason from the XML fault. Otherwise an original
``exceptions.InternalServerError`` is re-raised.
:param session : a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:raises keystoneauth1.exceptions.AuthorizationFailure: when HTTP
response from the ADFS server is not a valid XML ADFS security
token.
:raises keystoneauth1.exceptions.InternalServerError: If response
status code is HTTP 500 and the response XML cannot be
recognized.
"""
def _get_failure(e):
xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value'
content = e.response.content
try:
obj = self.str_to_xml(content).xpath(
xpath, namespaces=self.NAMESPACES)
obj = self._first(obj)
return obj.text
# NOTE(marek-denis): etree.Element.xpath() doesn't raise an
# exception, it just returns an empty list. In that case, _first()
# will raise IndexError and we should treat it as an indication XML
# is not valid. exceptions.AuthorizationFailure can be raised from
# str_to_xml(), however since server returned HTTP 500 we should
# re-raise exceptions.InternalServerError.
except (IndexError, exceptions.AuthorizationFailure):
raise e
request_security_token = self.xml_to_str(self.prepared_request)
try:
response = session.post(
url=self.identity_provider_url, headers=self.HEADER_SOAP,
data=request_security_token, authenticated=False)
except exceptions.InternalServerError as e:
reason = _get_failure(e)
raise exceptions.AuthorizationFailure(reason)
msg = ('Error parsing XML returned from '
'the ADFS Identity Provider, reason: %s')
self.adfs_token = self.str_to_xml(response.content, msg)
def _prepare_sp_request(self):
"""Prepare ADFS Security Token to be sent to the Service Provider.
The method works as follows:
* Extract SAML2 assertion from the ADFS Security Token.
* Replace namespaces
* urlencode assertion
* concatenate static string with the encoded assertion
"""
assertion = self.adfs_token.xpath(
self.ADFS_ASSERTION_XPATH, namespaces=self.ADFS_TOKEN_NAMESPACES)
assertion = self._first(assertion)
assertion = self.xml_to_str(assertion)
# TODO(marek-denis): Ideally no string replacement should occur.
# Unfortunately lxml doesn't allow for namespaces changing in-place and
# probably the only solution good for now is to build the assertion
# from scratch and reuse values from the adfs security token.
assertion = assertion.replace(
b'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
b'http://schemas.xmlsoap.org/ws/2005/02/trust')
encoded_assertion = urllib.parse.quote(assertion)
self.encoded_assertion = 'wa=wsignin1.0&wresult=' + encoded_assertion
def _send_assertion_to_service_provider(self, session):
"""Send prepared assertion to a service provider.
As the assertion doesn't contain a protected resource, the value from
the ``location`` header is not valid and we should not let the Session
object get redirected there. The aim of this call is to get a cookie in
the response which is required for entering a protected endpoint.
:param session : a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:raises: Corresponding HTTP error exception
"""
session.post(
url=self.service_provider_endpoint, data=self.encoded_assertion,
headers=self.HEADER_X_FORM, redirect=False, authenticated=False)
def _access_service_provider(self, session):
"""Access protected endpoint and fetch unscoped token.
After federated authentication workflow a protected endpoint should be
accessible with the session object. The access is granted basing on the
cookies stored within the session object. If, for some reason no
cookies are present (quantity test) it means something went wrong and
user will not be able to fetch an unscoped token. In that case an
``exceptions.AuthorizationFailure` exception is raised and no HTTP call
is even made.
:param session : a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:raises keystoneauth1.exceptions.AuthorizationFailure: in case session
object has empty cookie jar.
"""
if self._cookies(session) is False:
raise exceptions.AuthorizationFailure(
"Session object doesn't contain a cookie, therefore you are "
"not allowed to enter the Identity Provider's protected area.")
self.authenticated_response = session.get(self.federated_token_url,
authenticated=False)
def get_unscoped_auth_ref(self, session, *kwargs):
"""Retrieve unscoped token after authentcation with ADFS server.
This is a multistep process:
* Prepare ADFS Request Securty Token -
build an etree.XML object filling certain attributes with proper user
credentials, created/expires dates (ticket is be valid for 120
seconds as currently we don't handle reusing ADFS issued security
tokens).
* Send ADFS Security token to the ADFS server. Step handled by
* Receive and parse security token, extract actual SAML assertion and
prepare a request addressed for the Service Provider endpoint.
This also includes changing namespaces in the XML document. Step
handled by ``ADFSPassword._prepare_sp_request()`` method.
* Send prepared assertion to the Service Provider endpoint. Usually
the server will respond with HTTP 301 code which should be ignored as
the 'location' header doesn't contain protected area. The goal of
this operation is fetching the session cookie which later allows for
accessing protected URL endpoints. Step handed by
``ADFSPassword._send_assertion_to_service_provider()`` method.
* Once the session cookie is issued, the protected endpoint can be
accessed and an unscoped token can be retrieved. Step handled by
``ADFSPassword._access_service_provider()`` method.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: AccessInfo
:rtype: :py:class:`keystoneauth1.access.AccessInfo`
"""
self._prepare_adfs_request()
self._get_adfs_security_token(session)
self._prepare_sp_request()
self._send_assertion_to_service_provider(session)
self._access_service_provider(session)
return access.create(resp=self.authenticated_response)

View File

@ -1,98 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
try:
from lxml import etree
except ImportError:
etree = None
from keystoneauth1 import exceptions
from keystoneauth1.identity import v3
class _Saml2TokenAuthMethod(v3.AuthMethod):
_method_parameters = []
def get_auth_data(self, session, auth, headers, **kwargs):
raise exceptions.MethodNotImplemented('This method should never '
'be called')
class BaseSAMLPlugin(v3.FederationBaseAuth):
HTTP_MOVED_TEMPORARILY = 302
HTTP_SEE_OTHER = 303
_auth_method_class = _Saml2TokenAuthMethod
def __init__(self, auth_url,
identity_provider, identity_provider_url,
username, password, protocol,
**kwargs):
"""Class constructor accepting following parameters.
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: Name of the Identity Provider the client
will authenticate against. This parameter
will be used to build a dynamic URL used to
obtain unscoped OpenStack token.
:type identity_provider: string
:param identity_provider_url: An Identity Provider URL, where the
SAML2 auhentication request will be
sent.
:type identity_provider_url: string
:param username: User's login
:type username: string
:param password: User's password
:type password: string
:param protocol: Protocol to be used for the authentication.
The name must be equal to one configured at the
keystone sp side. This value is used for building
dynamic authentication URL.
Typical value would be: saml2
:type protocol: string
"""
super(BaseSAMLPlugin, self).__init__(
auth_url=auth_url, identity_provider=identity_provider,
protocol=protocol,
**kwargs)
self.identity_provider_url = identity_provider_url
self.username = username
self.password = password
@staticmethod
def _first(_list):
if len(_list) != 1:
raise IndexError('Only single element list is acceptable')
return _list[0]
@staticmethod
def str_to_xml(content, msg=None, include_exc=True):
try:
return etree.XML(content)
except etree.XMLSyntaxError as e:
if not msg:
msg = str(e)
else:
msg = msg % e if include_exc else msg
raise exceptions.AuthorizationFailure(msg)
@staticmethod
def xml_to_str(content, **kwargs):
return etree.tostring(content, **kwargs)

View File

@ -1,301 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
try:
from lxml import etree
except ImportError:
etree = None
import requests
import requests.auth
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.identity import v3
_PAOS_NAMESPACE = 'urn:liberty:paos:2003-08'
_ECP_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'
_PAOS_HEADER = 'application/vnd.paos+xml'
_PAOS_VER = 'ver="%s";"%s"' % (_PAOS_NAMESPACE, _ECP_NAMESPACE)
_XML_NAMESPACES = {
'ecp': _ECP_NAMESPACE,
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
'paos': _PAOS_NAMESPACE,
}
_XBASE = '/S:Envelope/S:Header/'
_XPATH_SP_RELAY_STATE = '//ecp:RelayState'
_XPATH_SP_CONSUMER_URL = _XBASE + 'paos:Request/@responseConsumerURL'
_XPATH_IDP_CONSUMER_URL = _XBASE + 'ecp:Response/@AssertionConsumerServiceURL'
_SOAP_FAULT = """
<S: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>
"""
class SamlException(Exception):
"""Base SAML plugin exception."""
class InvalidResponse(SamlException):
"""Invalid Response from SAML authentication."""
class ConsumerMismatch(SamlException):
"""The SP and IDP consumers do not match."""
def _response_xml(response, name):
try:
return etree.XML(response.content)
except etree.XMLSyntaxError as e:
msg = 'SAML2: Error parsing XML returned from %s: %s' % (name, e)
raise InvalidResponse(msg)
def _str_from_xml(xml, path):
l = xml.xpath(path, namespaces=_XML_NAMESPACES)
if len(l) != 1:
raise IndexError('%s should provide a single element list' % path)
return l[0]
class _SamlAuth(requests.auth.AuthBase):
"""A generic SAML ECP plugin for requests.
This is a multi-step process including multiple HTTP requests.
Authentication consists of:
* HTTP GET request to the Service Provider.
It's crucial to include HTTP headers indicating we are expecting SOAP
message in return. Service Provider should respond with a SOAP
message.
* HTTP POST request to the external Identity Provider service with
ECP extension enabled. The content sent is a header removed SOAP
message returned from the Service Provider. It's also worth noting
that ECP extension to the SAML2 doesn't define authentication method.
The most popular is HttpBasicAuth with just user and password.
Other possibilities could be X509 certificates or Kerberos.
Upon successful authentication the user should receive a SAML2
assertion.
* HTTP POST request again to the Service Provider. The body of the
request includes SAML2 assertion issued by a trusted Identity
Provider. The request should be sent to the Service Provider
consumer url specified in the SAML2 assertion.
Providing the authentication was successful and both Service Provider
and Identity Providers are trusted to each other, the Service
Provider will issue an unscoped token with a list of groups the
federated user is a member of.
"""
def __init__(self, identity_provider_url, requests_auth):
super(_SamlAuth, self).__init__()
self.identity_provider_url = identity_provider_url
self.requests_auth = requests_auth
def __call__(self, request):
try:
accept = request.headers['Accept']
except KeyError:
request.headers['Accept'] = _PAOS_HEADER
else:
request.headers['Accept'] = ','.join([accept, _PAOS_HEADER])
request.headers['PAOS'] = _PAOS_VER
request.register_hook('response', self._handle_response)
return request
def _handle_response(self, response, **kwargs):
if (response.status_code == 200 and
response.headers.get('Content-Type') == _PAOS_HEADER):
response = self._ecp_retry(response, **kwargs)
return response
def _ecp_retry(self, sp_response, **kwargs):
history = [sp_response]
def send(*send_args, **send_kwargs):
req = requests.Request(*send_args, **send_kwargs)
return sp_response.connection.send(req.prepare(), **kwargs)
authn_request = _response_xml(sp_response, 'Service Provider')
relay_state = _str_from_xml(authn_request, _XPATH_SP_RELAY_STATE)
sp_consumer_url = _str_from_xml(authn_request, _XPATH_SP_CONSUMER_URL)
authn_request.remove(authn_request[0])
idp_response = send('POST',
self.identity_provider_url,
headers={'Content-type': 'text/xml'},
data=etree.tostring(authn_request),
auth=self.requests_auth)
history.append(idp_response)
authn_response = _response_xml(idp_response, 'Identity Provider')
idp_consumer_url = _str_from_xml(authn_response,
_XPATH_IDP_CONSUMER_URL)
if sp_consumer_url != idp_consumer_url:
# send fault message to the SP, discard the response
send('POST',
sp_consumer_url,
data=_SOAP_FAULT,
headers={'Content-Type': _PAOS_HEADER})
# prepare error message and raise an exception.
msg = ('Consumer URLs from Service Provider %(service_provider)s '
'%(sp_consumer_url)s and Identity Provider '
'%(identity_provider)s %(idp_consumer_url)s are not equal')
msg = msg % {
'service_provider': sp_response.request.url,
'sp_consumer_url': sp_consumer_url,
'identity_provider': self.identity_provider_url,
'idp_consumer_url': idp_consumer_url
}
raise ConsumerMismatch(msg)
authn_response[0][0] = relay_state
# idp_consumer_url is the URL on the SP that handles the ECP body
# returned and creates an authenticated session.
final_resp = send('POST',
idp_consumer_url,
headers={'Content-Type': _PAOS_HEADER},
cookies=idp_response.cookies,
data=etree.tostring(authn_response))
history.append(final_resp)
# the SP should then redirect us back to the original URL to retry the
# original request.
if final_resp.status_code in (requests.codes.found,
requests.codes.other):
# Consume content and release the original connection
# to allow our new request to reuse the same one.
sp_response.content
sp_response.raw.release_conn()
req = sp_response.request.copy()
req.url = final_resp.headers['location']
req.prepare_cookies(final_resp.cookies)
final_resp = sp_response.connection.send(req, **kwargs)
history.append(final_resp)
final_resp.history.extend(history)
return final_resp
class _FederatedSaml(v3.FederationBaseAuth):
def __init__(self, auth_url, identity_provider, protocol,
identity_provider_url, **kwargs):
super(_FederatedSaml, self).__init__(auth_url,
identity_provider,
protocol,
**kwargs)
self.identity_provider_url = identity_provider_url
@abc.abstractmethod
def get_requests_auth(self):
raise NotImplementedError()
def get_unscoped_auth_ref(self, session, **kwargs):
method = self.get_requests_auth()
auth = _SamlAuth(self.identity_provider_url, method)
try:
resp = session.get(self.federated_token_url,
requests_auth=auth,
authenticated=False)
except SamlException as e:
raise exceptions.AuthorizationFailure(str(e))
return access.create(resp=resp)
class Password(_FederatedSaml):
r"""Implement authentication plugin for SAML2 protocol.
ECP stands for `Enhanced Client or Proxy` and is a SAML2 extension
for federated authentication where a transportation layer consists of
HTTP protocol and XML SOAP messages.
`Read for more information
<https://wiki.shibboleth.net/confluence/display/CONCEPT/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
:param protocol: Protocol to be used for the authentication.
The name must be equal to one configured at the
keystone sp side. This value is used for building
dynamic authentication URL.
Typical value would be: saml2
:type protocol: string
"""
def __init__(self, auth_url, identity_provider, protocol,
identity_provider_url, username, password, **kwargs):
super(Password, self).__init__(auth_url,
identity_provider,
protocol,
identity_provider_url,
**kwargs)
self.username = username
self.password = password
def get_requests_auth(self):
return requests.auth.HTTPBasicAuth(self.username, self.password)

View File

@ -1,89 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Kerberos authentication plugins.
.. warning::
This module requires installation of an extra package (`requests_kerberos`)
not installed by default. Without the extra package an import error will
occur. The extra package can be installed using::
$ pip install keystoneauth1[kerberos]
"""
try:
import requests_kerberos
except ImportError:
requests_kerberos = None
from keystoneauth1 import access
from keystoneauth1.identity import v3
from keystoneauth1.identity.v3 import federation
def _requests_auth():
# NOTE(jamielennox): request_kerberos.OPTIONAL allows the plugin to accept
# unencrypted error messages where we can't verify the origin of the error
# because we aren't authenticated.
return requests_kerberos.HTTPKerberosAuth(
mutual_authentication=requests_kerberos.OPTIONAL)
def _dependency_check():
if requests_kerberos is None:
raise ImportError("""
Using the kerberos authentication plugin requires installation of additional
packages. These can be installed with::
$ pip install keystoneauth1[kerberos]
""")
class KerberosMethod(v3.AuthMethod):
_method_parameters = []
def __init__(self, *args, **kwargs):
_dependency_check()
super(KerberosMethod, self).__init__(*args, **kwargs)
def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs):
# NOTE(jamielennox): request_kwargs is passed as a kwarg however it is
# required and always present when called from keystoneclient.
request_kwargs['requests_auth'] = _requests_auth()
return 'kerberos', {}
class Kerberos(v3.AuthConstructor):
_auth_method_class = KerberosMethod
class MappedKerberos(federation.FederationBaseAuth):
"""Authenticate using Kerberos via the keystone federation mechanisms.
This uses the OS-FEDERATION extension to gain an unscoped token and then
use the standard keystone auth process to scope that to any given project.
"""
def __init__(self, auth_url, identity_provider, protocol, **kwargs):
_dependency_check()
super(MappedKerberos, self).__init__(auth_url, identity_provider,
protocol, **kwargs)
def get_unscoped_auth_ref(self, session, **kwargs):
resp = session.get(self.federated_token_url,
requests_auth=_requests_auth(),
authenticated=False)
return access.create(body=resp.json(), resp=resp)

View File

@ -1,36 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras import kerberos
from keystoneauth1 import loading
class Kerberos(loading.BaseV3Loader):
@property
def plugin_class(self):
return kerberos.Kerberos
@property
def available(self):
return kerberos.requests_kerberos is not None
class MappedKerberos(loading.BaseFederationLoader):
@property
def plugin_class(self):
return kerberos.MappedKerberos
@property
def available(self):
return kerberos.requests_kerberos is not None

View File

@ -1,19 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras.oauth1 import v3
__all__ = ('V3OAuth1Method', 'V3OAuth1')
V3OAuth1Method = v3.OAuth1Method
V3OAuth1 = v3.OAuth1

View File

@ -1,47 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.extras.oauth1 import v3
from keystoneauth1 import loading
# NOTE(jamielennox): This is not a BaseV3Loader because we don't want to
# include the scoping options like project-id in the option list
class V3OAuth1(loading.BaseIdentityLoader):
@property
def plugin_class(self):
return v3.OAuth1
@property
def available(self):
return v3.oauth1 is not None
def get_options(self):
options = super(V3OAuth1, self).get_options()
options.extend([
loading.Opt('consumer-key',
required=True,
help='OAuth Consumer ID/Key'),
loading.Opt('consumer-secret',
required=True,
help='OAuth Consumer Secret'),
loading.Opt('access-key',
required=True,
help='OAuth Access Key'),
loading.Opt('access-secret',
required=True,
help='OAuth Access Secret'),
])
return options

View File

@ -1,80 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Oauth authentication plugins.
.. warning::
This module requires installation of an extra package (`oauthlib`)
not installed by default. Without the extra package an import error will
occur. The extra package can be installed using::
$ pip install keystoneauth['oauth1']
"""
import logging
try:
from oauthlib import oauth1
except ImportError:
oauth1 = None
from keystoneauth1.identity import v3
__all__ = ('OAuth1Method', 'OAuth1')
LOG = logging.getLogger(__name__)
class OAuth1Method(v3.AuthMethod):
"""OAuth based authentication method.
:param string consumer_key: Consumer key.
:param string consumer_secret: Consumer secret.
:param string access_key: Access token key.
:param string access_secret: Access token secret.
"""
_method_parameters = ['consumer_key', 'consumer_secret',
'access_key', 'access_secret']
def get_auth_data(self, session, auth, headers, **kwargs):
# Add the oauth specific content into the headers
oauth_client = oauth1.Client(self.consumer_key,
client_secret=self.consumer_secret,
resource_owner_key=self.access_key,
resource_owner_secret=self.access_secret,
signature_method=oauth1.SIGNATURE_HMAC)
o_url, o_headers, o_body = oauth_client.sign(auth.token_url,
http_method='POST')
headers.update(o_headers)
return 'oauth1', {}
def get_cache_id_elements(self):
return dict(('oauth1_%s' % p, getattr(self, p))
for p in self._method_parameters)
class OAuth1(v3.AuthConstructor):
_auth_method_class = OAuth1Method
def __init__(self, *args, **kwargs):
super(OAuth1, self).__init__(*args, **kwargs)
if self.has_scope_parameters:
LOG.warning('Scoping parameters such as a project were provided '
'to the OAuth1 plugin. Because OAuth1 access is '
'always scoped to a project these will be ignored by '
'the identity server')

View File

@ -1,41 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Produce keystone compliant structures for use in testing.
They are part of the public API because they may be relied upon to generate
test tokens for other clients. However they should never be imported into the
main client (keystoneauth or other). Because of this there may be dependencies
from this module on libraries that are only available in testing.
"""
from keystoneauth1.fixture.discovery import * # noqa
from keystoneauth1.fixture import exception
from keystoneauth1.fixture import v2
from keystoneauth1.fixture import v3
FixtureValidationError = exception.FixtureValidationError
V2Token = v2.Token
V3Token = v3.Token
V3FederationToken = v3.V3FederationToken
__all__ = ('DiscoveryList',
'FixtureValidationError',
'V2Discovery',
'V3Discovery',
'V2Token',
'V3Token',
'V3FederationToken',
'VersionDiscovery',
)

View File

@ -1,373 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from positional import positional
from keystoneauth1 import _utils as utils
__all__ = ('DiscoveryList',
'V2Discovery',
'V3Discovery',
'VersionDiscovery',
)
_DEFAULT_DAYS_AGO = 30
class DiscoveryBase(dict):
"""The basic version discovery structure.
All version discovery elements should have access to these values.
:param string id: The version id for this version entry.
:param string status: The status of this entry.
:param DateTime updated: When the API was last updated.
"""
@positional()
def __init__(self, id, status=None, updated=None):
super(DiscoveryBase, self).__init__()
self.id = id
self.status = status or 'stable'
self.updated = updated or utils.before_utcnow(days=_DEFAULT_DAYS_AGO)
@property
def id(self):
return self.get('id')
@id.setter
def id(self, value):
self['id'] = value
@property
def status(self):
return self.get('status')
@status.setter
def status(self, value):
self['status'] = value
@property
def links(self):
return self.setdefault('links', [])
@property
def updated_str(self):
return self.get('updated')
@updated_str.setter
def updated_str(self, value):
self['updated'] = value
@property
def updated(self):
return utils.parse_isotime(self.updated_str)
@updated.setter
def updated(self, value):
self.updated_str = value.isoformat()
@positional()
def add_link(self, href, rel='self', type=None):
link = {'href': href, 'rel': rel}
if type:
link['type'] = type
self.links.append(link)
return link
@property
def media_types(self):
return self.setdefault('media-types', [])
@positional(1)
def add_media_type(self, base, type):
mt = {'base': base, 'type': type}
self.media_types.append(mt)
return mt
class VersionDiscovery(DiscoveryBase):
"""A Version element for non-keystone services without microversions.
Provides some default values and helper methods for creating a microversion
endpoint version structure. Clients should use this instead of creating
their own structures.
:param string href: The url that this entry should point to.
:param string id: The version id that should be reported.
"""
def __init__(self, href, id, **kwargs):
super(VersionDiscovery, self).__init__(id, **kwargs)
self.add_link(href)
class MicroversionDiscovery(DiscoveryBase):
"""A Version element that has microversions.
Provides some default values and helper methods for creating a microversion
endpoint version structure. Clients should use this instead of creating
their own structures.
:param string href: The url that this entry should point to.
:param string id: The version id that should be reported.
:param string min_version: The minimum supported microversion. (optional)
:param string max_version: The maximum supported microversion. (optional)
"""
@positional()
def __init__(self, href, id, min_version='', max_version='', **kwargs):
super(MicroversionDiscovery, self).__init__(id, **kwargs)
self.add_link(href)
self.min_version = min_version
self.max_version = max_version
@property
def min_version(self):
return self.get('min_version')
@min_version.setter
def min_version(self, value):
self['min_version'] = value
@property
def max_version(self):
return self.get('max_version')
@max_version.setter
def max_version(self, value):
self['max_version'] = value
class NovaMicroversionDiscovery(DiscoveryBase):
"""A Version element with nova-style microversions.
Provides some default values and helper methods for creating a microversion
endpoint version structure. Clients should use this instead of creating
their own structures.
:param href: The url that this entry should point to.
:param string id: The version id that should be reported.
:param string min_version: The minimum microversion supported. (optional)
:param string version: The maximum microversion supported. (optional)
"""
@positional()
def __init__(self, href, id, min_version=None, version=None, **kwargs):
super(NovaMicroversionDiscovery, self).__init__(id, **kwargs)
self.add_link(href)
self.min_version = min_version
self.version = version
@property
def min_version(self):
return self.get('min_version')
@min_version.setter
def min_version(self, value):
if value:
self['min_version'] = value
@property
def version(self):
return self.get('version')
@version.setter
def version(self, value):
if value:
self['version'] = value
class V2Discovery(DiscoveryBase):
"""A Version element for a V2 identity service endpoint.
Provides some default values and helper methods for creating a v2.0
endpoint version structure. Clients should use this instead of creating
their own structures.
:param string href: The url that this entry should point to.
:param string id: The version id that should be reported. (optional)
Defaults to 'v2.0'.
:param bool html: Add HTML describedby links to the structure.
:param bool pdf: Add PDF describedby links to the structure.
"""
_DESC_URL = 'https://developer.openstack.org/api-ref/identity/v2/'
@positional()
def __init__(self, href, id=None, html=True, pdf=True, **kwargs):
super(V2Discovery, self).__init__(id or 'v2.0', **kwargs)
self.add_link(href)
if html:
self.add_html_description()
if pdf:
self.add_pdf_description()
def add_html_description(self):
"""Add the HTML described by links.
The standard structure includes a link to a HTML document with the
API specification. Add it to this entry.
"""
self.add_link(href=self._DESC_URL + 'content',
rel='describedby',
type='text/html')
def add_pdf_description(self):
"""Add the PDF described by links.
The standard structure includes a link to a PDF document with the
API specification. Add it to this entry.
"""
self.add_link(href=self._DESC_URL + 'identity-dev-guide-2.0.pdf',
rel='describedby',
type='application/pdf')
class V3Discovery(DiscoveryBase):
"""A Version element for a V3 identity service endpoint.
Provides some default values and helper methods for creating a v3
endpoint version structure. Clients should use this instead of creating
their own structures.
:param href: The url that this entry should point to.
:param string id: The version id that should be reported. (optional)
Defaults to 'v3.0'.
:param bool json: Add JSON media-type elements to the structure.
:param bool xml: Add XML media-type elements to the structure.
"""
@positional()
def __init__(self, href, id=None, json=True, xml=True, **kwargs):
super(V3Discovery, self).__init__(id or 'v3.0', **kwargs)
self.add_link(href)
if json:
self.add_json_media_type()
if xml:
self.add_xml_media_type()
def add_json_media_type(self):
"""Add the JSON media-type links.
The standard structure includes a list of media-types that the endpoint
supports. Add JSON to the list.
"""
self.add_media_type(base='application/json',
type='application/vnd.openstack.identity-v3+json')
def add_xml_media_type(self):
"""Add the XML media-type links.
The standard structure includes a list of media-types that the endpoint
supports. Add XML to the list.
"""
self.add_media_type(base='application/xml',
type='application/vnd.openstack.identity-v3+xml')
class DiscoveryList(dict):
"""A List of version elements.
Creates a correctly structured list of identity service endpoints for
use in testing with discovery.
:param string href: The url that this should be based at.
:param bool v2: Add a v2 element.
:param bool v3: Add a v3 element.
:param string v2_status: The status to use for the v2 element.
:param DateTime v2_updated: The update time to use for the v2 element.
:param bool v2_html: True to add a html link to the v2 element.
:param bool v2_pdf: True to add a pdf link to the v2 element.
:param string v3_status: The status to use for the v3 element.
:param DateTime v3_updated: The update time to use for the v3 element.
:param bool v3_json: True to add a html link to the v2 element.
:param bool v3_xml: True to add a pdf link to the v2 element.
"""
TEST_URL = 'http://keystone.host:5000/'
@positional(2)
def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None,
v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True,
v3_status=None, v3_updated=None, v3_json=True, v3_xml=True):
super(DiscoveryList, self).__init__(versions={'values': []})
href = href or self.TEST_URL
if v2:
v2_href = href.rstrip('/') + '/v2.0'
self.add_v2(v2_href, id=v2_id, status=v2_status,
updated=v2_updated, html=v2_html, pdf=v2_pdf)
if v3:
v3_href = href.rstrip('/') + '/v3'
self.add_v3(v3_href, id=v3_id, status=v3_status,
updated=v3_updated, json=v3_json, xml=v3_xml)
@property
def versions(self):
return self['versions']['values']
def add_version(self, version):
"""Add a new version structure to the list.
:param dict version: A new version structure to add to the list.
"""
self.versions.append(version)
def add_v2(self, href, **kwargs):
"""Add a v2 version to the list.
The parameters are the same as V2Discovery.
"""
obj = V2Discovery(href, **kwargs)
self.add_version(obj)
return obj
def add_v3(self, href, **kwargs):
"""Add a v3 version to the list.
The parameters are the same as V3Discovery.
"""
obj = V3Discovery(href, **kwargs)
self.add_version(obj)
return obj
def add_microversion(self, href, id, **kwargs):
"""Add a microversion version to the list.
The parameters are the same as MicroversionDiscovery.
"""
obj = MicroversionDiscovery(href=href, id=id, **kwargs)
self.add_version(obj)
return obj
def add_nova_microversion(self, href, id, **kwargs):
"""Add a nova microversion version to the list.
The parameters are the same as NovaMicroversionDiscovery.
"""
obj = NovaMicroversionDiscovery(href=href, id=id, **kwargs)
self.add_version(obj)
return obj

View File

@ -1,20 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class FixtureValidationError(Exception):
"""The token you created is not legitimate.
The data contained in the token that was generated is not valid and would
not have been returned from a keystone server. You should not do testing
with this token.
"""

View File

@ -1,60 +0,0 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Custom hooks for betamax and keystoneauth.
Module providing a set of hooks specially designed for
interacting with clouds and keystone authentication.
:author: Yolanda Robla
"""
import json
def mask_fixture_values(nested, prev_key):
for key, value in nested.items():
if isinstance(value, dict):
mask_fixture_values(value, key)
else:
if key in ('tenantName', 'username'):
nested[key] = 'dummy'
elif prev_key in ('user', 'project', 'tenant') and key == 'name':
nested[key] = 'dummy'
elif prev_key == 'domain' and key == 'id':
nested[key] = 'dummy'
elif key == 'password':
nested[key] = '********'
elif prev_key == 'token' and key in ('expires', 'expires_at'):
nested[key] = '9999-12-31T23:59:59Z'
def pre_record_hook(interaction, cassette):
"""Hook to mask saved data.
This hook will be triggered before saving the interaction, and
will perform two tasks:
- mask user, project and password in the saved data
- set token expiration time to an inifinite time.
"""
request_body = interaction.data['request']['body']
if request_body.get('string'):
parsed_content = json.loads(request_body['string'])
mask_fixture_values(parsed_content, None)
request_body['string'] = json.dumps(parsed_content)
response_body = interaction.data['response']['body']
if response_body.get('string'):
parsed_content = json.loads(response_body['string'])
mask_fixture_values(parsed_content, None)
response_body['string'] = json.dumps(parsed_content)

View File

@ -1,136 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""A fixture to wrap the session constructor for use with Betamax."""
from functools import partial
import betamax
import fixtures
import mock
import requests
from keystoneauth1.fixture import hooks
from keystoneauth1.fixture import serializer as yaml_serializer
from keystoneauth1 import session
class BetamaxFixture(fixtures.Fixture):
def __init__(self, cassette_name, cassette_library_dir=None,
serializer=None, record=False,
pre_record_hook=hooks.pre_record_hook,
serializer_name=None, request_matchers=None):
"""Configure Betamax for the test suite.
:param str cassette_name:
This is simply the name of the cassette without any file extension
or containing directory. For example, to generate
``keystoneauth1/tests/unit/data/example.yaml``, one would pass
only ``example``.
:param str cassette_library_dir:
This is the directory that will contain all cassette files. In
``keystoneauth1/tests/unit/data/example.yaml`` you would pass
``keystoneauth1/tests/unit/data/``.
:param serializer:
A class that implements the Serializer API in Betamax. See also:
https://betamax.readthedocs.io/en/latest/serializers.html
:param record:
The Betamax record mode to use. If ``False`` (the default), then
Betamax will not record anything. For more information about
record modes, see:
https://betamax.readthedocs.io/en/latest/record_modes.html
:param callable pre_record_hook:
Function or callable to use to perform some handling of the
request or response data prior to saving it to disk.
:param str serializer_name:
The name of a serializer already registered with Betamax to use
to handle cassettes. For example, if you want to use the default
Betamax serializer, you would pass ``'json'`` to this parameter.
:param list request_matchers:
The list of request matcher names to use with Betamax. Betamax's
default list is used if none are specified. See also:
https://betamax.readthedocs.io/en/latest/matchers.html
"""
self.cassette_library_dir = cassette_library_dir
self.record = record
self.cassette_name = cassette_name
if not (serializer or serializer_name):
serializer = yaml_serializer.YamlJsonSerializer
serializer_name = serializer.name
if serializer:
betamax.Betamax.register_serializer(serializer)
self.serializer = serializer
self._serializer_name = serializer_name
self.pre_record_hook = pre_record_hook
self.use_cassette_kwargs = {}
if request_matchers is not None:
self.use_cassette_kwargs['match_requests_on'] = request_matchers
@property
def serializer_name(self):
"""Determine the name of the selected serializer.
If a class was specified, use the name attribute to generate this,
otherwise, use the serializer_name parameter from ``__init__``.
:returns:
Name of the serializer
:rtype:
str
"""
if self.serializer:
return self.serializer.name
return self._serializer_name
def setUp(self):
super(BetamaxFixture, self).setUp()
self.mockpatch = mock.patch.object(
session, '_construct_session',
partial(_construct_session_with_betamax, self))
self.mockpatch.start()
# Unpatch during cleanup
self.addCleanup(self.mockpatch.stop)
def _construct_session_with_betamax(fixture, session_obj=None):
# NOTE(morganfainberg): This function should contain the logic of
# keystoneauth1.session._construct_session as it replaces the
# _construct_session function to apply betamax magic to the requests
# session object.
if not session_obj:
session_obj = requests.Session()
# Use TCPKeepAliveAdapter to fix bug 1323862
for scheme in list(session_obj.adapters.keys()):
session_obj.mount(scheme, session.TCPKeepAliveAdapter())
with betamax.Betamax.configure() as config:
config.before_record(callback=fixture.pre_record_hook)
fixture.recorder = betamax.Betamax(
session_obj, cassette_library_dir=fixture.cassette_library_dir)
record = 'none'
serializer = None
if fixture.record in ['once', 'all', 'new_episodes']:
record = fixture.record
serializer = fixture.serializer_name
fixture.recorder.use_cassette(fixture.cassette_name,
serialize_with=serializer,
record=record,
**fixture.use_cassette_kwargs)
fixture.recorder.start()
fixture.addCleanup(fixture.recorder.stop)
return session_obj

View File

@ -1,98 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""A serializer to emit YAML but with request body in nicely formatted JSON."""
import json
import os
import betamax.serializers.base
import six
import yaml
def _should_use_block(value):
for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029":
if c in value:
return True
return False
def _represent_scalar(self, tag, value, style=None):
if style is None:
if _should_use_block(value):
style = '|'
else:
style = self.default_style
node = yaml.representer.ScalarNode(tag, value, style=style)
if self.alias_key is not None:
self.represented_objects[self.alias_key] = node
return node
def _unicode_representer(dumper, uni):
node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni)
return node
def _indent_json(val):
if not val:
return ''
return json.dumps(
json.loads(val), indent=2,
separators=(',', ': '), sort_keys=False,
default=six.text_type)
def _is_json_body(interaction):
content_type = interaction['headers'].get('Content-Type', [])
return 'application/json' in content_type
class YamlJsonSerializer(betamax.serializers.base.BaseSerializer):
name = "yamljson"
@staticmethod
def generate_cassette_name(cassette_library_dir, cassette_name):
return os.path.join(
cassette_library_dir, "{name}.yaml".format(name=cassette_name))
def serialize(self, cassette_data):
# Reserialize internal json with indentation
for interaction in cassette_data['http_interactions']:
for key in ('request', 'response'):
if _is_json_body(interaction[key]):
interaction[key]['body']['string'] = _indent_json(
interaction[key]['body']['string'])
class MyDumper(yaml.Dumper):
"""Specialized Dumper which does nice blocks and unicode."""
yaml.representer.BaseRepresenter.represent_scalar = _represent_scalar
MyDumper.add_representer(six.text_type, _unicode_representer)
return yaml.dump(
cassette_data, Dumper=MyDumper, default_flow_style=False)
def deserialize(self, cassette_data):
try:
deserialized = yaml.safe_load(cassette_data)
except yaml.error.YAMLError:
deserialized = None
if deserialized is not None:
return deserialized
return {}

View File

@ -1,247 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
from keystoneauth1 import _utils
from keystoneauth1.fixture import exception
class _Service(dict):
def add_endpoint(self, public, admin=None, internal=None,
tenant_id=None, region=None, id=None):
data = {'tenantId': tenant_id or uuid.uuid4().hex,
'publicURL': public,
'adminURL': admin or public,
'internalURL': internal or public,
'region': region,
'id': id or uuid.uuid4().hex}
self.setdefault('endpoints', []).append(data)
return data
class Token(dict):
"""A V2 Keystone token that can be used for testing.
This object is designed to allow clients to generate a correct V2 token for
use in there test code. It should prevent clients from having to know the
correct token format and allow them to test the portions of token handling
that matter to them and not copy and paste sample.
"""
def __init__(self, token_id=None, expires=None, issued=None,
tenant_id=None, tenant_name=None, user_id=None,
user_name=None, trust_id=None, trustee_user_id=None,
audit_id=None, audit_chain_id=None):
super(Token, self).__init__()
self.token_id = token_id or uuid.uuid4().hex
self.user_id = user_id or uuid.uuid4().hex
self.user_name = user_name or uuid.uuid4().hex
self.audit_id = audit_id or uuid.uuid4().hex
if not issued:
issued = _utils.before_utcnow(minutes=2)
if not expires:
expires = issued + datetime.timedelta(hours=1)
try:
self.issued = issued
except (TypeError, AttributeError):
# issued should be able to be passed as a string so ignore
self.issued_str = issued
try:
self.expires = expires
except (TypeError, AttributeError):
# expires should be able to be passed as a string so ignore
self.expires_str = expires
if tenant_id or tenant_name:
self.set_scope(tenant_id, tenant_name)
if trust_id or trustee_user_id:
# the trustee_user_id will generally be the same as the user_id as
# the token is being issued to the trustee
self.set_trust(id=trust_id,
trustee_user_id=trustee_user_id or user_id)
if audit_chain_id:
self.audit_chain_id = audit_chain_id
@property
def root(self):
return self.setdefault('access', {})
@property
def _token(self):
return self.root.setdefault('token', {})
@property
def token_id(self):
return self._token['id']
@token_id.setter
def token_id(self, value):
self._token['id'] = value
@property
def expires_str(self):
return self._token['expires']
@expires_str.setter
def expires_str(self, value):
self._token['expires'] = value
@property
def expires(self):
return _utils.parse_isotime(self.expires_str)
@expires.setter
def expires(self, value):
self.expires_str = value.isoformat()
@property
def issued_str(self):
return self._token['issued_at']
@issued_str.setter
def issued_str(self, value):
self._token['issued_at'] = value
@property
def issued(self):
return _utils.parse_isotime(self.issued_str)
@issued.setter
def issued(self, value):
self.issued_str = value.isoformat()
@property
def _user(self):
return self.root.setdefault('user', {})
@property
def user_id(self):
return self._user['id']
@user_id.setter
def user_id(self, value):
self._user['id'] = value
@property
def user_name(self):
return self._user['name']
@user_name.setter
def user_name(self, value):
self._user['name'] = value
@property
def tenant_id(self):
return self._token.get('tenant', {}).get('id')
@tenant_id.setter
def tenant_id(self, value):
self._token.setdefault('tenant', {})['id'] = value
@property
def tenant_name(self):
return self._token.get('tenant', {}).get('name')
@tenant_name.setter
def tenant_name(self, value):
self._token.setdefault('tenant', {})['name'] = value
@property
def _metadata(self):
return self.root.setdefault('metadata', {})
@property
def trust_id(self):
return self.root.setdefault('trust', {}).get('id')
@trust_id.setter
def trust_id(self, value):
self.root.setdefault('trust', {})['id'] = value
@property
def trustee_user_id(self):
return self.root.setdefault('trust', {}).get('trustee_user_id')
@trustee_user_id.setter
def trustee_user_id(self, value):
self.root.setdefault('trust', {})['trustee_user_id'] = value
@property
def audit_id(self):
try:
return self._token.get('audit_ids', [])[0]
except IndexError:
return None
@audit_id.setter
def audit_id(self, value):
audit_chain_id = self.audit_chain_id
lval = [value] if audit_chain_id else [value, audit_chain_id]
self._token['audit_ids'] = lval
@property
def audit_chain_id(self):
try:
return self._token.get('audit_ids', [])[1]
except IndexError:
return None
@audit_chain_id.setter
def audit_chain_id(self, value):
self._token['audit_ids'] = [self.audit_id, value]
def validate(self):
scoped = 'tenant' in self.token
catalog = self.root.get('serviceCatalog')
if catalog and not scoped:
msg = 'You cannot have a service catalog on an unscoped token'
raise exception.FixtureValidationError(msg)
if scoped and not self.user.get('roles'):
msg = 'You must have roles on a token to scope it'
raise exception.FixtureValidationError(msg)
def add_role(self, name=None, id=None):
id = id or uuid.uuid4().hex
name = name or uuid.uuid4().hex
roles = self._user.setdefault('roles', [])
roles.append({'name': name})
self._metadata.setdefault('roles', []).append(id)
return {'id': id, 'name': name}
def add_service(self, type, name=None):
name = name or uuid.uuid4().hex
service = _Service(name=name, type=type)
self.root.setdefault('serviceCatalog', []).append(service)
return service
def set_scope(self, id=None, name=None):
self.tenant_id = id or uuid.uuid4().hex
self.tenant_name = name or uuid.uuid4().hex
def set_trust(self, id=None, trustee_user_id=None):
self.trust_id = id or uuid.uuid4().hex
self.trustee_user_id = trustee_user_id or uuid.uuid4().hex
def set_bind(self, name, data):
self._token.setdefault('bind', {})[name] = data

View File

@ -1,466 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
from keystoneauth1 import _utils
from keystoneauth1.fixture import exception
class _Service(dict):
"""One of the services that exist in the catalog.
You use this by adding a service to a token which returns an instance of
this object and then you can add_endpoints to the service.
"""
def add_endpoint(self, interface, url, region=None, id=None):
data = {'id': id or uuid.uuid4().hex,
'interface': interface,
'url': url,
'region': region,
'region_id': region}
self.setdefault('endpoints', []).append(data)
return data
def add_standard_endpoints(self, public=None, admin=None, internal=None,
region=None):
ret = []
if public:
ret.append(self.add_endpoint('public', public, region=region))
if admin:
ret.append(self.add_endpoint('admin', admin, region=region))
if internal:
ret.append(self.add_endpoint('internal', internal, region=region))
return ret
class Token(dict):
"""A V3 Keystone token that can be used for testing.
This object is designed to allow clients to generate a correct V3 token for
use in there test code. It should prevent clients from having to know the
correct token format and allow them to test the portions of token handling
that matter to them and not copy and paste sample.
"""
def __init__(self, expires=None, issued=None, user_id=None, user_name=None,
user_domain_id=None, user_domain_name=None, methods=None,
project_id=None, project_name=None, project_domain_id=None,
project_domain_name=None, domain_id=None, domain_name=None,
trust_id=None, trust_impersonation=None, trustee_user_id=None,
trustor_user_id=None, oauth_access_token_id=None,
oauth_consumer_id=None, audit_id=None, audit_chain_id=None,
is_admin_project=None, project_is_domain=None):
super(Token, self).__init__()
self.user_id = user_id or uuid.uuid4().hex
self.user_name = user_name or uuid.uuid4().hex
self.user_domain_id = user_domain_id or uuid.uuid4().hex
self.user_domain_name = user_domain_name or uuid.uuid4().hex
self.audit_id = audit_id or uuid.uuid4().hex
if not methods:
methods = ['password']
self.methods.extend(methods)
if not issued:
issued = _utils.before_utcnow(minutes=2)
try:
self.issued = issued
except (TypeError, AttributeError):
# issued should be able to be passed as a string so ignore
self.issued_str = issued
if not expires:
expires = self.issued + datetime.timedelta(hours=1)
try:
self.expires = expires
except (TypeError, AttributeError):
# expires should be able to be passed as a string so ignore
self.expires_str = expires
if (project_id or project_name or
project_domain_id or project_domain_name):
self.set_project_scope(id=project_id,
name=project_name,
domain_id=project_domain_id,
domain_name=project_domain_name,
is_domain=project_is_domain)
if domain_id or domain_name:
self.set_domain_scope(id=domain_id, name=domain_name)
if (trust_id or (trust_impersonation is not None) or
trustee_user_id or trustor_user_id):
self.set_trust_scope(id=trust_id,
impersonation=trust_impersonation,
trustee_user_id=trustee_user_id,
trustor_user_id=trustor_user_id)
if oauth_access_token_id or oauth_consumer_id:
self.set_oauth(access_token_id=oauth_access_token_id,
consumer_id=oauth_consumer_id)
if audit_chain_id:
self.audit_chain_id = audit_chain_id
if is_admin_project is not None:
self.is_admin_project = is_admin_project
@property
def root(self):
return self.setdefault('token', {})
@property
def expires_str(self):
return self.root.get('expires_at')
@expires_str.setter
def expires_str(self, value):
self.root['expires_at'] = value
@property
def expires(self):
return _utils.parse_isotime(self.expires_str)
@expires.setter
def expires(self, value):
self.expires_str = value.isoformat()
@property
def issued_str(self):
return self.root.get('issued_at')
@issued_str.setter
def issued_str(self, value):
self.root['issued_at'] = value
@property
def issued(self):
return _utils.parse_isotime(self.issued_str)
@issued.setter
def issued(self, value):
self.issued_str = value.isoformat()
@property
def _user(self):
return self.root.setdefault('user', {})
@property
def user_id(self):
return self._user.get('id')
@user_id.setter
def user_id(self, value):
self._user['id'] = value
@property
def user_name(self):
return self._user.get('name')
@user_name.setter
def user_name(self, value):
self._user['name'] = value
@property
def _user_domain(self):
return self._user.setdefault('domain', {})
@_user_domain.setter
def _user_domain(self, domain):
self._user['domain'] = domain
@property
def user_domain_id(self):
return self._user_domain.get('id')
@user_domain_id.setter
def user_domain_id(self, value):
self._user_domain['id'] = value
@property
def user_domain_name(self):
return self._user_domain.get('name')
@user_domain_name.setter
def user_domain_name(self, value):
self._user_domain['name'] = value
@property
def methods(self):
return self.root.setdefault('methods', [])
@property
def project_id(self):
return self.root.get('project', {}).get('id')
@project_id.setter
def project_id(self, value):
self.root.setdefault('project', {})['id'] = value
@property
def project_is_domain(self):
return self.root.get('is_domain')
@project_is_domain.setter
def project_is_domain(self, value):
self.root['is_domain'] = value
@property
def project_name(self):
return self.root.get('project', {}).get('name')
@project_name.setter
def project_name(self, value):
self.root.setdefault('project', {})['name'] = value
@property
def project_domain_id(self):
return self.root.get('project', {}).get('domain', {}).get('id')
@project_domain_id.setter
def project_domain_id(self, value):
project = self.root.setdefault('project', {})
project.setdefault('domain', {})['id'] = value
@property
def project_domain_name(self):
return self.root.get('project', {}).get('domain', {}).get('name')
@project_domain_name.setter
def project_domain_name(self, value):
project = self.root.setdefault('project', {})
project.setdefault('domain', {})['name'] = value
@property
def domain_id(self):
return self.root.get('domain', {}).get('id')
@domain_id.setter
def domain_id(self, value):
self.root.setdefault('domain', {})['id'] = value
@property
def domain_name(self):
return self.root.get('domain', {}).get('name')
@domain_name.setter
def domain_name(self, value):
self.root.setdefault('domain', {})['name'] = value
@property
def trust_id(self):
return self.root.get('OS-TRUST:trust', {}).get('id')
@trust_id.setter
def trust_id(self, value):
self.root.setdefault('OS-TRUST:trust', {})['id'] = value
@property
def trust_impersonation(self):
return self.root.get('OS-TRUST:trust', {}).get('impersonation')
@trust_impersonation.setter
def trust_impersonation(self, value):
self.root.setdefault('OS-TRUST:trust', {})['impersonation'] = value
@property
def trustee_user_id(self):
trust = self.root.get('OS-TRUST:trust', {})
return trust.get('trustee_user', {}).get('id')
@trustee_user_id.setter
def trustee_user_id(self, value):
trust = self.root.setdefault('OS-TRUST:trust', {})
trust.setdefault('trustee_user', {})['id'] = value
@property
def trustor_user_id(self):
trust = self.root.get('OS-TRUST:trust', {})
return trust.get('trustor_user', {}).get('id')
@trustor_user_id.setter
def trustor_user_id(self, value):
trust = self.root.setdefault('OS-TRUST:trust', {})
trust.setdefault('trustor_user', {})['id'] = value
@property
def oauth_access_token_id(self):
return self.root.get('OS-OAUTH1', {}).get('access_token_id')
@oauth_access_token_id.setter
def oauth_access_token_id(self, value):
self.root.setdefault('OS-OAUTH1', {})['access_token_id'] = value
@property
def oauth_consumer_id(self):
return self.root.get('OS-OAUTH1', {}).get('consumer_id')
@oauth_consumer_id.setter
def oauth_consumer_id(self, value):
self.root.setdefault('OS-OAUTH1', {})['consumer_id'] = value
@property
def audit_id(self):
try:
return self.root.get('audit_ids', [])[0]
except IndexError:
return None
@audit_id.setter
def audit_id(self, value):
audit_chain_id = self.audit_chain_id
lval = [value] if audit_chain_id else [value, audit_chain_id]
self.root['audit_ids'] = lval
@property
def audit_chain_id(self):
try:
return self.root.get('audit_ids', [])[1]
except IndexError:
return None
@audit_chain_id.setter
def audit_chain_id(self, value):
self.root['audit_ids'] = [self.audit_id, value]
@property
def role_ids(self):
return [r['id'] for r in self.root.get('roles', [])]
@property
def role_names(self):
return [r['name'] for r in self.root.get('roles', [])]
@property
def is_admin_project(self):
return self.root.get('is_admin_project')
@is_admin_project.setter
def is_admin_project(self, value):
self.root['is_admin_project'] = value
@is_admin_project.deleter
def is_admin_project(self):
self.root.pop('is_admin_project', None)
def validate(self):
project = self.root.get('project')
domain = self.root.get('domain')
trust = self.root.get('OS-TRUST:trust')
catalog = self.root.get('catalog')
roles = self.root.get('roles')
scoped = project or domain or trust
if sum((bool(project), bool(domain), bool(trust))) > 1:
msg = 'You cannot scope to multiple targets'
raise exception.FixtureValidationError(msg)
if catalog and not scoped:
msg = 'You cannot have a service catalog on an unscoped token'
raise exception.FixtureValidationError(msg)
if scoped and not self.user.get('roles'):
msg = 'You must have roles on a token to scope it'
raise exception.FixtureValidationError(msg)
if bool(scoped) != bool(roles):
msg = 'You must be scoped to have roles and vice-versa'
raise exception.FixtureValidationError(msg)
def add_role(self, name=None, id=None):
roles = self.root.setdefault('roles', [])
data = {'id': id or uuid.uuid4().hex,
'name': name or uuid.uuid4().hex}
roles.append(data)
return data
def add_service(self, type, name=None, id=None):
service = _Service(type=type, id=id or uuid.uuid4().hex)
if name:
service['name'] = name
self.root.setdefault('catalog', []).append(service)
return service
def set_project_scope(self, id=None, name=None, domain_id=None,
domain_name=None, is_domain=None):
self.project_id = id or uuid.uuid4().hex
self.project_name = name or uuid.uuid4().hex
self.project_domain_id = domain_id or uuid.uuid4().hex
self.project_domain_name = domain_name or uuid.uuid4().hex
if is_domain is not None:
self.project_is_domain = is_domain
def set_domain_scope(self, id=None, name=None):
self.domain_id = id or uuid.uuid4().hex
self.domain_name = name or uuid.uuid4().hex
def set_trust_scope(self, id=None, impersonation=False,
trustee_user_id=None, trustor_user_id=None):
self.trust_id = id or uuid.uuid4().hex
self.trust_impersonation = impersonation
self.trustee_user_id = trustee_user_id or uuid.uuid4().hex
self.trustor_user_id = trustor_user_id or uuid.uuid4().hex
def set_oauth(self, access_token_id=None, consumer_id=None):
self.oauth_access_token_id = access_token_id or uuid.uuid4().hex
self.oauth_consumer_id = consumer_id or uuid.uuid4().hex
@property
def service_providers(self):
return self.root.get('service_providers')
def add_service_provider(self, sp_id, sp_auth_url, sp_url):
_service_providers = self.root.setdefault('service_providers', [])
sp = {'id': sp_id, 'auth_url': sp_auth_url, 'sp_url': sp_url}
_service_providers.append(sp)
return sp
def set_bind(self, name, data):
self.root.setdefault('bind', {})[name] = data
class V3FederationToken(Token):
"""A V3 Keystone Federation token that can be used for testing.
Similar to V3Token, this object is designed to allow clients to generate
a correct V3 federation token for use in test code.
"""
FEDERATED_DOMAIN_ID = 'Federated'
def __init__(self, methods=None, identity_provider=None, protocol=None,
groups=None):
methods = methods or ['saml2']
super(V3FederationToken, self).__init__(methods=methods)
self._user_domain = {'id': V3FederationToken.FEDERATED_DOMAIN_ID}
self.add_federation_info_to_user(identity_provider, protocol, groups)
def add_federation_info_to_user(self, identity_provider=None,
protocol=None, groups=None):
data = {
"OS-FEDERATION": {
"identity_provider": identity_provider or uuid.uuid4().hex,
"protocol": protocol or uuid.uuid4().hex,
"groups": groups or [{"id": uuid.uuid4().hex}]
}
}
self._user.update(data)
return data

View File

@ -1,37 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""keystoneauth1's pep8 extensions.
In order to make the review process faster and easier for core devs we are
adding some keystoneauth1 specific pep8 checks. This will catch common
errors so that core devs don't have to.
"""
import re
def check_oslo_namespace_imports(logical_line, blank_before, filename):
oslo_namespace_imports = re.compile(
r"(((from)|(import))\s+oslo\.)|(from\s+oslo\s+import\s+)")
if re.match(oslo_namespace_imports, logical_line):
msg = ("K333: '%s' must be used instead of '%s'.") % (
logical_line.replace('oslo.', 'oslo_'),
logical_line)
yield(0, msg)
def factory(register):
register(check_oslo_namespace_imports)

View File

@ -1,69 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.identity import base
from keystoneauth1.identity import generic
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
from keystoneauth1.identity.v3 import oidc
BaseIdentityPlugin = base.BaseIdentityPlugin
V2Password = v2.Password
"""See :class:`keystoneauth1.identity.v2.Password`"""
V2Token = v2.Token
"""See :class:`keystoneauth1.identity.v2.Token`"""
V3Password = v3.Password
"""See :class:`keystoneauth1.identity.v3.Password`"""
V3Token = v3.Token
"""See :class:`keystoneauth1.identity.v3.Token`"""
Password = generic.Password
"""See :class:`keystoneauth1.identity.generic.Password`"""
Token = generic.Token
"""See :class:`keystoneauth1.identity.generic.Token`"""
V3OidcClientCredentials = oidc.OidcClientCredentials
"""See :class:`keystoneauth1.identity.v3.oidc.OidcClientCredentials`"""
V3OidcPassword = oidc.OidcPassword
"""See :class:`keystoneauth1.identity.v3.oidc.OidcPassword`"""
V3OidcAuthorizationCode = oidc.OidcAuthorizationCode
"""See :class:`keystoneauth1.identity.v3.oidc.OidcAuthorizationCode`"""
V3OidcAccessToken = oidc.OidcAccessToken
"""See :class:`keystoneauth1.identity.v3.oidc.OidcAccessToken`"""
V3TOTP = v3.TOTP
"""See :class:`keystoneauth1.identity.v3.TOTP`"""
V3TokenlessAuth = v3.TokenlessAuth
"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`"""
__all__ = ('BaseIdentityPlugin',
'Password',
'Token',
'V2Password',
'V2Token',
'V3Password',
'V3Token',
'V3OidcPassword',
'V3OidcAuthorizationCode',
'V3OidcAccessToken',
'V3TOTP',
'V3TokenlessAuth')

View File

@ -1,48 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from positional import positional
from keystoneauth1.identity import base
class AccessInfoPlugin(base.BaseIdentityPlugin):
"""A plugin that turns an existing AccessInfo object into a usable plugin.
There are cases where reuse of an auth_ref or AccessInfo object is
warranted such as from a cache, from auth_token middleware, or another
source.
Turn the existing access info object into an identity plugin. This plugin
cannot be refreshed as the AccessInfo object does not contain any
authorizing information.
:param auth_ref: the existing AccessInfo object.
:type auth_ref: keystoneauth1.access.AccessInfo
:param auth_url: the url where this AccessInfo was retrieved from. Required
if using the AUTH_INTERFACE with get_endpoint. (optional)
"""
@positional()
def __init__(self, auth_ref, auth_url=None):
super(AccessInfoPlugin, self).__init__(auth_url=auth_url,
reauthenticate=False)
self.auth_ref = auth_ref
def get_auth_ref(self, session, **kwargs):
return self.auth_ref
def invalidate(self):
# NOTE(jamielennox): Don't allow the default invalidation to occur
# because on next authentication request we will only get the same
# auth_ref object again.
return False

View File

@ -1,507 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import base64
import hashlib
import json
import threading
from positional import positional
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import access
from keystoneauth1 import discover
from keystoneauth1 import exceptions
from keystoneauth1 import plugin
LOG = utils.get_logger(__name__)
@six.add_metaclass(abc.ABCMeta)
class BaseIdentityPlugin(plugin.BaseAuthPlugin):
# we count a token as valid (not needing refreshing) if it is valid for at
# least this many seconds before the token expiry time
MIN_TOKEN_LIFE_SECONDS = 120
def __init__(self, auth_url=None, reauthenticate=True):
super(BaseIdentityPlugin, self).__init__()
self.auth_url = auth_url
self.auth_ref = None
self.reauthenticate = reauthenticate
self._discovery_cache = {}
self._lock = threading.Lock()
@abc.abstractmethod
def get_auth_ref(self, session, **kwargs):
"""Obtain a token from an OpenStack Identity Service.
This method is overridden by the various token version plugins.
This function should not be called independently and is expected to be
invoked via the do_authenticate function.
This function will be invoked if the AcessInfo object cached by the
plugin is not valid. Thus plugins should always fetch a new AccessInfo
when invoked. If you are looking to just retrieve the current auth
data then you should use get_access.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:raises keystoneauth1.exceptions.response.InvalidResponse:
The response returned wasn't appropriate.
:raises keystoneauth1.exceptions.http.HttpError:
An error from an invalid HTTP response.
:returns: Token access information.
:rtype: :class:`keystoneauth1.access.AccessInfo`
"""
def get_token(self, session, **kwargs):
"""Return a valid auth token.
If a valid token is not present then a new one will be fetched.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: A valid token.
:rtype: string
"""
return self.get_access(session).auth_token
def _needs_reauthenticate(self):
"""Return if the existing token needs to be re-authenticated.
The token should be refreshed if it is about to expire.
:returns: True if the plugin should fetch a new token. False otherwise.
"""
if not self.auth_ref:
# authentication was never fetched.
return True
if not self.reauthenticate:
# don't re-authenticate if it has been disallowed.
return False
if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS):
# if it's about to expire we should re-authenticate now.
return True
# otherwise it's fine and use the existing one.
return False
def get_access(self, session, **kwargs):
"""Fetch or return a current AccessInfo object.
If a valid AccessInfo is present then it is returned otherwise a new
one will be fetched.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:returns: Valid AccessInfo
:rtype: :class:`keystoneauth1.access.AccessInfo`
"""
# Hey Kids! Thread safety is important particularly in the case where
# a service is creating an admin style plugin that will then proceed
# to make calls from many threads. As a token expires all the threads
# will try and fetch a new token at once, so we want to ensure that
# only one thread tries to actually fetch from keystone at once.
with self._lock:
if self._needs_reauthenticate():
self.auth_ref = self.get_auth_ref(session)
return self.auth_ref
def invalidate(self):
"""Invalidate the current authentication data.
This should result in fetching a new token on next call.
A plugin may be invalidated if an Unauthorized HTTP response is
returned to indicate that the token may have been revoked or is
otherwise now invalid.
:returns: True if there was something that the plugin did to
invalidate. This means that it makes sense to try again. If
nothing happens returns False to indicate give up.
:rtype: bool
"""
if self.auth_ref:
self.auth_ref = None
return True
return False
def get_endpoint_data(self, session, service_type=None, interface=None,
region_name=None, service_name=None, allow={},
allow_version_hack=True, discover_versions=True,
skip_discovery=False, min_version=None,
max_version=None, endpoint_override=None, **kwargs):
"""Return a valid endpoint data for a service.
If a valid token is not present then a new one will be fetched using
the session and kwargs.
version, min_version and max_version can all be given either as a
string or a tuple.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param string service_type: The type of service to lookup the endpoint
for. This plugin will return None (failure)
if service_type is not provided.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference. Can also be
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
that the auth_url should be used instead of the
value in the catalog. (optional, defaults to public)
:param string region_name: The region the endpoint should exist in.
(optional)
:param string service_name: The name of the service in the catalog.
(optional)
:param dict allow: Extra filters to pass when discovering API
versions. (optional)
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
URLS to support older schemes.
(optional, default True)
:param bool discover_versions: Whether to get version metadata from
the version discovery document even
if it's not neccessary to fulfill the
major version request. (optional,
defaults to True)
:param bool skip_discovery: Whether to skip version discovery even
if a version has been given. This is useful
if endpoint_override or similar has been
given and grabbing additional information
about the endpoint is not useful.
:param min_version: The minimum version that is acceptable. Mutually
exclusive with version. If min_version is given
with no max_version it is as if max version is
'latest'. (optional)
:param max_version: The maximum version that is acceptable. Mutually
exclusive with version. If min_version is given
with no max_version it is as if max version is
'latest'. (optional)
:param str endpoint_override: URL to use instead of looking in the
catalog. Catalog lookup will be skipped,
but version discovery will be run.
Sets allow_version_hack to False
(optional)
:param kwargs: Ignored.
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: Valid EndpointData or None if not available.
:rtype: `keystoneauth1.discover.EndpointData` or None
"""
min_version, max_version = discover._normalize_version_args(
None, min_version, max_version)
# NOTE(jamielennox): if you specifically ask for requests to be sent to
# the auth url then we can ignore many of the checks. Typically if you
# are asking for the auth endpoint it means that there is no catalog to
# query however we still need to support asking for a specific version
# of the auth_url for generic plugins.
if interface is plugin.AUTH_INTERFACE:
endpoint_data = discover.EndpointData(
service_url=self.auth_url,
service_type=service_type or 'identity')
project_id = None
elif endpoint_override:
# TODO(mordred) Make a code path that will look for a
# matching entry in the catalog if the catalog
# exists and fill in the interface, region_name, etc.
# For now, just use any information the use has
# provided.
endpoint_data = discover.EndpointData(
catalog_url=endpoint_override,
interface=interface,
region_name=region_name,
service_name=service_name)
# Setting an endpoint_override then calling get_endpoint_data means
# you absolutely want the discovery info for the URL in question.
# There are no code flows where this will happen for any other
# reasons.
allow_version_hack = False
project_id = self.get_project_id(session)
else:
if not service_type:
LOG.warning('Plugin cannot return an endpoint without '
'knowing the service type that is required. Add '
'service_type to endpoint filtering data.')
return None
# It's possible for things higher in the stack, because of
# defaults, to explicitly pass None.
if not interface:
interface = 'public'
service_catalog = self.get_access(session).service_catalog
project_id = self.get_project_id(session)
# NOTE(mordred): service_catalog.url_data_for raises if it can't
# find a match, so this will always be a valid object.
endpoint_data = service_catalog.endpoint_data_for(
service_type=service_type,
interface=interface,
region_name=region_name,
service_name=service_name)
if not endpoint_data:
return None
if skip_discovery:
return endpoint_data
try:
return endpoint_data.get_versioned_data(
session,
project_id=project_id,
min_version=min_version,
max_version=max_version,
cache=self._discovery_cache,
discover_versions=discover_versions,
allow_version_hack=allow_version_hack, allow=allow)
except (exceptions.DiscoveryFailure,
exceptions.HttpError,
exceptions.ConnectionError):
# If a version was requested, we didn't find it, return
# None.
if max_version or min_version:
return None
# If one wasn't, then the endpoint_data we already have
# should be fine
return endpoint_data
def get_endpoint(self, session, service_type=None, interface=None,
region_name=None, service_name=None, version=None,
allow={}, allow_version_hack=True,
skip_discovery=False,
min_version=None, max_version=None,
**kwargs):
"""Return a valid endpoint for a service.
If a valid token is not present then a new one will be fetched using
the session and kwargs.
version, min_version and max_version can all be given either as a
string or a tuple.
Valid interface types: `public` or `publicURL`,
`internal` or `internalURL`,
`admin` or 'adminURL`
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param string service_type: The type of service to lookup the endpoint
for. This plugin will return None (failure)
if service_type is not provided.
:param interface: Type of endpoint. Can be a single value or a list
of values. If it's a list of values, they will be
looked for in order of preference. Can also be
`keystoneauth1.plugin.AUTH_INTERFACE` to indicate
that the auth_url should be used instead of the
value in the catalog. (optional, defaults to public)
:param string region_name: The region the endpoint should exist in.
(optional)
:param string service_name: The name of the service in the catalog.
(optional)
:param version: The minimum version number required for this
endpoint. (optional)
:param dict allow: Extra filters to pass when discovering API
versions. (optional)
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
URLS to support older schemes.
(optional, default True)
:param bool skip_discovery: Whether to skip version discovery even
if a version has been given. This is useful
if endpoint_override or similar has been
given and grabbing additional information
about the endpoint is not useful.
:param min_version: The minimum version that is acceptable. Mutually
exclusive with version. If min_version is given
with no max_version it is as if max version is
'latest'. (optional)
:param max_version: The maximum version that is acceptable. Mutually
exclusive with version. If min_version is given
with no max_version it is as if max version is
'latest'. (optional)
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: A valid endpoint URL or None if not available.
:rtype: string or None
"""
# Explode `version` into min_version and max_version - everything below
# here uses the latter rather than the former.
min_version, max_version = discover._normalize_version_args(
version, min_version, max_version)
# Set discover_versions to False since we're only going to return
# a URL. Fetching the microversion data would be needlessly
# expensive in the common case. However, discover_versions=False
# will still run discovery if the version requested is not the
# version in the catalog.
endpoint_data = self.get_endpoint_data(
session, service_type=service_type, interface=interface,
region_name=region_name, service_name=service_name,
allow=allow, min_version=min_version, max_version=max_version,
discover_versions=False, skip_discovery=skip_discovery,
allow_version_hack=allow_version_hack, **kwargs)
return endpoint_data.url if endpoint_data else None
def get_user_id(self, session, **kwargs):
return self.get_access(session).user_id
def get_project_id(self, session, **kwargs):
return self.get_access(session).project_id
def get_sp_auth_url(self, session, sp_id, **kwargs):
try:
return self.get_access(
session).service_providers.get_auth_url(sp_id)
except exceptions.ServiceProviderNotFound:
return None
def get_sp_url(self, session, sp_id, **kwargs):
try:
return self.get_access(
session).service_providers.get_sp_url(sp_id)
except exceptions.ServiceProviderNotFound:
return None
@positional()
def get_discovery(self, session, url, authenticated=None):
"""Return the discovery object for a URL.
Check the session and the plugin cache to see if we have already
performed discovery on the URL and if so return it, otherwise create
a new discovery object, cache it and return it.
This function is expected to be used by subclasses and should not
be needed by users.
:param session: A session object to discover with.
:type session: keystoneauth1.session.Session
:param str url: The url to lookup.
:param bool authenticated: Include a token in the discovery call.
(optional) Defaults to None (use a token
if a plugin is installed).
:raises keystoneauth1.exceptions.discovery.DiscoveryFailure:
if for some reason the lookup fails.
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:returns: A discovery object with the results of looking up that URL.
"""
return discover.get_discovery(session=session, url=url,
cache=self._discovery_cache,
authenticated=authenticated)
def get_cache_id_elements(self):
"""Get the elements for this auth plugin that make it unique.
As part of the get_cache_id requirement we need to determine what
aspects of this plugin and its values that make up the unique elements.
This should be overridden by plugins that wish to allow caching.
:returns: The unique attributes and values of this plugin.
:rtype: A flat dict with a str key and str or None value. This is
required as we feed these values into a hash. Pairs where the
value is None are ignored in the hashed id.
"""
raise NotImplementedError()
def get_cache_id(self):
"""Fetch an identifier that uniquely identifies the auth options.
The returned identifier need not be decomposable or otherwise provide
any way to recreate the plugin.
This string MUST change if any of the parameters that are used to
uniquely identity this plugin change. It should not change upon a
reauthentication of the plugin.
:returns: A unique string for the set of options
:rtype: str or None if this is unsupported or unavailable.
"""
try:
elements = self.get_cache_id_elements()
except NotImplementedError:
return None
hasher = hashlib.sha256()
for k, v in sorted(elements.items()):
if v is not None:
# NOTE(jamielennox): in python3 you need to pass bytes to hash
if isinstance(k, six.string_types):
k = k.encode('utf-8')
if isinstance(v, six.string_types):
v = v.encode('utf-8')
hasher.update(k)
hasher.update(v)
return base64.b64encode(hasher.digest()).decode('utf-8')
def get_auth_state(self):
"""Retrieve the current authentication state for the plugin.
Retrieve any internal state that represents the authenticated plugin.
This should not fetch any new data if it is not present.
:returns: a string that can be stored or None if there is no auth state
present in the plugin. This string can be reloaded with
set_auth_state to set the same authentication.
:rtype: str or None if no auth present.
"""
if self.auth_ref:
data = {'auth_token': self.auth_ref.auth_token,
'body': self.auth_ref._data}
return json.dumps(data)
def set_auth_state(self, data):
"""Install existing authentication state for a plugin.
Take the output of get_auth_state and install that authentication state
into the current authentication plugin.
"""
if data:
auth_data = json.loads(data)
self.auth_ref = access.create(body=auth_data['body'],
auth_token=auth_data['auth_token'])
else:
self.auth_ref = None

View File

@ -1,21 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.identity.generic.base import BaseGenericPlugin # noqa
from keystoneauth1.identity.generic.password import Password # noqa
from keystoneauth1.identity.generic.token import Token # noqa
__all__ = ('BaseGenericPlugin',
'Password',
'Token',
)

View File

@ -1,215 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
import six.moves.urllib.parse as urlparse
from keystoneauth1 import _utils as utils
from keystoneauth1 import discover
from keystoneauth1 import exceptions
from keystoneauth1.identity import base
LOG = utils.get_logger(__name__)
@six.add_metaclass(abc.ABCMeta)
class BaseGenericPlugin(base.BaseIdentityPlugin):
"""An identity plugin that is not version dependent.
Internally we will construct a version dependent plugin with the resolved
URL and then proxy all calls from the base plugin to the versioned one.
"""
def __init__(self, auth_url,
tenant_id=None,
tenant_name=None,
project_id=None,
project_name=None,
project_domain_id=None,
project_domain_name=None,
domain_id=None,
domain_name=None,
trust_id=None,
default_domain_id=None,
default_domain_name=None,
reauthenticate=True):
super(BaseGenericPlugin, self).__init__(auth_url=auth_url,
reauthenticate=reauthenticate)
self._project_id = project_id or tenant_id
self._project_name = project_name or tenant_name
self._project_domain_id = project_domain_id
self._project_domain_name = project_domain_name
self._domain_id = domain_id
self._domain_name = domain_name
self._trust_id = trust_id
self._default_domain_id = default_domain_id
self._default_domain_name = default_domain_name
self._plugin = None
@abc.abstractmethod
def create_plugin(self, session, version, url, raw_status=None):
"""Create a plugin from the given parameters.
This function will be called multiple times with the version and url
of a potential endpoint. If a plugin can be constructed that fits the
params then it should return it. If not return None and then another
call will be made with other available URLs.
:param session: A session object.
:type session: keystoneauth1.session.Session
:param tuple version: A tuple of the API version at the URL.
:param str url: The base URL for this version.
:param str raw_status: The status that was in the discovery field.
:returns: A plugin that can match the parameters or None if nothing.
"""
return None
@property
def _has_domain_scope(self):
"""Are there domain parameters.
Domain parameters are v3 only so returns if any are set.
:returns: True if a domain parameter is set, false otherwise.
"""
return any([self._domain_id, self._domain_name,
self._project_domain_id, self._project_domain_name])
@property
def _v2_params(self):
"""Return the parameters that are common to v2 plugins."""
return {'trust_id': self._trust_id,
'tenant_id': self._project_id,
'tenant_name': self._project_name,
'reauthenticate': self.reauthenticate}
@property
def _v3_params(self):
"""Return the parameters that are common to v3 plugins."""
return {'trust_id': self._trust_id,
'project_id': self._project_id,
'project_name': self._project_name,
'project_domain_id': self.project_domain_id,
'project_domain_name': self.project_domain_name,
'domain_id': self._domain_id,
'domain_name': self._domain_name,
'reauthenticate': self.reauthenticate}
@property
def project_domain_id(self):
return self._project_domain_id or self._default_domain_id
@project_domain_id.setter
def project_domain_id(self, value):
self._project_domain_id = value
@property
def project_domain_name(self):
return self._project_domain_name or self._default_domain_name
@project_domain_name.setter
def project_domain_name(self, value):
self._project_domain_name = value
def _do_create_plugin(self, session):
plugin = None
try:
disc = self.get_discovery(session,
self.auth_url,
authenticated=False)
except (exceptions.DiscoveryFailure,
exceptions.HttpError,
exceptions.ConnectionError):
LOG.warning('Failed to discover available identity versions when '
'contacting %s. Attempting to parse version from URL.',
self.auth_url)
url_parts = urlparse.urlparse(self.auth_url)
path = url_parts.path.lower()
if path.startswith('/v2.0'):
if self._has_domain_scope:
raise exceptions.DiscoveryFailure(
'Cannot use v2 authentication with domain scope')
plugin = self.create_plugin(session, (2, 0), self.auth_url)
elif path.startswith('/v3'):
plugin = self.create_plugin(session, (3, 0), self.auth_url)
else:
# NOTE(jamielennox): version_data is always in oldest to newest
# order. This is fine normally because we explicitly skip v2 below
# if there is domain data present. With default_domain params
# though we want a v3 plugin if available and fall back to v2 so we
# have to process in reverse order. FIXME(jamielennox): if we ever
# go for another version we should reverse this logic as we always
# want to favour the newest available version.
reverse = self._default_domain_id or self._default_domain_name
disc_data = disc.version_data(reverse=bool(reverse))
v2_with_domain_scope = False
for data in disc_data:
version = data['version']
if (discover.version_match((2,), version) and
self._has_domain_scope):
# NOTE(jamielennox): if there are domain parameters there
# is no point even trying against v2 APIs.
v2_with_domain_scope = True
continue
plugin = self.create_plugin(session,
version,
data['url'],
raw_status=data['raw_status'])
if plugin:
break
if not plugin and v2_with_domain_scope:
raise exceptions.DiscoveryFailure(
'Cannot use v2 authentication with domain scope')
if plugin:
return plugin
# so there were no URLs that i could use for auth of any version.
raise exceptions.DiscoveryFailure('Could not determine a suitable URL '
'for the plugin')
def get_auth_ref(self, session, **kwargs):
if not self._plugin:
self._plugin = self._do_create_plugin(session)
return self._plugin.get_auth_ref(session, **kwargs)
def get_cache_id_elements(self, _implemented=False):
# NOTE(jamielennox): implemented here is just a way to make sure that
# something overrides this method. We don't want the base
# implementation to respond with a dict without the subclass modifying
# it to add their own data in case the subclass doesn't support caching
if not _implemented:
raise NotImplemented()
return {'auth_url': self.auth_url,
'project_id': self._project_id,
'project_name': self._project_name,
'project_domain_id': self.project_domain_id,
'project_domain_name': self.project_domain_name,
'domain_id': self._domain_id,
'domain_name': self._domain_name,
'trust_id': self._trust_id}

View File

@ -1,90 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from positional import positional
from keystoneauth1 import discover
from keystoneauth1.identity.generic import base
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
class Password(base.BaseGenericPlugin):
"""A common user/password authentication plugin.
:param string username: Username for authentication.
:param string user_id: User ID for authentication.
:param string password: Password for authentication.
:param string user_domain_id: User's domain ID for authentication.
:param string user_domain_name: User's domain name for authentication.
"""
@positional()
def __init__(self, auth_url, username=None, user_id=None, password=None,
user_domain_id=None, user_domain_name=None, **kwargs):
super(Password, self).__init__(auth_url=auth_url, **kwargs)
self._username = username
self._user_id = user_id
self._password = password
self._user_domain_id = user_domain_id
self._user_domain_name = user_domain_name
def create_plugin(self, session, version, url, raw_status=None):
if discover.version_match((2,), version):
if self._user_domain_id or self._user_domain_name:
return None
return v2.Password(auth_url=url,
user_id=self._user_id,
username=self._username,
password=self._password,
**self._v2_params)
elif discover.version_match((3,), version):
u_domain_id = self._user_domain_id or self._default_domain_id
u_domain_name = self._user_domain_name or self._default_domain_name
return v3.Password(auth_url=url,
user_id=self._user_id,
username=self._username,
user_domain_id=u_domain_id,
user_domain_name=u_domain_name,
password=self._password,
**self._v3_params)
@property
def user_domain_id(self):
return self._user_domain_id or self._default_domain_id
@user_domain_id.setter
def user_domain_id(self, value):
self._user_domain_id = value
@property
def user_domain_name(self):
return self._user_domain_name or self._default_domain_name
@user_domain_name.setter
def user_domain_name(self, value):
self._user_domain_name = value
def get_cache_id_elements(self):
elements = super(Password, self).get_cache_id_elements(
_implemented=True)
elements['username'] = self._username
elements['user_id'] = self._user_id
elements['password'] = self._password
elements['user_domain_id'] = self.user_domain_id
elements['user_domain_name'] = self.user_domain_name
return elements

View File

@ -1,39 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import discover
from keystoneauth1.identity.generic import base
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
class Token(base.BaseGenericPlugin):
"""Generic token auth plugin.
:param string token: Token for authentication.
"""
def __init__(self, auth_url, token=None, **kwargs):
super(Token, self).__init__(auth_url, **kwargs)
self._token = token
def create_plugin(self, session, version, url, raw_status=None):
if discover.version_match((2,), version):
return v2.Token(url, self._token, **self._v2_params)
elif discover.version_match((3,), version):
return v3.Token(url, self._token, **self._v3_params)
def get_cache_id_elements(self):
elements = super(Token, self).get_cache_id_elements(_implemented=True)
elements['token'] = self._token
return elements

View File

@ -1,178 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
from positional import positional
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.identity import base
_logger = utils.get_logger(__name__)
@six.add_metaclass(abc.ABCMeta)
class Auth(base.BaseIdentityPlugin):
"""Identity V2 Authentication Plugin.
:param string auth_url: Identity service endpoint for authorization.
:param string trust_id: Trust ID for trust scoping.
:param string tenant_id: Tenant ID for project scoping.
:param string tenant_name: Tenant name for project scoping.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
"""
@positional()
def __init__(self, auth_url,
trust_id=None,
tenant_id=None,
tenant_name=None,
reauthenticate=True):
super(Auth, self).__init__(auth_url=auth_url,
reauthenticate=reauthenticate)
self.trust_id = trust_id
self.tenant_id = tenant_id
self.tenant_name = tenant_name
def get_auth_ref(self, session, **kwargs):
headers = {'Accept': 'application/json'}
url = self.auth_url.rstrip('/') + '/tokens'
params = {'auth': self.get_auth_data(headers)}
if self.tenant_id:
params['auth']['tenantId'] = self.tenant_id
elif self.tenant_name:
params['auth']['tenantName'] = self.tenant_name
if self.trust_id:
params['auth']['trust_id'] = self.trust_id
_logger.debug('Making authentication request to %s', url)
resp = session.post(url, json=params, headers=headers,
authenticated=False, log=False)
try:
resp_data = resp.json()
except ValueError:
raise exceptions.InvalidResponse(response=resp)
if 'access' not in resp_data:
raise exceptions.InvalidResponse(response=resp)
return access.AccessInfoV2(resp_data)
@abc.abstractmethod
def get_auth_data(self, headers=None):
"""Return the authentication section of an auth plugin.
:param dict headers: The headers that will be sent with the auth
request if a plugin needs to add to them.
:return: A dict of authentication data for the auth type.
:rtype: dict
"""
@property
def has_scope_parameters(self):
"""Return true if parameters can be used to create a scoped token."""
return self.tenant_id or self.tenant_name or self.trust_id
_NOT_PASSED = object()
class Password(Auth):
"""A plugin for authenticating with a username and password.
A username or user_id must be provided.
:param string auth_url: Identity service endpoint for authorization.
:param string username: Username for authentication.
:param string password: Password for authentication.
:param string user_id: User ID for authentication.
:param string trust_id: Trust ID for trust scoping.
:param string tenant_id: Tenant ID for tenant scoping.
:param string tenant_name: Tenant name for tenant scoping.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
:raises TypeError: if a user_id or username is not provided.
"""
@positional(4)
def __init__(self, auth_url, username=_NOT_PASSED, password=None,
user_id=_NOT_PASSED, **kwargs):
super(Password, self).__init__(auth_url, **kwargs)
if username is _NOT_PASSED and user_id is _NOT_PASSED:
msg = 'You need to specify either a username or user_id'
raise TypeError(msg)
if username is _NOT_PASSED:
username = None
if user_id is _NOT_PASSED:
user_id = None
self.user_id = user_id
self.username = username
self.password = password
def get_auth_data(self, headers=None):
auth = {'password': self.password}
if self.username:
auth['username'] = self.username
elif self.user_id:
auth['userId'] = self.user_id
return {'passwordCredentials': auth}
def get_cache_id_elements(self):
return {'username': self.username,
'user_id': self.user_id,
'password': self.password,
'auth_url': self.auth_url,
'tenant_id': self.tenant_id,
'tenant_name': self.tenant_name,
'trust_id': self.trust_id}
class Token(Auth):
"""A plugin for authenticating with an existing token.
:param string auth_url: Identity service endpoint for authorization.
:param string token: Existing token for authentication.
:param string tenant_id: Tenant ID for tenant scoping.
:param string tenant_name: Tenant name for tenant scoping.
:param string trust_id: Trust ID for trust scoping.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
"""
def __init__(self, auth_url, token, **kwargs):
super(Token, self).__init__(auth_url, **kwargs)
self.token = token
def get_auth_data(self, headers=None):
if headers is not None:
headers['X-Auth-Token'] = self.token
return {'token': {'id': self.token}}
def get_cache_id_elements(self):
return {'token': self.token,
'auth_url': self.auth_url,
'tenant_id': self.tenant_id,
'tenant_name': self.tenant_name,
'trust_id': self.trust_id}

View File

@ -1,46 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.identity.v3.base import * # noqa
from keystoneauth1.identity.v3.federation import * # noqa
from keystoneauth1.identity.v3.k2k import * # noqa
from keystoneauth1.identity.v3.oidc import * # noqa
from keystoneauth1.identity.v3.password import * # noqa
from keystoneauth1.identity.v3.token import * # noqa
from keystoneauth1.identity.v3.totp import * # noqa
from keystoneauth1.identity.v3.tokenless_auth import * # noqa
__all__ = ('Auth',
'AuthConstructor',
'AuthMethod',
'BaseAuth',
'FederationBaseAuth',
'Keystone2Keystone',
'Password',
'PasswordMethod',
'Token',
'TokenMethod',
'OidcAccessToken',
'OidcAuthorizationCode',
'OidcClientCredentials',
'OidcPassword',
'TOTPMethod',
'TOTP',
'TokenlessAuth')

View File

@ -1,282 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import json
from positional import positional
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.identity import base
_logger = utils.get_logger(__name__)
__all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth')
@six.add_metaclass(abc.ABCMeta)
class BaseAuth(base.BaseIdentityPlugin):
"""Identity V3 Authentication Plugin.
:param string auth_url: Identity service endpoint for authentication.
:param string trust_id: Trust ID for trust scoping.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
:param bool include_catalog: Include the service catalog in the returned
token. (optional) default True.
"""
@positional()
def __init__(self, auth_url,
trust_id=None,
domain_id=None,
domain_name=None,
project_id=None,
project_name=None,
project_domain_id=None,
project_domain_name=None,
reauthenticate=True,
include_catalog=True):
super(BaseAuth, self).__init__(auth_url=auth_url,
reauthenticate=reauthenticate)
self.trust_id = trust_id
self.domain_id = domain_id
self.domain_name = domain_name
self.project_id = project_id
self.project_name = project_name
self.project_domain_id = project_domain_id
self.project_domain_name = project_domain_name
self.include_catalog = include_catalog
@property
def token_url(self):
"""The full URL where we will send authentication data."""
return '%s/auth/tokens' % self.auth_url.rstrip('/')
@abc.abstractmethod
def get_auth_ref(self, session, **kwargs):
return None
@property
def has_scope_parameters(self):
"""Return true if parameters can be used to create a scoped token."""
return (self.domain_id or self.domain_name or
self.project_id or self.project_name or
self.trust_id)
class Auth(BaseAuth):
"""Identity V3 Authentication Plugin.
:param string auth_url: Identity service endpoint for authentication.
:param list auth_methods: A collection of methods to authenticate with.
:param string trust_id: Trust ID for trust scoping.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
:param bool include_catalog: Include the service catalog in the returned
token. (optional) default True.
:param bool unscoped: Force the return of an unscoped token. This will make
the keystone server return an unscoped token even if
a default_project_id is set for this user.
"""
def __init__(self, auth_url, auth_methods, **kwargs):
self.unscoped = kwargs.pop('unscoped', False)
super(Auth, self).__init__(auth_url=auth_url, **kwargs)
self.auth_methods = auth_methods
def get_auth_ref(self, session, **kwargs):
headers = {'Accept': 'application/json'}
body = {'auth': {'identity': {}}}
ident = body['auth']['identity']
rkwargs = {}
for method in self.auth_methods:
name, auth_data = method.get_auth_data(session,
self,
headers,
request_kwargs=rkwargs)
ident.setdefault('methods', []).append(name)
ident[name] = auth_data
if not ident:
raise exceptions.AuthorizationFailure(
'Authentication method required (e.g. password)')
mutual_exclusion = [bool(self.domain_id or self.domain_name),
bool(self.project_id or self.project_name),
bool(self.trust_id),
bool(self.unscoped)]
if sum(mutual_exclusion) > 1:
raise exceptions.AuthorizationFailure(
'Authentication cannot be scoped to multiple targets. Pick '
'one of: project, domain, trust or unscoped')
if self.domain_id:
body['auth']['scope'] = {'domain': {'id': self.domain_id}}
elif self.domain_name:
body['auth']['scope'] = {'domain': {'name': self.domain_name}}
elif self.project_id:
body['auth']['scope'] = {'project': {'id': self.project_id}}
elif self.project_name:
scope = body['auth']['scope'] = {'project': {}}
scope['project']['name'] = self.project_name
if self.project_domain_id:
scope['project']['domain'] = {'id': self.project_domain_id}
elif self.project_domain_name:
scope['project']['domain'] = {'name': self.project_domain_name}
elif self.trust_id:
body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}}
elif self.unscoped:
body['auth']['scope'] = 'unscoped'
# NOTE(jamielennox): we add nocatalog here rather than in token_url
# directly as some federation plugins require the base token_url
token_url = self.token_url
if not self.include_catalog:
token_url += '?nocatalog'
_logger.debug('Making authentication request to %s', token_url)
resp = session.post(token_url, json=body, headers=headers,
authenticated=False, log=False, **rkwargs)
try:
_logger.debug(json.dumps(resp.json()))
resp_data = resp.json()
except ValueError:
raise exceptions.InvalidResponse(response=resp)
if 'token' not in resp_data:
raise exceptions.InvalidResponse(response=resp)
return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'],
body=resp_data)
def get_cache_id_elements(self):
if not self.auth_methods:
return None
params = {'auth_url': self.auth_url,
'domain_id': self.domain_id,
'domain_name': self.domain_name,
'project_id': self.project_id,
'project_name': self.project_name,
'project_domain_id': self.project_domain_id,
'project_domain_name': self.project_domain_name,
'trust_id': self.trust_id}
for method in self.auth_methods:
try:
elements = method.get_cache_id_elements()
except NotImplementedError:
return None
params.update(elements)
return params
@six.add_metaclass(abc.ABCMeta)
class AuthMethod(object):
"""One part of a V3 Authentication strategy.
V3 Tokens allow multiple methods to be presented when authentication
against the server. Each one of these methods is implemented by an
AuthMethod.
Note: When implementing an AuthMethod use the method_parameters
and do not use positional arguments. Otherwise they can't be picked up by
the factory method and don't work as well with AuthConstructors.
"""
_method_parameters = []
def __init__(self, **kwargs):
for param in self._method_parameters:
setattr(self, param, kwargs.pop(param, None))
if kwargs:
msg = "Unexpected Attributes: %s" % ", ".join(kwargs.keys())
raise AttributeError(msg)
@classmethod
def _extract_kwargs(cls, kwargs):
"""Remove parameters related to this method from other kwargs."""
return dict([(p, kwargs.pop(p, None))
for p in cls._method_parameters])
@abc.abstractmethod
def get_auth_data(self, session, auth, headers, **kwargs):
"""Return the authentication section of an auth plugin.
:param session: The communication session.
:type session: keystoneauth1.session.Session
:param base.Auth auth: The auth plugin calling the method.
:param dict headers: The headers that will be sent with the auth
request if a plugin needs to add to them.
:return: The identifier of this plugin and a dict of authentication
data for the auth type.
:rtype: tuple(string, dict)
"""
def get_cache_id_elements(self):
"""Get the elements for this auth method that make it unique.
These elements will be used as part of the
:py:meth:`keystoneauth1.plugin.BaseIdentityPlugin.get_cache_id` to
allow caching of the auth plugin.
Plugins should override this if they want to allow caching of their
state.
To avoid collision or overrides the keys of the returned dictionary
should be prefixed with the plugin identifier. For example the password
plugin returns its username value as 'password_username'.
"""
raise NotImplementedError()
@six.add_metaclass(abc.ABCMeta)
class AuthConstructor(Auth):
"""Abstract base class for creating an Auth Plugin.
The Auth Plugin created contains only one authentication method. This
is generally the required usage.
An AuthConstructor creates an AuthMethod based on the method's
arguments and the auth_method_class defined by the plugin. It then
creates the auth plugin with only that authentication method.
"""
_auth_method_class = None
def __init__(self, auth_url, *args, **kwargs):
method_kwargs = self._auth_method_class._extract_kwargs(kwargs)
method = self._auth_method_class(*args, **method_kwargs)
super(AuthConstructor, self).__init__(auth_url, [method], **kwargs)

View File

@ -1,115 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
from keystoneauth1.identity.v3 import base
from keystoneauth1.identity.v3 import token
__all__ = ('FederationBaseAuth',)
@six.add_metaclass(abc.ABCMeta)
class _Rescoped(base.BaseAuth):
"""A plugin that is always going to go through a rescope process.
The original keystone plugins could simply pass a project or domain to
along with the credentials and get a scoped token. For federation, K2K and
newer mechanisms we always get an unscoped token first and then rescope.
This is currently not public as it's generally an abstraction of a flow
used by plugins within keystoneauth1.
It also cannot go in base as it depends on token.Token for rescoping which
would create a circular dependency.
"""
rescoping_plugin = token.Token
def _get_scoping_data(self):
return {'trust_id': self.trust_id,
'domain_id': self.domain_id,
'domain_name': self.domain_name,
'project_id': self.project_id,
'project_name': self.project_name,
'project_domain_id': self.project_domain_id,
'project_domain_name': self.project_domain_name}
def get_auth_ref(self, session, **kwargs):
"""Authenticate retrieve token information.
This is a multi-step process where a client does federated authn
receives an unscoped token.
If an unscoped token is successfully received and scoping information
is present then the token is rescoped to that target.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a token data representation
:rtype: :py:class:`keystoneauth1.access.AccessInfo`
"""
auth_ref = self.get_unscoped_auth_ref(session)
scoping = self._get_scoping_data()
if any(scoping.values()):
token_plugin = self.rescoping_plugin(self.auth_url,
token=auth_ref.auth_token,
**scoping)
auth_ref = token_plugin.get_auth_ref(session)
return auth_ref
@abc.abstractmethod
def get_unscoped_auth_ref(self, session, **kwargs):
"""Fetch unscoped federated token."""
class FederationBaseAuth(_Rescoped):
"""Federation authentication plugin.
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: name of the Identity Provider the client
will authenticate against. This parameter
will be used to build a dynamic URL used to
obtain unscoped OpenStack token.
:type identity_provider: string
:param protocol: name of the protocol the client will authenticate
against.
:type protocol: string
"""
def __init__(self, auth_url, identity_provider, protocol, **kwargs):
super(FederationBaseAuth, self).__init__(auth_url=auth_url, **kwargs)
self.identity_provider = identity_provider
self.protocol = protocol
@property
def federated_token_url(self):
"""Full URL where authorization data is sent."""
values = {
'host': self.auth_url.rstrip('/'),
'identity_provider': self.identity_provider,
'protocol': self.protocol
}
url = ("%(host)s/OS-FEDERATION/identity_providers/"
"%(identity_provider)s/protocols/%(protocol)s/auth")
url = url % values
return url

View File

@ -1,173 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.identity.v3 import federation
from keystoneauth1 import plugin
__all__ = ('Keystone2Keystone',)
class Keystone2Keystone(federation._Rescoped):
"""Plugin to execute the Keystone to Keyestone authentication flow.
In this plugin, an ECP wrapped SAML assertion provided by a keystone
Identity Provider (IdP) is used to request an OpenStack unscoped token
from a keystone Service Provider (SP).
:param base_plugin: Auth plugin already authenticated against the keystone
IdP.
:type base_plugin: keystoneauth1.identity.v3.base.BaseAuth
:param service_provider: The Service Provider ID as returned by
ServiceProviderManager.list()
:type service_provider: str
"""
REQUEST_ECP_URL = '/auth/OS-FEDERATION/saml2/ecp'
"""Path where the ECP wrapped SAML assertion should be presented to the
Keystone Service Provider."""
HTTP_MOVED_TEMPORARILY = 302
HTTP_SEE_OTHER = 303
def __init__(self, base_plugin, service_provider, **kwargs):
super(Keystone2Keystone, self).__init__(auth_url=None, **kwargs)
self._local_cloud_plugin = base_plugin
self._sp_id = service_provider
@classmethod
def _remote_auth_url(cls, auth_url):
"""Return auth_url of the remote Keystone Service Provider.
Remote cloud's auth_url is an endpoint for getting federated unscoped
token, typically that would be
``https://remote.example.com:5000/v3/OS-FEDERATION/identity_providers/
<idp>/protocols/<protocol_id>/auth``. However we need to generate a
real auth_url, used for token scoping. This function assumes there are
static values today in the remote auth_url stored in the Service
Provider attribute and those can be used as a delimiter. If the
sp_auth_url doesn't comply with standard federation auth url the
function will simply return whole string.
:param auth_url: auth_url of the remote cloud
:type auth_url: str
:returns: auth_url of remote cloud where a token can be validated or
scoped.
:rtype: str
"""
PATTERN = '/OS-FEDERATION/'
idx = auth_url.index(PATTERN) if PATTERN in auth_url else len(auth_url)
return auth_url[:idx]
def _get_ecp_assertion(self, session):
body = {
'auth': {
'identity': {
'methods': ['token'],
'token': {
'id': self._local_cloud_plugin.get_token(session)
}
},
'scope': {
'service_provider': {
'id': self._sp_id
}
}
}
}
endpoint_filter = {'version': (3, 0),
'interface': plugin.AUTH_INTERFACE}
headers = {'Accept': 'application/json'}
resp = session.post(self.REQUEST_ECP_URL,
json=body,
auth=self._local_cloud_plugin,
endpoint_filter=endpoint_filter,
headers=headers,
authenticated=False,
raise_exc=False)
# NOTE(marek-denis): I am not sure whether disabling exceptions in the
# Session object and testing if resp.ok is sufficient. An alternative
# would be catching locally all exceptions and reraising with custom
# warning.
if not resp.ok:
msg = ("Error while requesting ECP wrapped assertion: response "
"exit code: %(status_code)d, reason: %(err)s")
msg = msg % {'status_code': resp.status_code, 'err': resp.reason}
raise exceptions.AuthorizationFailure(msg)
if not resp.text:
raise exceptions.InvalidResponse(resp)
return six.text_type(resp.text)
def _send_service_provider_ecp_authn_response(self, session, sp_url,
sp_auth_url):
"""Present ECP wrapped SAML assertion to the keystone SP.
The assertion is issued by the keystone IdP and it is targeted to the
keystone that will serve as Service Provider.
:param session: a session object to send out HTTP requests.
:param sp_url: URL where the ECP wrapped SAML assertion will be
presented to the keystone SP. Usually, something like:
https://sp.com/Shibboleth.sso/SAML2/ECP
:type sp_url: str
:param sp_auth_url: Federated authentication URL of the keystone SP.
It is specified by IdP, for example:
https://sp.com/v3/OS-FEDERATION/identity_providers/
idp_id/protocols/protocol_id/auth
:type sp_auth_url: str
"""
response = session.post(
sp_url,
headers={'Content-Type': 'application/vnd.paos+xml'},
data=self._get_ecp_assertion(session),
authenticated=False,
redirect=False)
# Don't follow HTTP specs - after the HTTP 302/303 response don't
# repeat the call directed to the Location URL. In this case, this is
# an indication that SAML2 session is now active and protected resource
# can be accessed.
if response.status_code in (self.HTTP_MOVED_TEMPORARILY,
self.HTTP_SEE_OTHER):
response = session.get(
sp_auth_url,
headers={'Content-Type': 'application/vnd.paos+xml'},
authenticated=False)
return response
def get_unscoped_auth_ref(self, session, **kwargs):
sp_auth_url = self._local_cloud_plugin.get_sp_auth_url(
session, self._sp_id)
sp_url = self._local_cloud_plugin.get_sp_url(session, self._sp_id)
self.auth_url = self._remote_auth_url(sp_auth_url)
response = self._send_service_provider_ecp_authn_response(
session, sp_url, sp_auth_url)
return access.create(resp=response)

View File

@ -1,480 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import warnings
from positional import positional
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import access
from keystoneauth1 import exceptions
from keystoneauth1.identity.v3 import federation
_logger = utils.get_logger(__name__)
__all__ = ('OidcAuthorizationCode',
'OidcClientCredentials',
'OidcPassword',
'OidcAccessToken')
@six.add_metaclass(abc.ABCMeta)
class _OidcBase(federation.FederationBaseAuth):
"""Base class for different OpenID Connect based flows.
The OpenID Connect specification can be found at::
``http://openid.net/specs/openid-connect-core-1_0.html``
"""
grant_type = None
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret,
access_token_type,
scope="openid profile",
access_token_endpoint=None,
discovery_endpoint=None,
grant_type=None,
**kwargs):
"""The OpenID Connect plugin expects the following.
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: Name of the Identity Provider the client
will authenticate against
:type identity_provider: string
:param protocol: Protocol name as configured in keystone
:type protocol: string
:param client_id: OAuth 2.0 Client ID
:type client_id: string
:param client_secret: OAuth 2.0 Client Secret
:type client_secret: string
:param access_token_type: OAuth 2.0 Authorization Server Introspection
token type, it is used to decide which type
of token will be used when processing token
introspection. Valid values are:
"access_token" or "id_token"
:type access_token_type: string
:param access_token_endpoint: OpenID Connect Provider Token Endpoint,
for example:
https://localhost:8020/oidc/OP/token
Note that if a discovery document is
provided this value will override
the discovered one.
:type access_token_endpoint: string
:param discovery_endpoint: OpenID Connect Discovery Document URL,
for example:
https://localhost:8020/oidc/.well-known/openid-configuration
:type access_token_endpoint: string
:param scope: OpenID Connect scope that is requested from OP,
for example: "openid profile email", defaults to
"openid profile". Note that OpenID Connect specification
states that "openid" must be always specified.
:type scope: string
"""
super(_OidcBase, self).__init__(auth_url, identity_provider, protocol,
**kwargs)
self.client_id = client_id
self.client_secret = client_secret
self.discovery_endpoint = discovery_endpoint
self._discovery_document = {}
self.access_token_endpoint = access_token_endpoint
self.access_token_type = access_token_type
self.scope = scope
if grant_type is not None:
if grant_type != self.grant_type:
raise exceptions.OidcGrantTypeMissmatch()
warnings.warn("Passing grant_type as an argument has been "
"deprecated as it is now defined in the plugin "
"itself. You should stop passing this argument "
"to the plugin, as it will be ignored, since you "
"cannot pass a free text string as a grant_type. "
"This argument will be dropped from the plugin in "
"July 2017 or with the next major release of "
"keystoneauth (3.0.0)",
DeprecationWarning)
def _get_discovery_document(self, session):
"""Get the contents of the OpenID Connect Discovery Document.
This method grabs the contents of the OpenID Connect Discovery Document
if a discovery_endpoint was passed to the constructor and returns it as
a dict, otherwise returns an empty dict. Note that it will fetch the
discovery document only once, so subsequent calls to this method will
return the cached result, if any.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a python dictionary containing the discovery document if any,
otherwise it will return an empty dict.
:rtype: dict
"""
if (self.discovery_endpoint is not None and
not self._discovery_document):
try:
resp = session.get(self.discovery_endpoint,
authenticated=False)
except exceptions.HttpError:
_logger.error("Cannot fetch discovery document %(discovery)s" %
{"discovery": self.discovery_endpoint})
raise
try:
self._discovery_document = resp.json()
except Exception:
pass
if not self._discovery_document:
raise exceptions.InvalidOidcDiscoveryDocument()
return self._discovery_document
def _get_access_token_endpoint(self, session):
"""Get the "token_endpoint" for the OpenID Connect flow.
This method will return the correct access token endpoint to be used.
If the user has explicitly passed an access_token_endpoint to the
constructor that will be returned. If there is no explicit endpoint and
a discovery url is provided, it will try to get it from the discovery
document. If nothing is found, an exception will be raised.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:return: the endpoint to use
:rtype: string or None if no endpoint is found
"""
if self.access_token_endpoint is not None:
return self.access_token_endpoint
discovery = self._get_discovery_document(session)
endpoint = discovery.get("token_endpoint")
if endpoint is None:
raise exceptions.OidcAccessTokenEndpointNotFound()
return endpoint
def _get_access_token(self, session, payload):
"""Exchange a variety of user supplied values for an access token.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:param payload: a dict containing various OpenID Connect values, for
example::
{'grant_type': 'password', 'username': self.username,
'password': self.password, 'scope': self.scope}
:type payload: dict
"""
client_auth = (self.client_id, self.client_secret)
access_token_endpoint = self._get_access_token_endpoint(session)
op_response = session.post(access_token_endpoint,
requests_auth=client_auth,
data=payload,
authenticated=False)
access_token = op_response.json()[self.access_token_type]
return access_token
def _get_keystone_token(self, session, access_token):
r"""Exchange an access token for a keystone token.
By Sending the access token in an `Authorization: Bearer` header, to
an OpenID Connect protected endpoint (Federated Token URL). The
OpenID Connect server will use the access token to look up information
about the authenticated user (this technique is called instrospection).
The output of the instrospection will be an OpenID Connect Claim, that
will be used against the mapping engine. Should the mapping engine
succeed, a Keystone token will be presented to the user.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:param access_token: The OpenID Connect access token.
:type access_token: str
"""
# use access token against protected URL
headers = {'Authorization': 'Bearer ' + access_token}
auth_response = session.post(self.federated_token_url,
headers=headers,
authenticated=False)
return auth_response
def get_unscoped_auth_ref(self, session):
"""Authenticate with OpenID Connect and get back claims.
This is a multi-step process:
1.- An access token must be retrieved from the server. In order to do
so, we need to exchange an authorization grant or refresh token
with the token endpoint in order to obtain an access token. The
authorization grant varies from plugin to plugin.
2.- We then exchange the access token upon accessing the protected
Keystone endpoint (federated auth URL). This will trigger the
OpenID Connect Provider to perform a user introspection and
retrieve information (specified in the scope) about the user in the
form of an OpenID Connect Claim. These claims will be sent to
Keystone in the form of environment variables.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a token data representation
:rtype: :py:class:`keystoneauth1.access.AccessInfoV3`
"""
# First of all, check if the grant type is supported
discovery = self._get_discovery_document(session)
grant_types = discovery.get("grant_types_supported")
if (grant_types and
self.grant_type is not None and
self.grant_type not in grant_types):
raise exceptions.OidcPluginNotSupported()
# Get the payload
payload = self.get_payload(session)
payload.setdefault('grant_type', self.grant_type)
# get an access token
access_token = self._get_access_token(session, payload)
response = self._get_keystone_token(session, access_token)
# grab the unscoped token
return access.create(resp=response)
@abc.abstractmethod
def get_payload(self, session):
"""Get the plugin specific payload for obtainin an access token.
OpenID Connect supports different grant types. This method should
prepare the payload that needs to be exchanged with the server in
order to get an access token for the particular grant type that the
plugin is implementing.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a python dictionary containing the payload to be exchanged
:rtype: dict
"""
raise NotImplementedError()
class OidcPassword(_OidcBase):
"""Implementation for OpenID Connect Resource Owner Password Credential."""
grant_type = "password"
@positional(4)
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret,
access_token_endpoint=None,
discovery_endpoint=None,
access_token_type='access_token',
username=None, password=None,
**kwargs):
"""The OpenID Password plugin expects the following.
:param username: Username used to authenticate
:type username: string
:param password: Password used to authenticate
:type password: string
"""
super(OidcPassword, self).__init__(
auth_url=auth_url,
identity_provider=identity_provider,
protocol=protocol,
client_id=client_id,
client_secret=client_secret,
access_token_endpoint=access_token_endpoint,
discovery_endpoint=discovery_endpoint,
access_token_type=access_token_type,
**kwargs)
self.username = username
self.password = password
def get_payload(self, session):
"""Get an authorization grant for the "password" grant type.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a python dictionary containing the payload to be exchanged
:rtype: dict
"""
payload = {'username': self.username,
'password': self.password,
'scope': self.scope}
return payload
class OidcClientCredentials(_OidcBase):
"""Implementation for OpenID Connect Client Credentials."""
grant_type = 'client_credentials'
@positional(4)
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret,
access_token_endpoint=None,
discovery_endpoint=None,
access_token_type='access_token',
**kwargs):
"""The OpenID Client Credentials expects the following.
:param client_id: Client ID used to authenticate
:type username: string
:param client_secret: Client Secret used to authenticate
:type password: string
"""
super(OidcClientCredentials, self).__init__(
auth_url=auth_url,
identity_provider=identity_provider,
protocol=protocol,
client_id=client_id,
client_secret=client_secret,
access_token_endpoint=access_token_endpoint,
discovery_endpoint=discovery_endpoint,
access_token_type=access_token_type,
**kwargs)
def get_payload(self, session):
"""Get an authorization grant for the client credentials grant type.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a python dictionary containing the payload to be exchanged
:rtype: dict
"""
payload = {'scope': self.scope}
return payload
class OidcAuthorizationCode(_OidcBase):
"""Implementation for OpenID Connect Authorization Code."""
grant_type = 'authorization_code'
@positional(4)
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret,
access_token_endpoint=None,
discovery_endpoint=None,
access_token_type='access_token',
redirect_uri=None, code=None, **kwargs):
"""The OpenID Authorization Code plugin expects the following.
:param redirect_uri: OpenID Connect Client Redirect URL
:type redirect_uri: string
:param code: OAuth 2.0 Authorization Code
:type code: string
"""
super(OidcAuthorizationCode, self).__init__(
auth_url=auth_url,
identity_provider=identity_provider,
protocol=protocol,
client_id=client_id,
client_secret=client_secret,
access_token_endpoint=access_token_endpoint,
discovery_endpoint=discovery_endpoint,
access_token_type=access_token_type,
**kwargs)
self.redirect_uri = redirect_uri
self.code = code
def get_payload(self, session):
"""Get an authorization grant for the "authorization_code" grant type.
:param session: a session object to send out HTTP requests.
:type session: keystoneauth1.session.Session
:returns: a python dictionary containing the payload to be exchanged
:rtype: dict
"""
payload = {'redirect_uri': self.redirect_uri, 'code': self.code}
return payload
class OidcAccessToken(_OidcBase):
"""Implementation for OpenID Connect access token reuse."""
@positional(5)
def __init__(self, auth_url, identity_provider, protocol,
access_token, **kwargs):
"""The OpenID Connect plugin based on the Access Token.
It expects the following:
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: Name of the Identity Provider the client
will authenticate against
:type identity_provider: string
:param protocol: Protocol name as configured in keystone
:type protocol: string
:param access_token: OpenID Connect Access token
:type access_token: string
"""
super(OidcAccessToken, self).__init__(auth_url, identity_provider,
protocol,
client_id=None,
client_secret=None,
access_token_endpoint=None,
access_token_type=None,
**kwargs)
self.access_token = access_token
def get_payload(self, session):
"""OidcAccessToken does not require a payload."""
return {}
def get_unscoped_auth_ref(self, session):
"""Authenticate with OpenID Connect and get back claims.
We exchange the access token upon accessing the protected Keystone
endpoint (federated auth URL). This will trigger the OpenID Connect
Provider to perform a user introspection and retrieve information
(specified in the scope) about the user in the form of an OpenID
Connect Claim. These claims will be sent to Keystone in the form of
environment variables.
:param session: a session object to send out HTTP requests.
:type session: keystoneclient.session.Session
:returns: a token data representation
:rtype: :py:class:`keystoneauth1.access.AccessInfoV3`
"""
response = self._get_keystone_token(session, self.access_token)
return access.create(resp=response)

View File

@ -1,75 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.identity.v3 import base
__all__ = ('PasswordMethod', 'Password')
class PasswordMethod(base.AuthMethod):
"""Construct a User/Password based authentication method.
:param string password: Password for authentication.
:param string username: Username for authentication.
:param string user_id: User ID for authentication.
:param string user_domain_id: User's domain ID for authentication.
:param string user_domain_name: User's domain name for authentication.
"""
_method_parameters = ['user_id',
'username',
'user_domain_id',
'user_domain_name',
'password']
def get_auth_data(self, session, auth, headers, **kwargs):
user = {'password': self.password}
if self.user_id:
user['id'] = self.user_id
elif self.username:
user['name'] = self.username
if self.user_domain_id:
user['domain'] = {'id': self.user_domain_id}
elif self.user_domain_name:
user['domain'] = {'name': self.user_domain_name}
return 'password', {'user': user}
def get_cache_id_elements(self):
return dict(('password_%s' % p, getattr(self, p))
for p in self._method_parameters)
class Password(base.AuthConstructor):
"""A plugin for authenticating with a username and password.
:param string auth_url: Identity service endpoint for authentication.
:param string password: Password for authentication.
:param string username: Username for authentication.
:param string user_id: User ID for authentication.
:param string user_domain_id: User's domain ID for authentication.
:param string user_domain_name: User's domain name for authentication.
:param string trust_id: Trust ID for trust scoping.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
"""
_auth_method_class = PasswordMethod

View File

@ -1,54 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.identity.v3 import base
__all__ = ('TokenMethod', 'Token')
class TokenMethod(base.AuthMethod):
"""Construct an Auth plugin to fetch a token from a token.
:param string token: Token for authentication.
"""
_method_parameters = ['token']
def get_auth_data(self, session, auth, headers, **kwargs):
headers['X-Auth-Token'] = self.token
return 'token', {'id': self.token}
def get_cache_id_elements(self):
return {'token_token': self.token}
class Token(base.AuthConstructor):
"""A plugin for authenticating with an existing Token.
:param string auth_url: Identity service endpoint for authentication.
:param string token: Token for authentication.
:param string trust_id: Trust ID for trust scoping.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
"""
_auth_method_class = TokenMethod
def __init__(self, auth_url, token, **kwargs):
super(Token, self).__init__(auth_url, token=token, **kwargs)

View File

@ -1,115 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import plugin
LOG = utils.get_logger(__name__)
@six.add_metaclass(abc.ABCMeta)
class TokenlessAuth(plugin.BaseAuthPlugin):
"""A plugin for authenticating with Tokenless Auth.
This is for Tokenless Authentication. Scoped information
like domain name and project ID will be passed in the headers and
token validation request will be authenticated based on
the provided HTTPS certificate along with the scope information.
"""
def __init__(self, auth_url,
domain_id=None,
domain_name=None,
project_id=None,
project_name=None,
project_domain_id=None,
project_domain_name=None):
"""A init method for TokenlessAuth.
:param string auth_url: Identity service endpoint for authentication.
The URL must include a version or any request
will result in a 404 NotFound error.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
"""
self.auth_url = auth_url
self.domain_id = domain_id
self.domain_name = domain_name
self.project_id = project_id
self.project_name = project_name
self.project_domain_id = project_domain_id
self.project_domain_name = project_domain_name
def get_headers(self, session, **kwargs):
"""Fetch authentication headers for message.
This is to override the default get_headers method to provide
tokenless auth scope headers if token is not provided in the
session.
:param session: The session object that the auth_plugin belongs to.
:type session: keystoneauth1.session.Session
:returns: Headers that are set to authenticate a message or None for
failure. Note that when checking this value that the empty
dict is a valid, non-failure response.
:rtype: dict
"""
scope_headers = {}
if self.project_id:
scope_headers['X-Project-Id'] = self.project_id
elif self.project_name:
scope_headers['X-Project-Name'] = self.project_name
if self.project_domain_id:
scope_headers['X-Project-Domain-Id'] = (
self.project_domain_id)
elif self.project_domain_name:
scope_headers['X-Project-Domain-Name'] = (
self.project_domain_name)
else:
LOG.warning(
'Neither Project Domain ID nor Project Domain Name was '
'provided.')
return None
elif self.domain_id:
scope_headers['X-Domain-Id'] = self.domain_id
elif self.domain_name:
scope_headers['X-Domain-Name'] = self.domain_name
else:
LOG.warning(
'Neither Project nor Domain scope was provided.')
return None
return scope_headers
def get_endpoint(self, session, service_type=None, **kwargs):
"""Return a valid endpoint for a service.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param string service_type: The type of service to lookup the endpoint
for. This plugin will return None (failure)
if service_type is not provided.
:return: A valid endpoint URL or None if not available.
:rtype: string or None
"""
if (service_type is plugin.AUTH_INTERFACE
or service_type.lower() == 'identity'):
return self.auth_url
return None

View File

@ -1,81 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from keystoneauth1.identity.v3 import base
__all__ = ('TOTPMethod', 'TOTP')
class TOTPMethod(base.AuthMethod):
"""Construct a User/Passcode based authentication method.
:param string passcode: TOTP passcode for authentication.
:param string username: Username for authentication.
:param string user_id: User ID for authentication.
:param string user_domain_id: User's domain ID for authentication.
:param string user_domain_name: User's domain name for authentication.
"""
_method_parameters = ['user_id',
'username',
'user_domain_id',
'user_domain_name',
'passcode']
def get_auth_data(self, session, auth, headers, **kwargs):
user = {'passcode': self.passcode}
if self.user_id:
user['id'] = self.user_id
elif self.username:
user['name'] = self.username
if self.user_domain_id:
user['domain'] = {'id': self.user_domain_id}
elif self.user_domain_name:
user['domain'] = {'name': self.user_domain_name}
return 'totp', {'user': user}
def get_cache_id_elements(self):
# NOTE(gyee): passcode is not static so we cannot use it as part of
# the key in caching.
params = copy.copy(self._method_parameters)
params.remove('passcode')
return dict(('totp_%s' % p, getattr(self, p))
for p in self._method_parameters)
class TOTP(base.AuthConstructor):
"""A plugin for authenticating with a username and TOTP passcode.
:param string auth_url: Identity service endpoint for authentication.
:param string passcode: TOTP passcode for authentication.
:param string username: Username for authentication.
:param string user_id: User ID for authentication.
:param string user_domain_id: User's domain ID for authentication.
:param string user_domain_name: User's domain name for authentication.
:param string trust_id: Trust ID for trust scoping.
:param string domain_id: Domain ID for domain scoping.
:param string domain_name: Domain name for domain scoping.
:param string project_id: Project ID for project scoping.
:param string project_name: Project name for project scoping.
:param string project_domain_id: Project's domain ID for project.
:param string project_domain_name: Project's domain name for project.
:param bool reauthenticate: Allow fetching a new token if the current one
is going to expire. (optional) default True
"""
_auth_method_class = TOTPMethod

View File

@ -1,85 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.loading import adapter
from keystoneauth1.loading.base import * # noqa
from keystoneauth1.loading import cli
from keystoneauth1.loading import conf
from keystoneauth1.loading.identity import * # noqa
from keystoneauth1.loading.opts import * # noqa
from keystoneauth1.loading import session
register_auth_argparse_arguments = cli.register_argparse_arguments
load_auth_from_argparse_arguments = cli.load_from_argparse_arguments
get_auth_common_conf_options = conf.get_common_conf_options
get_auth_plugin_conf_options = conf.get_plugin_conf_options
register_auth_conf_options = conf.register_conf_options
load_auth_from_conf_options = conf.load_from_conf_options
register_session_argparse_arguments = session.register_argparse_arguments
load_session_from_argparse_arguments = session.load_from_argparse_arguments
register_session_conf_options = session.register_conf_options
load_session_from_conf_options = session.load_from_conf_options
get_session_conf_options = session.get_conf_options
register_adapter_argparse_arguments = adapter.register_argparse_arguments
register_service_adapter_argparse_arguments = (
adapter.register_service_argparse_arguments)
register_adapter_conf_options = adapter.register_conf_options
load_adapter_from_conf_options = adapter.load_from_conf_options
get_adapter_conf_options = adapter.get_conf_options
__all__ = (
# loading.base
'BaseLoader',
'get_available_plugin_names',
'get_available_plugin_loaders',
'get_plugin_loader',
'PLUGIN_NAMESPACE',
# loading.identity
'BaseIdentityLoader',
'BaseV2Loader',
'BaseV3Loader',
'BaseFederationLoader',
'BaseGenericLoader',
# auth cli
'register_auth_argparse_arguments',
'load_auth_from_argparse_arguments',
# auth conf
'get_auth_common_conf_options',
'get_auth_plugin_conf_options',
'register_auth_conf_options',
'load_auth_from_conf_options',
# session
'register_session_argparse_arguments',
'load_session_from_argparse_arguments',
'register_session_conf_options',
'load_session_from_conf_options',
'get_session_conf_options',
# adapter
'register_adapter_argparse_arguments',
'register_service_adapter_argparse_arguments',
'register_adapter_conf_options',
'load_adapter_from_conf_options',
'get_adapter_conf_options',
# loading.opts
'Opt',
)

View File

@ -1,46 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import loading
from keystoneauth1 import token_endpoint
class AdminToken(loading.BaseLoader):
"""Use an existing token and a known endpoint to perform requests.
This plugin is primarily useful for development or for use with identity
service ADMIN tokens. Because this token is used directly there is no
fetching a service catalog or determining scope information and so it
cannot be used by clients that expect use this scope information.
Because there is no service catalog the endpoint that is supplied with
initialization is used for all operations performed with this plugin so
must be the full base URL to an actual service.
"""
@property
def plugin_class(self):
return token_endpoint.Token
def get_options(self):
options = super(AdminToken, self).get_options()
options.extend([
loading.Opt('endpoint',
deprecated=[loading.Opt('url')],
help='The endpoint that will always be used'),
loading.Opt('token',
secret=True,
help='The token that will always be used'),
])
return options

View File

@ -1,75 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import identity
from keystoneauth1 import loading
class Token(loading.BaseGenericLoader):
"""Given an existing token rescope it to another target.
This plugin uses the Identity service's rescope mechanism to get a new
token based upon an existing token. Because an auth plugin requires a
service catalog and scope information it is often easier to fetch a new
token based on an existing one than validate and reuse the one you already
have.
As a generic plugin this plugin is identity version independent and will
discover available versions before use. This means it expects to be
providen an unversioned URL to operate against.
"""
@property
def plugin_class(self):
return identity.Token
def get_options(self):
options = super(Token, self).get_options()
options.extend([
loading.Opt('token', secret=True,
help='Token to authenticate with'),
])
return options
class Password(loading.BaseGenericLoader):
"""Authenticate via a username and password.
Authenticate to the identity service using an inbuilt username and
password. This is the standard and most common form of authentication.
As a generic plugin this plugin is identity version independent and will
discover available versions before use. This means it expects to be
providen an unversioned URL to operate against.
"""
@property
def plugin_class(self):
return identity.Password
def get_options(cls):
options = super(Password, cls).get_options()
options.extend([
loading.Opt('user-id', help='User id'),
loading.Opt('username',
help='Username',
deprecated=[loading.Opt('user-name')]),
loading.Opt('user-domain-id', help="User's domain id"),
loading.Opt('user-domain-name', help="User's domain name"),
loading.Opt('password',
secret=True,
prompt='Password: ',
help="User's password"),
])
return options

View File

@ -1,53 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import identity
from keystoneauth1 import loading
class Token(loading.BaseV2Loader):
@property
def plugin_class(self):
return identity.V2Token
def get_options(self):
options = super(Token, self).get_options()
options.extend([
loading.Opt('token', secret=True, help='Token'),
])
return options
class Password(loading.BaseV2Loader):
@property
def plugin_class(self):
return identity.V2Password
def get_options(self):
options = super(Password, self).get_options()
options.extend([
loading.Opt('username',
deprecated=[loading.Opt('user-name')],
help='Username to login with'),
loading.Opt('user-id', help='User ID to login with'),
loading.Opt('password',
secret=True,
prompt='Password: ',
help='Password to use'),
])
return options

View File

@ -1,256 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import exceptions
from keystoneauth1 import identity
from keystoneauth1 import loading
def _add_common_identity_options(options):
options.extend([
loading.Opt('user-id', help='User ID'),
loading.Opt('username',
help='Username',
deprecated=[loading.Opt('user-name')]),
loading.Opt('user-domain-id', help="User's domain id"),
loading.Opt('user-domain-name', help="User's domain name"),
])
def _assert_identity_options(options):
if (options.get('username') and
not (options.get('user_domain_name') or
options.get('user_domain_id'))):
m = "You have provided a username. In the V3 identity API a " \
"username is only unique within a domain so you must " \
"also provide either a user_domain_id or user_domain_name."
raise exceptions.OptionError(m)
class Password(loading.BaseV3Loader):
@property
def plugin_class(self):
return identity.V3Password
def get_options(self):
options = super(Password, self).get_options()
_add_common_identity_options(options)
options.extend([
loading.Opt('password',
secret=True,
prompt='Password: ',
help="User's password"),
])
return options
def load_from_options(self, **kwargs):
_assert_identity_options(kwargs)
return super(Password, self).load_from_options(**kwargs)
class Token(loading.BaseV3Loader):
@property
def plugin_class(self):
return identity.V3Token
def get_options(self):
options = super(Token, self).get_options()
options.extend([
loading.Opt('token',
secret=True,
help='Token to authenticate with'),
])
return options
class _OpenIDConnectBase(loading.BaseFederationLoader):
def load_from_options(self, **kwargs):
if not (kwargs.get('access_token_endpoint') or
kwargs.get('discovery_endpoint')):
m = ("You have to specify either an 'access-token-endpoint' or "
"a 'discovery-endpoint'.")
raise exceptions.OptionError(m)
return super(_OpenIDConnectBase, self).load_from_options(**kwargs)
def get_options(self):
options = super(_OpenIDConnectBase, self).get_options()
options.extend([
loading.Opt('client-id', help='OAuth 2.0 Client ID'),
loading.Opt('client-secret', secret=True,
help='OAuth 2.0 Client Secret'),
loading.Opt('openid-scope', default="openid profile",
dest="scope",
help='OpenID Connect scope that is requested from '
'authorization server. Note that the OpenID '
'Connect specification states that "openid" '
'must be always specified.'),
loading.Opt('access-token-endpoint',
help='OpenID Connect Provider Token Endpoint. Note '
'that if a discovery document is being passed this '
'option will override the endpoint provided by the '
'server in the discovery document.'),
loading.Opt('discovery-endpoint',
help='OpenID Connect Discovery Document URL. '
'The discovery document will be used to obtain the '
'values of the access token endpoint and the '
'authentication endpoint. This URL should look like '
'https://idp.example.org/.well-known/'
'openid-configuration'),
loading.Opt('access-token-type',
help='OAuth 2.0 Authorization Server Introspection '
'token type, it is used to decide which type '
'of token will be used when processing token '
'introspection. Valid values are: '
'"access_token" or "id_token"'),
])
return options
class OpenIDConnectClientCredentials(_OpenIDConnectBase):
@property
def plugin_class(self):
return identity.V3OidcClientCredentials
def get_options(self):
options = super(OpenIDConnectClientCredentials, self).get_options()
return options
class OpenIDConnectPassword(_OpenIDConnectBase):
@property
def plugin_class(self):
return identity.V3OidcPassword
def get_options(self):
options = super(OpenIDConnectPassword, self).get_options()
options.extend([
loading.Opt('username', help='Username', required=True),
loading.Opt('password', secret=True,
help='Password', required=True),
])
return options
class OpenIDConnectAuthorizationCode(_OpenIDConnectBase):
@property
def plugin_class(self):
return identity.V3OidcAuthorizationCode
def get_options(self):
options = super(OpenIDConnectAuthorizationCode, self).get_options()
options.extend([
loading.Opt('redirect-uri', help='OpenID Connect Redirect URL'),
loading.Opt('code', secret=True, required=True,
deprecated=[loading.Opt('authorization-code')],
help='OAuth 2.0 Authorization Code'),
])
return options
class OpenIDConnectAccessToken(loading.BaseFederationLoader):
@property
def plugin_class(self):
return identity.V3OidcAccessToken
def get_options(self):
options = super(OpenIDConnectAccessToken, self).get_options()
options.extend([
loading.Opt('access-token', secret=True, required=True,
help='OAuth 2.0 Access Token'),
])
return options
class TOTP(loading.BaseV3Loader):
@property
def plugin_class(self):
return identity.V3TOTP
def get_options(self):
options = super(TOTP, self).get_options()
_add_common_identity_options(options)
options.extend([
loading.Opt('passcode', secret=True, help="User's TOTP passcode"),
])
return options
def load_from_options(self, **kwargs):
_assert_identity_options(kwargs)
return super(TOTP, self).load_from_options(**kwargs)
class TokenlessAuth(loading.BaseLoader):
@property
def plugin_class(self):
return identity.V3TokenlessAuth
def get_options(self):
options = super(TokenlessAuth, self).get_options()
options.extend([
loading.Opt('auth-url', required=True,
help='Authentication URL'),
loading.Opt('domain-id', help='Domain ID to scope to'),
loading.Opt('domain-name', help='Domain name to scope to'),
loading.Opt('project-id', help='Project ID to scope to'),
loading.Opt('project-name', help='Project name to scope to'),
loading.Opt('project-domain-id',
help='Domain ID containing project'),
loading.Opt('project-domain-name',
help='Domain name containing project'),
])
return options
def load_from_options(self, **kwargs):
if (not kwargs.get('domain_id') and
not kwargs.get('domain_name') and
not kwargs.get('project_id') and
not kwargs.get('project_name') or
(kwargs.get('project_name') and
not (kwargs.get('project_domain_name') or
kwargs.get('project_domain_id')))):
m = ('You need to provide either a domain_name, domain_id, '
'project_id or project_name. '
'If you have provided a project_name, in the V3 identity '
'API a project_name is only unique within a domain so '
'you must also provide either a project_domain_id or '
'project_domain_name.')
raise exceptions.OptionError(m)
return super(TokenlessAuth, self).load_from_options(**kwargs)

View File

@ -1,33 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import loading
from keystoneauth1 import noauth
class NoAuth(loading.BaseLoader):
"""Use no tokens to perform requests.
This must be used together with adapter.Adapter.endpoint_override
to instantiate clients for services deployed in noauth/standalone mode.
There is no fetching a service catalog or determining scope information
and so it cannot be used by clients that expect use this scope information.
"""
@property
def plugin_class(self):
return noauth.NoAuth
def get_options(self):
return []

View File

@ -1,40 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
cfg = None
_NOT_FOUND = object()
def get_oslo_config():
"""Runtime load the oslo.config object.
In performance optimization of openstackclient it was determined that even
optimistically loading oslo.config if available had a performance cost.
Given that we used to only raise the ImportError when the function was
called also attempt to do the import to do everything at runtime.
"""
global cfg
# First Call
if not cfg:
try:
from oslo_config import cfg
except ImportError:
cfg = _NOT_FOUND
if cfg is _NOT_FOUND:
raise ImportError("oslo.config is not an automatic dependency of "
"keystoneauth. If you wish to use oslo.config "
"you need to import it into your application's "
"requirements file. ")
return cfg

View File

@ -1,209 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import adapter
from keystoneauth1.loading import _utils
from keystoneauth1.loading import base
__all__ = ('register_argparse_arguments',
'register_service_argparse_arguments',
'register_conf_options',
'load_from_conf_options',
'get_conf_options')
class Adapter(base.BaseLoader):
@property
def plugin_class(self):
return adapter.Adapter
def get_options(self):
return []
@staticmethod
def get_conf_options():
"""Get oslo_config options that are needed for a :py:class:`.Adapter`.
These may be useful without being registered for config file generation
or to manipulate the options before registering them yourself.
The options that are set are:
:service_type: The default service_type for URL discovery.
:service_name: The default service_name for URL discovery.
:interface: The default interface for URL discovery.
(deprecated)
:valid_interfaces: List of acceptable interfaces for URL
discovery. Can be a list of any of
'public', 'internal' or 'admin'.
:region_name: The default region_name for URL discovery.
:endpoint_override: Always use this endpoint URL for requests
for this client.
:version: The minimum version restricted to a given Major
API. Mutually exclusive with min_version and
max_version.
:min_version: The minimum major version of a given API,
intended to be used as the lower bound of a
range with max_version. Mutually exclusive with
version. If min_version is given with no
max_version it is as if max version is
'latest'.
:max_version: The maximum major version of a given API,
intended to be used as the upper bound of a
range with min_version. Mutually exclusive with
version.
:returns: A list of oslo_config options.
"""
cfg = _utils.get_oslo_config()
return [cfg.StrOpt('service-type',
help='The default service_type for endpoint URL '
'discovery.'),
cfg.StrOpt('service-name',
help='The default service_name for endpoint URL '
'discovery.'),
cfg.StrOpt('interface',
help='The default interface for endpoint URL '
'discovery.',
deprecated_for_removal=True,
deprecated_reason='Using valid-interfaces is'
' preferrable because it is'
' capable of accepting a list of'
' possible interfaces.'),
cfg.ListOpt('valid-interfaces',
help='List of interfaces, in order of preference, '
'for endpoint URL.'),
cfg.StrOpt('region-name',
help='The default region_name for endpoint URL '
'discovery.'),
cfg.StrOpt('endpoint-override',
help='Always use this endpoint URL for requests '
'for this client.'),
cfg.StrOpt('version',
help='Minimum Major API version within a given '
'Major API version for endpoint URL '
'discovery. Mutually exclusive with '
'min_version and max_version'),
cfg.StrOpt('min-version',
help='The minimum major version of a given API, '
'intended to be used as the lower bound of a '
'range with max_version. Mutually exclusive '
'with version. If min_version is given with '
'no max_version it is as if max version is '
'"latest".'),
cfg.StrOpt('max-version',
help='The maximum major version of a given API, '
'intended to be used as the upper bound of a '
'range with min_version. Mutually exclusive '
'with version.'),
]
def register_conf_options(self, conf, group):
"""Register the oslo_config options that are needed for an Adapter.
The options that are set are:
:service_type: The default service_type for URL discovery.
:service_name: The default service_name for URL discovery.
:interface: The default interface for URL discovery.
(deprecated)
:valid_interfaces: List of acceptable interfaces for URL
discovery. Can be a list of any of
'public', 'internal' or 'admin'.
:region_name: The default region_name for URL discovery.
:endpoint_override: Always use this endpoint URL for requests
for this client.
:version: The minimum version restricted to a given Major
API. Mutually exclusive with min_version and
max_version.
:min_version: The minimum major version of a given API,
intended to be used as the lower bound of a
range with max_version. Mutually exclusive with
version. If min_version is given with no
max_version it is as if max version is
'latest'.
:max_version: The maximum major version of a given API,
intended to be used as the upper bound of a
range with min_version. Mutually exclusive with
version.
:param oslo_config.Cfg conf: config object to register with.
:param string group: The ini group to register options in.
:returns: The list of options that was registered.
"""
opts = self.get_conf_options()
conf.register_group(_utils.get_oslo_config().OptGroup(group))
conf.register_opts(opts, group=group)
return opts
def load_from_conf_options(self, conf, group, **kwargs):
"""Create an Adapter object from an oslo_config object.
The options must have been previously registered with
register_conf_options.
:param oslo_config.Cfg conf: config object to register with.
:param string group: The ini group to register options in.
:param dict kwargs: Additional parameters to pass to Adapter
construction.
:returns: A new Adapter object.
:rtype: :py:class:`.Adapter`
"""
c = conf[group]
if c.valid_interfaces and c.interface:
raise TypeError("interface and valid_interfaces are mutually"
" exclusive. Please use valid_interfaces.")
if c.valid_interfaces:
for iface in c.valid_interfaces:
if iface not in ('public', 'internal', 'admin'):
raise TypeError("'{iface}' is not a valid value for"
" valid_interfaces. Valid valies are"
" public, internal or admin")
kwargs.setdefault('interface', c.valid_interfaces)
else:
kwargs.setdefault('interface', c.interface)
kwargs.setdefault('service_type', c.service_type)
kwargs.setdefault('service_name', c.service_name)
kwargs.setdefault('region_name', c.region_name)
kwargs.setdefault('endpoint_override', c.endpoint_override)
kwargs.setdefault('version', c.version)
kwargs.setdefault('min_version', c.min_version)
kwargs.setdefault('max_version', c.max_version)
if kwargs['version'] and (
kwargs['max_version'] or kwargs['min_version']):
raise TypeError(
"version is mutually exclusive with min_version and"
" max_version")
return self.load_from_options(**kwargs)
def register_argparse_arguments(*args, **kwargs):
return adapter.register_adapter_argparse_arguments(*args, **kwargs)
def register_service_argparse_arguments(*args, **kwargs):
return adapter.register_service_adapter_argparse_arguments(*args, **kwargs)
def register_conf_options(*args, **kwargs):
return Adapter().register_conf_options(*args, **kwargs)
def load_from_conf_options(*args, **kwargs):
return Adapter().load_from_conf_options(*args, **kwargs)
def get_conf_options():
return Adapter.get_conf_options()

View File

@ -1,187 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
import stevedore
from keystoneauth1 import exceptions
PLUGIN_NAMESPACE = 'keystoneauth1.plugin'
__all__ = ('get_available_plugin_names',
'get_available_plugin_loaders',
'get_plugin_loader',
'get_plugin_options',
'BaseLoader',
'PLUGIN_NAMESPACE')
def _auth_plugin_available(ext):
"""Read the value of available for whether to load this plugin."""
return ext.obj.available
def get_available_plugin_names():
"""Get the names of all the plugins that are available on the system.
This is particularly useful for help and error text to prompt a user for
example what plugins they may specify.
:returns: A list of names.
:rtype: frozenset
"""
mgr = stevedore.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE,
check_func=_auth_plugin_available,
invoke_on_load=True,
propagate_map_exceptions=True)
return frozenset(mgr.names())
def get_available_plugin_loaders():
"""Retrieve all the plugin classes available on the system.
:returns: A dict with plugin entrypoint name as the key and the plugin
loader as the value.
:rtype: dict
"""
mgr = stevedore.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE,
check_func=_auth_plugin_available,
invoke_on_load=True,
propagate_map_exceptions=True)
return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.obj)))
def get_plugin_loader(name):
"""Retrieve a plugin class by its entrypoint name.
:param str name: The name of the object to get.
:returns: An auth plugin class.
:rtype: :py:class:`keystoneauth1.loading.BaseLoader`
:raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin:
if a plugin cannot be created.
"""
try:
mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE,
invoke_on_load=True,
name=name)
except RuntimeError:
raise exceptions.NoMatchingPlugin(name)
return mgr.driver
def get_plugin_options(name):
"""Get the options for a specific plugin.
This will be the list of options that is registered and loaded by the
specified plugin.
:returns: A list of :py:class:`keystoneauth1.loading.Opt` options.
:raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin:
if a plugin cannot be created.
"""
return get_plugin_loader(name).get_options()
@six.add_metaclass(abc.ABCMeta)
class BaseLoader(object):
@property
def plugin_class(self):
raise NotImplementedError()
def create_plugin(self, **kwargs):
"""Create a plugin from the options available for the loader.
Given the options that were specified by the loader create an
appropriate plugin. You can override this function in your loader.
This used to be specified by providing the plugin_class property and
this is still supported, however specifying a property didn't let you
choose a plugin type based upon the options that were presented.
Override this function if you wish to return different plugins based on
the options presented, otherwise you can simply provide the
plugin_class property.
Added 2.9
"""
return self.plugin_class(**kwargs)
@abc.abstractmethod
def get_options(self):
"""Return the list of parameters associated with the auth plugin.
This list may be used to generate CLI or config arguments.
:returns: A list of Param objects describing available plugin
parameters.
:rtype: list
"""
return []
@property
def available(self):
"""Return if the plugin is available for loading.
If a plugin is missing dependencies or for some other reason should not
be available to the current system it should override this property and
return False to exclude itself from the plugin list.
:rtype: bool
"""
return True
def load_from_options(self, **kwargs):
"""Create a plugin from the arguments retrieved from get_options.
A client can override this function to do argument validation or to
handle differences between the registered options and what is required
to create the plugin.
"""
missing_required = [o for o in self.get_options()
if o.required and kwargs.get(o.dest) is None]
if missing_required:
raise exceptions.MissingRequiredOptions(missing_required)
return self.create_plugin(**kwargs)
def load_from_options_getter(self, getter, **kwargs):
"""Load a plugin from getter function that returns appropriate values.
To handle cases other than the provided CONF and CLI loading you can
specify a custom loader function that will be queried for the option
value.
The getter is a function that takes a
:py:class:`keystoneauth1.loading.Opt` and returns a value to load with.
:param getter: A function that returns a value for the given opt.
:type getter: callable
:returns: An authentication Plugin.
:rtype: :py:class:`keystoneauth1.plugin.BaseAuthPlugin`
"""
for opt in (o for o in self.get_options() if o.dest not in kwargs):
val = getter(opt)
if val is not None:
val = opt.type(val)
kwargs[opt.dest] = val
return self.load_from_options(**kwargs)

View File

@ -1,105 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
from positional import positional
from keystoneauth1.loading import base
__all__ = ('register_argparse_arguments',
'load_from_argparse_arguments')
def _register_plugin_argparse_arguments(parser, plugin):
for opt in plugin.get_options():
parser.add_argument(*opt.argparse_args,
default=opt.argparse_default,
metavar=opt.metavar,
help=opt.help,
dest='os_%s' % opt.dest)
@positional()
def register_argparse_arguments(parser, argv, default=None):
"""Register CLI options needed to create a plugin.
The function inspects the provided arguments so that it can also register
the options required for that specific plugin if available.
:param parser: the parser to attach argparse options to.
:type parser: argparse.ArgumentParser
:param list argv: the arguments provided to the appliation.
:param str/class default: a default plugin name or a plugin object to use
if one isn't specified by the CLI. default: None.
:returns: The plugin class that will be loaded or None if not provided.
:rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin`
:raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin:
if a plugin cannot be created.
"""
in_parser = argparse.ArgumentParser(add_help=False)
env_plugin = os.environ.get('OS_AUTH_TYPE',
os.environ.get('OS_AUTH_PLUGIN', default))
for p in (in_parser, parser):
p.add_argument('--os-auth-type',
'--os-auth-plugin',
metavar='<name>',
default=env_plugin,
help='Authentication type to use')
options, _args = in_parser.parse_known_args(argv)
if not options.os_auth_type:
return None
if isinstance(options.os_auth_type, base.BaseLoader):
msg = 'Default Authentication options'
plugin = options.os_auth_type
else:
msg = 'Options specific to the %s plugin.' % options.os_auth_type
plugin = base.get_plugin_loader(options.os_auth_type)
group = parser.add_argument_group('Authentication Options', msg)
_register_plugin_argparse_arguments(group, plugin)
return plugin
def load_from_argparse_arguments(namespace, **kwargs):
"""Retrieve the created plugin from the completed argparse results.
Loads and creates the auth plugin from the information parsed from the
command line by argparse.
:param Namespace namespace: The result from CLI parsing.
:returns: An auth plugin, or None if a name is not provided.
:rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin`
:raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin:
if a plugin cannot be created.
"""
if not namespace.os_auth_type:
return None
if isinstance(namespace.os_auth_type, type):
plugin = namespace.os_auth_type
else:
plugin = base.get_plugin_loader(namespace.os_auth_type)
def _getter(opt):
return getattr(namespace, 'os_%s' % opt.dest)
return plugin.load_from_options_getter(_getter, **kwargs)

View File

@ -1,135 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1.loading import base
from keystoneauth1.loading import opts
_AUTH_TYPE_OPT = opts.Opt('auth_type',
deprecated=[opts.Opt('auth_plugin')],
help='Authentication type to load')
_section_help = 'Config Section from which to load plugin specific options'
_AUTH_SECTION_OPT = opts.Opt('auth_section', help=_section_help)
__all__ = ('get_common_conf_options',
'get_plugin_conf_options',
'register_conf_options',
'load_from_conf_options')
def get_common_conf_options():
"""Get the oslo_config options common for all auth plugins.
These may be useful without being registered for config file generation
or to manipulate the options before registering them yourself.
The options that are set are:
:auth_type: The name of the plugin to load.
:auth_section: The config file section to load options from.
:returns: A list of oslo_config options.
"""
return [_AUTH_TYPE_OPT._to_oslo_opt(), _AUTH_SECTION_OPT._to_oslo_opt()]
def get_plugin_conf_options(plugin):
"""Get the oslo_config options for a specific plugin.
This will be the list of config options that is registered and loaded by
the specified plugin.
:param plugin: The name of the plugin loader or a plugin loader object
:type plugin: str or keystoneauth1._loading.BaseLoader
:returns: A list of oslo_config options.
"""
try:
getter = plugin.get_options
except AttributeError:
opts = base.get_plugin_options(plugin)
else:
opts = getter()
return [o._to_oslo_opt() for o in opts]
def register_conf_options(conf, group):
"""Register the oslo_config options that are needed for a plugin.
This only registers the basic options shared by all plugins. Options that
are specific to a plugin are loaded just before they are read.
The defined options are:
- auth_type: the name of the auth plugin that will be used for
authentication.
- auth_section: the group from which further auth plugin options should be
taken. If section is not provided then the auth plugin options will be
taken from the same group as provided in the parameters.
:param conf: config object to register with.
:type conf: oslo_config.cfg.ConfigOpts
:param string group: The ini group to register options in.
"""
conf.register_opt(_AUTH_SECTION_OPT._to_oslo_opt(), group=group)
# NOTE(jamielennox): plugins are allowed to specify a 'section' which is
# the group that auth options should be taken from. If not present they
# come from the same as the base options were registered in. If present
# then the auth_plugin option may be read from that section so add that
# option.
if conf[group].auth_section:
group = conf[group].auth_section
conf.register_opt(_AUTH_TYPE_OPT._to_oslo_opt(), group=group)
def load_from_conf_options(conf, group, **kwargs):
"""Load a plugin from an oslo_config CONF object.
Each plugin will register their own required options and so there is no
standard list and the plugin should be consulted.
The base options should have been registered with register_conf_options
before this function is called.
:param conf: A conf object.
:type conf: oslo_config.cfg.ConfigOpts
:param str group: The group name that options should be read from.
:returns: An authentication Plugin or None if a name is not provided
:rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin`
:raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin:
if a plugin cannot be created.
"""
# NOTE(jamielennox): plugins are allowed to specify a 'section' which is
# the group that auth options should be taken from. If not present they
# come from the same as the base options were registered in.
if conf[group].auth_section:
group = conf[group].auth_section
name = conf[group].auth_type
if not name:
return None
plugin = base.get_plugin_loader(name)
plugin_opts = plugin.get_options()
oslo_opts = [o._to_oslo_opt() for o in plugin_opts]
conf.register_opts(oslo_opts, group=group)
def _getter(opt):
return conf[group][opt.dest]
return plugin.load_from_options_getter(_getter, **kwargs)

View File

@ -1,162 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import exceptions
from keystoneauth1.loading import base
from keystoneauth1.loading import opts
__all__ = ('BaseIdentityLoader',
'BaseV2Loader',
'BaseV3Loader',
'BaseFederationLoader',
'BaseGenericLoader')
class BaseIdentityLoader(base.BaseLoader):
"""Base Option handling for identity plugins.
This class defines options and handling that should be common across all
plugins that are developed against the OpenStack identity service. It
provides the options expected by the
:py:class:`keystoneauth1.identity.BaseIdentityPlugin` class.
"""
def get_options(self):
options = super(BaseIdentityLoader, self).get_options()
options.extend([
opts.Opt('auth-url',
required=True,
help='Authentication URL'),
])
return options
class BaseV2Loader(BaseIdentityLoader):
"""Base Option handling for identity plugins.
This class defines options and handling that should be common to the V2
identity API. It provides the options expected by the
:py:class:`keystoneauth1.identity.v2.Auth` class.
"""
def get_options(self):
options = super(BaseV2Loader, self).get_options()
options.extend([
opts.Opt('tenant-id', help='Tenant ID'),
opts.Opt('tenant-name', help='Tenant Name'),
opts.Opt('trust-id', help='Trust ID'),
])
return options
class BaseV3Loader(BaseIdentityLoader):
"""Base Option handling for identity plugins.
This class defines options and handling that should be common to the V3
identity API. It provides the options expected by the
:py:class:`keystoneauth1.identity.v3.Auth` class.
"""
def get_options(self):
options = super(BaseV3Loader, self).get_options()
options.extend([
opts.Opt('domain-id', help='Domain ID to scope to'),
opts.Opt('domain-name', help='Domain name to scope to'),
opts.Opt('project-id', help='Project ID to scope to'),
opts.Opt('project-name', help='Project name to scope to'),
opts.Opt('project-domain-id',
help='Domain ID containing project'),
opts.Opt('project-domain-name',
help='Domain name containing project'),
opts.Opt('trust-id', help='Trust ID'),
])
return options
def load_from_options(self, **kwargs):
if (kwargs.get('project_name') and
not (kwargs.get('project_domain_name') or
kwargs.get('project_domain_id'))):
m = "You have provided a project_name. In the V3 identity API a " \
"project_name is only unique within a domain so you must " \
"also provide either a project_domain_id or " \
"project_domain_name."
raise exceptions.OptionError(m)
return super(BaseV3Loader, self).load_from_options(**kwargs)
class BaseFederationLoader(BaseV3Loader):
"""Base Option handling for federation plugins.
This class defines options and handling that should be common to the V3
identity federation API. It provides the options expected by the
:py:class:`keystoneauth1.identity.v3.FederationBaseAuth` class.
"""
def get_options(self):
options = super(BaseFederationLoader, self).get_options()
options.extend([
opts.Opt('identity-provider',
help="Identity Provider's name",
required=True),
opts.Opt('protocol',
help='Protocol for federated plugin',
required=True),
])
return options
class BaseGenericLoader(BaseIdentityLoader):
"""Base Option handling for generic plugins.
This class defines options and handling that should be common to generic
plugins. These plugins target the OpenStack identity service however are
designed to be independent of API version. It provides the options expected
by the :py:class:`keystoneauth1.identity.v3.BaseGenericPlugin` class.
"""
def get_options(self):
options = super(BaseGenericLoader, self).get_options()
options.extend([
opts.Opt('domain-id', help='Domain ID to scope to'),
opts.Opt('domain-name', help='Domain name to scope to'),
opts.Opt('project-id', help='Project ID to scope to',
deprecated=[opts.Opt('tenant-id')]),
opts.Opt('project-name', help='Project name to scope to',
deprecated=[opts.Opt('tenant-name')]),
opts.Opt('project-domain-id',
help='Domain ID containing project'),
opts.Opt('project-domain-name',
help='Domain name containing project'),
opts.Opt('trust-id', help='Trust ID'),
opts.Opt('default-domain-id',
help='Optional domain ID to use with v3 and v2 '
'parameters. It will be used for both the user '
'and project domain in v3 and ignored in '
'v2 authentication.'),
opts.Opt('default-domain-name',
help='Optional domain name to use with v3 API and v2 '
'parameters. It will be used for both the user '
'and project domain in v3 and ignored in '
'v2 authentication.'),
])
return options

View File

@ -1,151 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import os
from positional import positional
from keystoneauth1.loading import _utils
__all__ = ('Opt',)
class Opt(object):
"""An option required by an authentication plugin.
Opts provide a means for authentication plugins that are going to be
dynamically loaded to specify the parameters that are to be passed to the
plugin on initialization.
The Opt specifies information about the way the plugin parameter is to be
represented in different loading mechanisms.
When defining an Opt with a - the - should be present in the name
parameter. This will automatically be converted to an _ when passing to the
plugin initialization. For example, you should specify::
Opt('user-domain-id')
which will pass the value as `user_domain_id` to the plugin's
initialization.
:param str name: The name of the option.
:param callable type: The type of the option. This is a callable which is
passed the raw option that was loaded (often a string) and is required
to return the parameter in the type expected by __init__.
:param str help: The help text that is shown along with the option.
:param bool secret: If the parameter is secret it should not be printed or
logged in debug output.
:param str dest: the name of the argument that will be passed to __init__.
This allows you to have a different name in loading than is used by the
__init__ function. Defaults to the value of name.
:param keystoneauth1.loading.Opt: A list of other options that are
deprecated in favour of this one. This ensures the old options are
still registered.
:type opt: list(Opt)
:param default: A default value that can be used if one is not provided.
:param str metavar: The <metavar> that should be printed in CLI help text.
:param bool required: If the option is required to load the plugin. If a
required option is not present loading should fail.
:param str prompt: If the option can be requested via a prompt (where
appropriate) set the string that should be used to prompt with.
"""
@positional()
def __init__(self,
name,
type=str,
help=None,
secret=False,
dest=None,
deprecated=None,
default=None,
metavar=None,
required=False,
prompt=None):
if not callable(type):
raise TypeError('type must be callable')
if dest is None:
dest = name.replace('-', '_')
self.name = name
self.type = type
self.help = help
self.secret = secret
self.required = required
self.dest = dest
self.deprecated = [] if deprecated is None else deprecated
self.default = default
self.metavar = metavar
self.prompt = prompt
# These are for oslo.config compat
self.deprecated_opts = self.deprecated
self.deprecated_for_removal = []
self.sample_default = None
self.group = None
def __repr__(self):
"""Return string representation of option name."""
return '<Opt: %s>' % self.name
def _to_oslo_opt(self):
cfg = _utils.get_oslo_config()
deprecated_opts = [cfg.DeprecatedOpt(o.name) for o in self.deprecated]
return cfg.Opt(name=self.name,
type=self.type,
help=self.help,
secret=self.secret,
required=self.required,
dest=self.dest,
deprecated_opts=deprecated_opts,
metavar=self.metavar)
def __eq__(self, other):
"""Define equality operator on option parameters."""
return (type(self) == type(other) and
self.name == other.name and
self.type == other.type and
self.help == other.help and
self.secret == other.secret and
self.required == other.required and
self.dest == other.dest and
self.deprecated == other.deprecated and
self.default == other.default and
self.metavar == other.metavar)
# NOTE: This function is only needed by Python 2. If we get to point where
# we don't support Python 2 anymore, this function should be removed.
def __ne__(self, other):
"""Define inequality operator on option parameters."""
return not self.__eq__(other)
@property
def _all_opts(self):
return itertools.chain([self], self.deprecated)
@property
def argparse_args(self):
return ['--os-%s' % o.name for o in self._all_opts]
@property
def argparse_default(self):
# select the first ENV that is not false-y or return None
for o in self._all_opts:
v = os.environ.get('OS_%s' % o.name.replace('-', '_').upper())
if v:
return v
return self.default

View File

@ -1,254 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
from positional import positional
from keystoneauth1.loading import _utils
from keystoneauth1.loading import base
from keystoneauth1 import session
__all__ = ('register_argparse_arguments',
'load_from_argparse_arguments',
'register_conf_options',
'load_from_conf_options',
'get_conf_options')
def _positive_non_zero_float(argument_value):
if argument_value is None:
return None
try:
value = float(argument_value)
except ValueError:
msg = "%s must be a float" % argument_value
raise argparse.ArgumentTypeError(msg)
if value <= 0:
msg = "%s must be greater than 0" % argument_value
raise argparse.ArgumentTypeError(msg)
return value
class Session(base.BaseLoader):
@property
def plugin_class(self):
return session.Session
def get_options(self):
return []
@positional(1)
def load_from_options(self,
insecure=False,
verify=None,
cacert=None,
cert=None,
key=None,
**kwargs):
"""Create a session with individual certificate parameters.
Some parameters used to create a session don't lend themselves to be
loaded from config/CLI etc. Create a session by converting those
parameters into session __init__ parameters.
"""
if verify is None:
if insecure:
verify = False
else:
verify = cacert or True
if cert and key:
# passing cert and key together is deprecated in favour of the
# requests lib form of having the cert and key as a tuple
cert = (cert, key)
return super(Session, self).load_from_options(verify=verify,
cert=cert,
**kwargs)
def register_argparse_arguments(self, parser):
session_group = parser.add_argument_group(
'API Connection Options',
'Options controlling the HTTP API Connections')
session_group.add_argument(
'--insecure',
default=False,
action='store_true',
help='Explicitly allow client to perform '
'"insecure" TLS (https) requests. The '
'server\'s certificate will not be verified '
'against any certificate authorities. This '
'option should be used with caution.')
session_group.add_argument(
'--os-cacert',
metavar='<ca-certificate>',
default=os.environ.get('OS_CACERT'),
help='Specify a CA bundle file to use in '
'verifying a TLS (https) server certificate. '
'Defaults to env[OS_CACERT].')
session_group.add_argument(
'--os-cert',
metavar='<certificate>',
default=os.environ.get('OS_CERT'),
help='Defaults to env[OS_CERT].')
session_group.add_argument(
'--os-key',
metavar='<key>',
default=os.environ.get('OS_KEY'),
help='Defaults to env[OS_KEY].')
session_group.add_argument(
'--timeout',
default=600,
type=_positive_non_zero_float,
metavar='<seconds>',
help='Set request timeout (in seconds).')
def load_from_argparse_arguments(self, namespace, **kwargs):
kwargs.setdefault('insecure', namespace.insecure)
kwargs.setdefault('cacert', namespace.os_cacert)
kwargs.setdefault('cert', namespace.os_cert)
kwargs.setdefault('key', namespace.os_key)
kwargs.setdefault('timeout', namespace.timeout)
return self.load_from_options(**kwargs)
def get_conf_options(self, deprecated_opts=None):
"""Get oslo_config options that are needed for a :py:class:`.Session`.
These may be useful without being registered for config file generation
or to manipulate the options before registering them yourself.
The options that are set are:
:cafile: The certificate authority filename.
:certfile: The client certificate file to present.
:keyfile: The key for the client certificate.
:insecure: Whether to ignore SSL verification.
:timeout: The max time to wait for HTTP connections.
:param dict deprecated_opts: Deprecated options that should be included
in the definition of new options. This should be a dict from the
name of the new option to a list of oslo.DeprecatedOpts that
correspond to the new option. (optional)
For example, to support the ``ca_file`` option pointing to the new
``cafile`` option name::
old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group')
deprecated_opts={'cafile': [old_opt]}
:returns: A list of oslo_config options.
"""
cfg = _utils.get_oslo_config()
if deprecated_opts is None:
deprecated_opts = {}
return [cfg.StrOpt('cafile',
deprecated_opts=deprecated_opts.get('cafile'),
help='PEM encoded Certificate Authority to use '
'when verifying HTTPs connections.'),
cfg.StrOpt('certfile',
deprecated_opts=deprecated_opts.get('certfile'),
help='PEM encoded client certificate cert file'),
cfg.StrOpt('keyfile',
deprecated_opts=deprecated_opts.get('keyfile'),
help='PEM encoded client certificate key file'),
cfg.BoolOpt('insecure',
default=False,
deprecated_opts=deprecated_opts.get('insecure'),
help='Verify HTTPS connections.'),
cfg.IntOpt('timeout',
deprecated_opts=deprecated_opts.get('timeout'),
help='Timeout value for http requests'),
]
def register_conf_options(self, conf, group, deprecated_opts=None):
"""Register the oslo_config options that are needed for a session.
The options that are set are:
:cafile: The certificate authority filename.
:certfile: The client certificate file to present.
:keyfile: The key for the client certificate.
:insecure: Whether to ignore SSL verification.
:timeout: The max time to wait for HTTP connections.
:param oslo_config.Cfg conf: config object to register with.
:param string group: The ini group to register options in.
:param dict deprecated_opts: Deprecated options that should be included
in the definition of new options. This should be a dict from the
name of the new option to a list of oslo.DeprecatedOpts that
correspond to the new option. (optional)
For example, to support the ``ca_file`` option pointing to the new
``cafile`` option name::
old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group')
deprecated_opts={'cafile': [old_opt]}
:returns: The list of options that was registered.
"""
opts = self.get_conf_options(deprecated_opts=deprecated_opts)
conf.register_group(_utils.get_oslo_config().OptGroup(group))
conf.register_opts(opts, group=group)
return opts
def load_from_conf_options(self, conf, group, **kwargs):
"""Create a session object from an oslo_config object.
The options must have been previously registered with
register_conf_options.
:param oslo_config.Cfg conf: config object to register with.
:param string group: The ini group to register options in.
:param dict kwargs: Additional parameters to pass to session
construction.
:returns: A new session object.
:rtype: :py:class:`.Session`
"""
c = conf[group]
kwargs.setdefault('insecure', c.insecure)
kwargs.setdefault('cacert', c.cafile)
kwargs.setdefault('cert', c.certfile)
kwargs.setdefault('key', c.keyfile)
kwargs.setdefault('timeout', c.timeout)
return self.load_from_options(**kwargs)
def register_argparse_arguments(*args, **kwargs):
return Session().register_argparse_arguments(*args, **kwargs)
def load_from_argparse_arguments(*args, **kwargs):
return Session().load_from_argparse_arguments(*args, **kwargs)
def register_conf_options(*args, **kwargs):
return Session().register_conf_options(*args, **kwargs)
def load_from_conf_options(*args, **kwargs):
return Session().load_from_conf_options(*args, **kwargs)
def get_conf_options(*args, **kwargs):
return Session().get_conf_options(*args, **kwargs)

View File

@ -1,24 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import plugin
class NoAuth(plugin.BaseAuthPlugin):
"""A provider that will always use no auth.
This is useful to unify session/adapter loading for services
that might be deployed in standalone/noauth mode.
"""
def get_token(self, session):
return 'notused'

Some files were not shown because too many files have changed in this diff Show More