Revert "Adopt pre-commit"
This reverts commit 40379eb763. This
change includes other, unrelated changes which should be reviewed on
their own merits. Let's pull this back out and re-try.
Change-Id: Ib813cd9e9dba24024870b85487ed27e6eec144dd
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
49 files changed
+4790
-5699
No files matched your search
@@ -1,25 +0,0 @@
|
||||
---
|
||||
default_language_version:
|
||||
# force all unspecified python hooks to run python3
|
||||
python: python3
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: mixed-line-ending
|
||||
args: ['--fix', 'lf']
|
||||
exclude: '.*\.(svg)$'
|
||||
- id: check-byte-order-marker
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: debug-statements
|
||||
- id: check-yaml
|
||||
files: .*\.(yaml|yml)$
|
||||
exclude: '^zuul.d/.*$'
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.16.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ['--fix', '--unsafe-fixes']
|
||||
- id: ruff-format
|
||||
+7
-14
@@ -34,20 +34,13 @@ def run_apidoc(app):
|
||||
return
|
||||
run_already = True
|
||||
|
||||
package_dir = path.abspath(path.join(app.srcdir, "..", "..", "keystonemiddleware"))
|
||||
source_dir = path.join(app.srcdir, "api")
|
||||
apidoc.main(
|
||||
[
|
||||
"apidoc",
|
||||
package_dir,
|
||||
"-f",
|
||||
"-H",
|
||||
"keystonemiddleware Modules",
|
||||
"-o",
|
||||
source_dir,
|
||||
]
|
||||
)
|
||||
package_dir = path.abspath(path.join(app.srcdir, '..', '..',
|
||||
'keystonemiddleware'))
|
||||
source_dir = path.join(app.srcdir, 'api')
|
||||
apidoc.main(['apidoc', package_dir, '-f',
|
||||
'-H', 'keystonemiddleware Modules',
|
||||
'-o', source_dir])
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect("builder-inited", run_apidoc)
|
||||
app.connect('builder-inited', run_apidoc)
|
||||
+79
-77
@@ -14,72 +14,78 @@
|
||||
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__),
|
||||
'..', '..')))
|
||||
|
||||
# NOTE(blk-u): Path for our Sphinx extension, remove when
|
||||
# https://launchpad.net/bugs/1260495 is fixed.
|
||||
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('.'))
|
||||
#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.todo",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.intersphinx",
|
||||
"openstackdocstheme",
|
||||
"oslo_config.sphinxconfiggen",
|
||||
"sphinxcontrib.apidoc",
|
||||
"sphinxcontrib.rsvgconverter",
|
||||
]
|
||||
extensions = ['sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.intersphinx',
|
||||
'openstackdocstheme',
|
||||
'oslo_config.sphinxconfiggen',
|
||||
'sphinxcontrib.apidoc',
|
||||
'sphinxcontrib.rsvgconverter',
|
||||
]
|
||||
|
||||
# sphinxcontrib.apidoc options
|
||||
apidoc_module_dir = "../../keystonemiddleware"
|
||||
apidoc_output_dir = "api"
|
||||
apidoc_excluded_paths = ["tests/*", "tests", "test"]
|
||||
apidoc_module_dir = '../../keystonemiddleware'
|
||||
apidoc_output_dir = 'api'
|
||||
apidoc_excluded_paths = [
|
||||
'tests/*',
|
||||
'tests',
|
||||
'test']
|
||||
apidoc_separate_modules = True
|
||||
|
||||
config_generator_config_file = "../../config-generator/keystonemiddleware.conf"
|
||||
sample_config_basename = "_static/keystonemiddleware"
|
||||
config_generator_config_file = '../../config-generator/keystonemiddleware.conf'
|
||||
sample_config_basename = '_static/keystonemiddleware'
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['_templates']
|
||||
#templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = ".rst"
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8'
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
copyright = "OpenStack Contributors"
|
||||
copyright = 'OpenStack Contributors'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
# unused_docs = []
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
@@ -87,7 +93,7 @@ exclude_trees = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
@@ -98,110 +104,107 @@ add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "native"
|
||||
pygments_style = 'native'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ["keystonemiddleware."]
|
||||
modindex_common_prefix = ['keystonemiddleware.']
|
||||
|
||||
# Grouping the document tree for man pages.
|
||||
# List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual'
|
||||
|
||||
# man_pages = []
|
||||
#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"
|
||||
#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 = {}
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
#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
|
||||
#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
|
||||
#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']
|
||||
#html_static_path = ['_static']
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_use_modindex = True
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
#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 = ''
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = ''
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "keystonemiddlewaredoc"
|
||||
htmlhelp_basename = 'keystonemiddlewaredoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
# latex_paper_size = 'letter'
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# latex_font_size = '10pt'
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual])
|
||||
# .
|
||||
latex_documents = [
|
||||
(
|
||||
"index",
|
||||
"doc-keystonemiddleware.tex",
|
||||
"keystonemiddleware Documentation",
|
||||
"Openstack Developers",
|
||||
"manual",
|
||||
),
|
||||
('index', 'doc-keystonemiddleware.tex',
|
||||
u'keystonemiddleware Documentation',
|
||||
u'Openstack Developers',
|
||||
'manual'),
|
||||
]
|
||||
|
||||
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
|
||||
@@ -210,37 +213,36 @@ latex_use_xindy = False
|
||||
latex_domain_indices = False
|
||||
|
||||
latex_elements = {
|
||||
"extraclassoptions": "openany",
|
||||
"makeindex": "",
|
||||
"printindex": "",
|
||||
"preamble": r"\setcounter{tocdepth}{3}",
|
||||
"maxlistdepth": 10,
|
||||
'extraclassoptions': 'openany',
|
||||
'makeindex': '',
|
||||
'printindex': '',
|
||||
'preamble': r'\setcounter{tocdepth}{3}',
|
||||
'maxlistdepth': 10,
|
||||
}
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# latex_preamble = ''
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_use_modindex = True
|
||||
#latex_use_modindex = True
|
||||
|
||||
keystoneclient = "https://docs.openstack.org/python-keystoneclient/latest/"
|
||||
keystoneclient = 'https://docs.openstack.org/python-keystoneclient/latest/'
|
||||
|
||||
intersphinx_mapping = {
|
||||
"keystoneclient": (keystoneclient, None),
|
||||
}
|
||||
intersphinx_mapping = {'keystoneclient': (keystoneclient, None),
|
||||
}
|
||||
|
||||
# -- Options for openstackdocstheme -------------------------------------------
|
||||
openstackdocs_repo_name = "openstack/keystonemiddleware"
|
||||
openstackdocs_bug_project = "keystonemiddleware"
|
||||
openstackdocs_bug_tag = ""
|
||||
openstackdocs_repo_name = 'openstack/keystonemiddleware'
|
||||
openstackdocs_bug_project = 'keystonemiddleware'
|
||||
openstackdocs_bug_tag = ''
|
||||
openstackdocs_pdf_link = True
|
||||
@@ -38,7 +38,7 @@ def _conf_values_type_convert(group_name, all_options, conf):
|
||||
continue
|
||||
|
||||
for o in options:
|
||||
type_dest = (getattr(o, "type", str), o.dest)
|
||||
type_dest = (getattr(o, 'type', str), o.dest)
|
||||
opt_types[o.dest] = type_dest
|
||||
# Also add the deprecated name with the same type and dest.
|
||||
for d_o in o.deprecated_opts:
|
||||
@@ -51,34 +51,34 @@ def _conf_values_type_convert(group_name, all_options, conf):
|
||||
try:
|
||||
# 'here' and '__file__' come from paste.deploy
|
||||
# 'configkey' is added by panko and gnocchi
|
||||
if v is not None and k not in ["here", "__file__", "configkey"]:
|
||||
if v is not None and k not in ['here', '__file__', 'configkey']:
|
||||
type_, dest = opt_types[k]
|
||||
v = type_(v)
|
||||
except KeyError: # nosec
|
||||
_LOG.warning('The option "%s" is not known to keystonemiddleware', k)
|
||||
_LOG.warning(
|
||||
'The option "%s" is not known to keystonemiddleware', k)
|
||||
except ValueError as e:
|
||||
raise exceptions.ConfigurationError(
|
||||
_(
|
||||
'Unable to convert the value of option "%(key)s" into '
|
||||
"correct type: %(ex)s"
|
||||
)
|
||||
% {"key": k, "ex": e}
|
||||
)
|
||||
_('Unable to convert the value of option "%(key)s" into '
|
||||
'correct type: %(ex)s') % {'key': k, 'ex': e})
|
||||
opts[dest] = v
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
class Config(object):
|
||||
|
||||
def __init__(self, name, group_name, all_options, conf):
|
||||
local_oslo_config = conf.pop("oslo_config_config", None)
|
||||
local_config_project = conf.pop("oslo_config_project", None)
|
||||
local_config_file = conf.pop("oslo_config_file", None)
|
||||
local_oslo_config = conf.pop('oslo_config_config', None)
|
||||
local_config_project = conf.pop('oslo_config_project', None)
|
||||
local_config_file = conf.pop('oslo_config_file', None)
|
||||
|
||||
# NOTE(wanghong): If options are set in paste file, all the option
|
||||
# values passed into conf are string type. So, we should convert the
|
||||
# conf value into correct type.
|
||||
self.paste_overrides = _conf_values_type_convert(group_name, all_options, conf)
|
||||
self.paste_overrides = _conf_values_type_convert(group_name,
|
||||
all_options,
|
||||
conf)
|
||||
|
||||
# NOTE(sileht, cdent): If we don't want to use oslo.config global
|
||||
# object there are two options: set "oslo_config_project" in
|
||||
@@ -91,12 +91,10 @@ class Config(object):
|
||||
config_files = [local_config_file] if local_config_file else None
|
||||
|
||||
local_oslo_config = cfg.ConfigOpts()
|
||||
local_oslo_config(
|
||||
[],
|
||||
project=local_config_project,
|
||||
default_config_files=config_files,
|
||||
validate_default_values=True,
|
||||
)
|
||||
local_oslo_config([],
|
||||
project=local_config_project,
|
||||
default_config_files=config_files,
|
||||
validate_default_values=True)
|
||||
|
||||
if local_oslo_config:
|
||||
for group, opts in all_options:
|
||||
@@ -129,7 +127,7 @@ class Config(object):
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.get("project", group=self.group_name)
|
||||
return self.get('project', group=self.group_name)
|
||||
except cfg.NoSuchOptError:
|
||||
try:
|
||||
# CONF.project will exist only if the service uses
|
||||
@@ -143,7 +141,7 @@ class Config(object):
|
||||
@property
|
||||
def user_agent(self):
|
||||
if not self._user_agent:
|
||||
project = self.project or ""
|
||||
project = self.project or ''
|
||||
|
||||
if project:
|
||||
try:
|
||||
@@ -156,7 +154,6 @@ class Config(object):
|
||||
self._user_agent = "%skeystonemiddleware.%s/%s" % (
|
||||
project,
|
||||
self.name,
|
||||
pbr.version.VersionInfo("keystonemiddleware").version_string(),
|
||||
)
|
||||
pbr.version.VersionInfo('keystonemiddleware').version_string())
|
||||
|
||||
return self._user_agent
|
||||
@@ -39,38 +39,30 @@ from keystonemiddleware.audit import _notifier
|
||||
|
||||
|
||||
_LOG = None
|
||||
AUDIT_MIDDLEWARE_GROUP = "audit_middleware_notifications"
|
||||
AUDIT_MIDDLEWARE_GROUP = 'audit_middleware_notifications'
|
||||
|
||||
_AUDIT_OPTS = [
|
||||
cfg.BoolOpt(
|
||||
"use_oslo_messaging",
|
||||
default=True,
|
||||
help="Indicate whether to use oslo_messaging as the notifier. "
|
||||
"If set to False, the local logger will be used as the "
|
||||
"notifier. If set to True, the oslo_messaging package "
|
||||
"must also be present. Otherwise, the local will be used "
|
||||
"instead.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"driver",
|
||||
help="The Driver to handle sending notifications. Possible "
|
||||
"values are messaging, messagingv2, routing, log, test, "
|
||||
"noop. If not specified, then value from "
|
||||
"oslo_messaging_notifications conf section is used.",
|
||||
),
|
||||
cfg.ListOpt(
|
||||
"topics",
|
||||
help="List of AMQP topics used for OpenStack notifications. If"
|
||||
" not specified, then value from "
|
||||
" oslo_messaging_notifications conf section is used.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"transport_url",
|
||||
secret=True,
|
||||
help="A URL representing messaging driver to use for "
|
||||
"notification. If not specified, we fall back to the same "
|
||||
"configuration used for RPC.",
|
||||
),
|
||||
cfg.BoolOpt('use_oslo_messaging',
|
||||
default=True,
|
||||
help='Indicate whether to use oslo_messaging as the notifier. '
|
||||
'If set to False, the local logger will be used as the '
|
||||
'notifier. If set to True, the oslo_messaging package '
|
||||
'must also be present. Otherwise, the local will be used '
|
||||
'instead.'),
|
||||
cfg.StrOpt('driver',
|
||||
help='The Driver to handle sending notifications. Possible '
|
||||
'values are messaging, messagingv2, routing, log, test, '
|
||||
'noop. If not specified, then value from '
|
||||
'oslo_messaging_notifications conf section is used.'),
|
||||
cfg.ListOpt('topics',
|
||||
help='List of AMQP topics used for OpenStack notifications. If'
|
||||
' not specified, then value from '
|
||||
' oslo_messaging_notifications conf section is used.'),
|
||||
cfg.StrOpt('transport_url',
|
||||
secret=True,
|
||||
help='A URL representing messaging driver to use for '
|
||||
'notification. If not specified, we fall back to the same '
|
||||
'configuration used for RPC.'),
|
||||
]
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(_AUDIT_OPTS, group=AUDIT_MIDDLEWARE_GROUP)
|
||||
@@ -82,8 +74,8 @@ def _log_and_ignore_error(fn):
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
except Exception as e:
|
||||
_LOG.exception("An exception occurred processing the API call: %s ", e)
|
||||
|
||||
_LOG.exception('An exception occurred processing '
|
||||
'the API call: %s ', e)
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -98,39 +90,40 @@ class AuditMiddleware(object):
|
||||
|
||||
def __init__(self, app, **conf):
|
||||
self._application = app
|
||||
self._conf = config.Config("audit", AUDIT_MIDDLEWARE_GROUP, list_opts(), conf)
|
||||
self._conf = config.Config('audit',
|
||||
AUDIT_MIDDLEWARE_GROUP,
|
||||
list_opts(),
|
||||
conf)
|
||||
global _LOG
|
||||
_LOG = logging.getLogger(conf.get("log_name", __name__))
|
||||
self._service_name = conf.get("service_name")
|
||||
self._ignore_req_list = [
|
||||
x.upper().strip() for x in conf.get("ignore_req_list", "").split(",")
|
||||
]
|
||||
self._ignore_path_list = [
|
||||
x for x in conf.get("ignore_path_list", "").split(",") if x
|
||||
]
|
||||
self._cadf_audit = _api.OpenStackAuditApi(conf.get("audit_map_file"), _LOG)
|
||||
_LOG = logging.getLogger(conf.get('log_name', __name__))
|
||||
self._service_name = conf.get('service_name')
|
||||
self._ignore_req_list = [x.upper().strip() for x in
|
||||
conf.get('ignore_req_list', '').split(',')]
|
||||
self._ignore_path_list = [x for x in
|
||||
conf.get('ignore_path_list', '').split(',')
|
||||
if x]
|
||||
self._cadf_audit = _api.OpenStackAuditApi(conf.get('audit_map_file'),
|
||||
_LOG)
|
||||
self._notifier = _notifier.create_notifier(self._conf, _LOG)
|
||||
|
||||
def _create_event(self, req):
|
||||
event = self._cadf_audit._create_event(req)
|
||||
# cache model in request to allow tracking of transistive steps.
|
||||
req.environ["cadf_event"] = event
|
||||
req.environ['cadf_event'] = event
|
||||
return event
|
||||
|
||||
@_log_and_ignore_error
|
||||
def _process_request(self, request):
|
||||
self._notifier.notify(
|
||||
request.environ["audit.context"],
|
||||
"audit.http.request",
|
||||
self._create_event(request).as_dict(),
|
||||
)
|
||||
self._notifier.notify(request.environ['audit.context'],
|
||||
'audit.http.request',
|
||||
self._create_event(request).as_dict())
|
||||
|
||||
@_log_and_ignore_error
|
||||
def _process_response(self, request, response=None):
|
||||
# NOTE(gordc): handle case where error processing request
|
||||
if "cadf_event" not in request.environ:
|
||||
if 'cadf_event' not in request.environ:
|
||||
self._create_event(request)
|
||||
event = request.environ["cadf_event"]
|
||||
event = request.environ['cadf_event']
|
||||
|
||||
if response:
|
||||
if response.status_int >= 200 and response.status_int < 400:
|
||||
@@ -138,8 +131,7 @@ class AuditMiddleware(object):
|
||||
else:
|
||||
result = taxonomy.OUTCOME_FAILURE
|
||||
event.reason = reason.Reason(
|
||||
reasonType="HTTP", reasonCode=str(response.status_int)
|
||||
)
|
||||
reasonType='HTTP', reasonCode=str(response.status_int))
|
||||
else:
|
||||
result = taxonomy.UNKNOWN
|
||||
|
||||
@@ -147,27 +139,26 @@ class AuditMiddleware(object):
|
||||
event.add_reporterstep(
|
||||
reporterstep.Reporterstep(
|
||||
role=cadftype.REPORTER_ROLE_MODIFIER,
|
||||
reporter=resource.Resource(id="target"),
|
||||
reporterTime=timestamp.get_utc_now(),
|
||||
)
|
||||
)
|
||||
reporter=resource.Resource(id='target'),
|
||||
reporterTime=timestamp.get_utc_now()))
|
||||
|
||||
self._notifier.notify(
|
||||
request.environ["audit.context"], "audit.http.response", event.as_dict()
|
||||
)
|
||||
self._notifier.notify(request.environ['audit.context'],
|
||||
'audit.http.response',
|
||||
event.as_dict())
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
if (
|
||||
req.method in self._ignore_req_list
|
||||
or req.path_info in self._ignore_path_list
|
||||
req.method in self._ignore_req_list or
|
||||
req.path_info in self._ignore_path_list
|
||||
):
|
||||
return req.get_response(self._application)
|
||||
|
||||
# Cannot use a RequestClass on wsgify above because the `req` object is
|
||||
# a `WebOb.Request` when this method is called so the RequestClass is
|
||||
# ignored by the wsgify wrapper.
|
||||
req.environ["audit.context"] = oslo_context.get_admin_context().to_dict()
|
||||
req.environ['audit.context'] = \
|
||||
oslo_context.get_admin_context().to_dict()
|
||||
|
||||
self._process_request(req)
|
||||
try:
|
||||
@@ -203,7 +194,6 @@ def filter_factory(global_conf, **local_conf):
|
||||
|
||||
def audit_filter(app):
|
||||
return AuditMiddleware(app, **conf)
|
||||
|
||||
return audit_filter
|
||||
|
||||
|
||||
|
||||
@@ -28,15 +28,16 @@ from pycadf import tag
|
||||
from urllib import parse as urlparse
|
||||
|
||||
|
||||
Service = collections.namedtuple(
|
||||
"Service", ["id", "name", "type", "admin_endp", "public_endp", "private_endp"]
|
||||
)
|
||||
Service = collections.namedtuple('Service',
|
||||
['id', 'name', 'type', 'admin_endp',
|
||||
'public_endp', 'private_endp'])
|
||||
|
||||
|
||||
AuditMap = collections.namedtuple(
|
||||
"AuditMap",
|
||||
["path_kw", "custom_actions", "service_endpoints", "default_target_endpoint_type"],
|
||||
)
|
||||
AuditMap = collections.namedtuple('AuditMap',
|
||||
['path_kw',
|
||||
'custom_actions',
|
||||
'service_endpoints',
|
||||
'default_target_endpoint_type'])
|
||||
|
||||
|
||||
class PycadfAuditApiConfigError(Exception):
|
||||
@@ -46,9 +47,8 @@ class PycadfAuditApiConfigError(Exception):
|
||||
|
||||
|
||||
class ClientResource(resource.Resource):
|
||||
def __init__(
|
||||
self, project_id=None, request_id=None, global_request_id=None, **kwargs
|
||||
):
|
||||
def __init__(self, project_id=None, request_id=None,
|
||||
global_request_id=None, **kwargs):
|
||||
super(ClientResource, self).__init__(**kwargs)
|
||||
if project_id is not None:
|
||||
self.project_id = project_id
|
||||
@@ -66,6 +66,7 @@ class KeystoneCredential(credential.Credential):
|
||||
|
||||
|
||||
class OpenStackAuditApi(object):
|
||||
|
||||
def __init__(self, cfg_file, log=logging.getLogger(__name__)):
|
||||
"""Configure to recognize and map known api paths."""
|
||||
path_kw = {}
|
||||
@@ -81,47 +82,43 @@ class OpenStackAuditApi(object):
|
||||
|
||||
try:
|
||||
default_target_endpoint_type = map_conf.get(
|
||||
"DEFAULT", "target_endpoint_type"
|
||||
)
|
||||
'DEFAULT', 'target_endpoint_type')
|
||||
except configparser.NoOptionError: # nosec
|
||||
# Ignore the undefined config option,
|
||||
# default_target_endpoint_type remains None which is valid.
|
||||
pass
|
||||
|
||||
try:
|
||||
custom_actions = dict(map_conf.items("custom_actions"))
|
||||
custom_actions = dict(map_conf.items('custom_actions'))
|
||||
except configparser.Error: # nosec
|
||||
# custom_actions remains {} which is valid.
|
||||
pass
|
||||
|
||||
try:
|
||||
path_kw = dict(map_conf.items("path_keywords"))
|
||||
path_kw = dict(map_conf.items('path_keywords'))
|
||||
except configparser.Error: # nosec
|
||||
# path_kw remains {} which is valid.
|
||||
pass
|
||||
|
||||
try:
|
||||
endpoints = dict(map_conf.items("service_endpoints"))
|
||||
endpoints = dict(map_conf.items('service_endpoints'))
|
||||
except configparser.Error: # nosec
|
||||
# endpoints remains {} which is valid.
|
||||
pass
|
||||
except configparser.ParsingError as err:
|
||||
raise PycadfAuditApiConfigError(
|
||||
"Error parsing audit map file: %s" % err
|
||||
)
|
||||
'Error parsing audit map file: %s' % err)
|
||||
|
||||
self._log = log
|
||||
self._MAP = AuditMap(
|
||||
path_kw=path_kw,
|
||||
custom_actions=custom_actions,
|
||||
path_kw=path_kw, custom_actions=custom_actions,
|
||||
service_endpoints=endpoints,
|
||||
default_target_endpoint_type=default_target_endpoint_type,
|
||||
)
|
||||
default_target_endpoint_type=default_target_endpoint_type)
|
||||
|
||||
@staticmethod
|
||||
def _clean_path(value):
|
||||
"""Clean path if path has json suffix."""
|
||||
return value[:-5] if value.endswith(".json") else value
|
||||
return value[:-5] if value.endswith('.json') else value
|
||||
|
||||
def get_action(self, req):
|
||||
"""Take a given Request, parse url path to calculate action type.
|
||||
@@ -145,20 +142,21 @@ class OpenStackAuditApi(object):
|
||||
if HEAD, assume read action.
|
||||
|
||||
"""
|
||||
path = req.path[:-1] if req.path.endswith("/") else req.path
|
||||
url_ending = self._clean_path(path[path.rfind("/") + 1 :])
|
||||
path = req.path[:-1] if req.path.endswith('/') else req.path
|
||||
url_ending = self._clean_path(path[path.rfind('/') + 1:])
|
||||
method = req.method
|
||||
|
||||
if url_ending + "/" + method.lower() in self._MAP.custom_actions:
|
||||
action = self._MAP.custom_actions[url_ending + "/" + method.lower()]
|
||||
if url_ending + '/' + method.lower() in self._MAP.custom_actions:
|
||||
action = self._MAP.custom_actions[url_ending + '/' +
|
||||
method.lower()]
|
||||
elif url_ending in self._MAP.custom_actions:
|
||||
action = self._MAP.custom_actions[url_ending]
|
||||
elif method == "POST":
|
||||
if url_ending == "action":
|
||||
elif method == 'POST':
|
||||
if url_ending == 'action':
|
||||
try:
|
||||
if req.json:
|
||||
body_action = list(req.json.keys())[0]
|
||||
action = taxonomy.ACTION_UPDATE + "/" + body_action
|
||||
action = taxonomy.ACTION_UPDATE + '/' + body_action
|
||||
else:
|
||||
action = taxonomy.ACTION_CREATE
|
||||
except ValueError:
|
||||
@@ -167,16 +165,16 @@ class OpenStackAuditApi(object):
|
||||
action = taxonomy.ACTION_UPDATE
|
||||
else:
|
||||
action = taxonomy.ACTION_CREATE
|
||||
elif method == "GET":
|
||||
elif method == 'GET':
|
||||
if url_ending in self._MAP.path_kw:
|
||||
action = taxonomy.ACTION_LIST
|
||||
else:
|
||||
action = taxonomy.ACTION_READ
|
||||
elif method == "PUT" or method == "PATCH":
|
||||
elif method == 'PUT' or method == 'PATCH':
|
||||
action = taxonomy.ACTION_UPDATE
|
||||
elif method == "DELETE":
|
||||
elif method == 'DELETE':
|
||||
action = taxonomy.ACTION_DELETE
|
||||
elif method == "HEAD":
|
||||
elif method == 'HEAD':
|
||||
action = taxonomy.ACTION_READ
|
||||
else:
|
||||
action = taxonomy.UNKNOWN
|
||||
@@ -185,21 +183,20 @@ class OpenStackAuditApi(object):
|
||||
|
||||
def _get_service_info(self, endp):
|
||||
service = Service(
|
||||
type=self._MAP.service_endpoints.get(endp["type"], taxonomy.UNKNOWN),
|
||||
name=endp["name"],
|
||||
id=endp["endpoints"][0].get("id", endp["name"]),
|
||||
type=self._MAP.service_endpoints.get(
|
||||
endp['type'],
|
||||
taxonomy.UNKNOWN),
|
||||
name=endp['name'],
|
||||
id=endp['endpoints'][0].get('id', endp['name']),
|
||||
admin_endp=endpoint.Endpoint(
|
||||
name="admin", url=endp["endpoints"][0].get("adminURL", taxonomy.UNKNOWN)
|
||||
),
|
||||
name='admin',
|
||||
url=endp['endpoints'][0].get('adminURL', taxonomy.UNKNOWN)),
|
||||
private_endp=endpoint.Endpoint(
|
||||
name="private",
|
||||
url=endp["endpoints"][0].get("internalURL", taxonomy.UNKNOWN),
|
||||
),
|
||||
name='private',
|
||||
url=endp['endpoints'][0].get('internalURL', taxonomy.UNKNOWN)),
|
||||
public_endp=endpoint.Endpoint(
|
||||
name="public",
|
||||
url=endp["endpoints"][0].get("publicURL", taxonomy.UNKNOWN),
|
||||
),
|
||||
)
|
||||
name='public',
|
||||
url=endp['endpoints'][0].get('publicURL', taxonomy.UNKNOWN)))
|
||||
|
||||
return service
|
||||
|
||||
@@ -208,14 +205,14 @@ class OpenStackAuditApi(object):
|
||||
|
||||
Combines service type and corresponding path for greater detail.
|
||||
"""
|
||||
type_uri = ""
|
||||
type_uri = ''
|
||||
prev_key = None
|
||||
for key in re.split("/", req.path):
|
||||
for key in re.split('/', req.path):
|
||||
key = self._clean_path(key)
|
||||
if key in self._MAP.path_kw:
|
||||
type_uri += "/" + key
|
||||
type_uri += '/' + key
|
||||
elif prev_key in self._MAP.path_kw:
|
||||
type_uri += "/" + self._MAP.path_kw[prev_key]
|
||||
type_uri += '/' + self._MAP.path_kw[prev_key]
|
||||
prev_key = key
|
||||
return service_type + type_uri
|
||||
|
||||
@@ -223,12 +220,9 @@ class OpenStackAuditApi(object):
|
||||
"""Build target resource."""
|
||||
target_typeURI = (
|
||||
self._build_typeURI(req, service.type)
|
||||
if service.type != taxonomy.UNKNOWN
|
||||
else service.type
|
||||
)
|
||||
target = resource.Resource(
|
||||
typeURI=target_typeURI, id=service.id, name=service.name
|
||||
)
|
||||
if service.type != taxonomy.UNKNOWN else service.type)
|
||||
target = resource.Resource(typeURI=target_typeURI,
|
||||
id=service.id, name=service.name)
|
||||
if service.admin_endp:
|
||||
target.add_address(service.admin_endp)
|
||||
if service.private_endp:
|
||||
@@ -244,50 +238,42 @@ class OpenStackAuditApi(object):
|
||||
from service catalog. If not, the information will be taken from
|
||||
given config file.
|
||||
"""
|
||||
service_info = Service(
|
||||
type=taxonomy.UNKNOWN,
|
||||
name=taxonomy.UNKNOWN,
|
||||
id=taxonomy.UNKNOWN,
|
||||
admin_endp=None,
|
||||
private_endp=None,
|
||||
public_endp=None,
|
||||
)
|
||||
service_info = Service(type=taxonomy.UNKNOWN, name=taxonomy.UNKNOWN,
|
||||
id=taxonomy.UNKNOWN, admin_endp=None,
|
||||
private_endp=None, public_endp=None)
|
||||
|
||||
catalog = {}
|
||||
try:
|
||||
catalog = jsonutils.loads(req.environ["HTTP_X_SERVICE_CATALOG"])
|
||||
catalog = jsonutils.loads(req.environ['HTTP_X_SERVICE_CATALOG'])
|
||||
except KeyError:
|
||||
self._log.warning(
|
||||
"Unable to discover target information because "
|
||||
"service catalog is missing. Either the incoming "
|
||||
"request does not contain an auth token or auth "
|
||||
"token does not contain a service catalog. For "
|
||||
"the latter, please make sure the "
|
||||
'Unable to discover target information because '
|
||||
'service catalog is missing. Either the incoming '
|
||||
'request does not contain an auth token or auth '
|
||||
'token does not contain a service catalog. For '
|
||||
'the latter, please make sure the '
|
||||
'"include_service_catalog" property in '
|
||||
'auth_token middleware is set to "True"'
|
||||
)
|
||||
'auth_token middleware is set to "True"')
|
||||
|
||||
default_endpoint = None
|
||||
for endp in catalog:
|
||||
if not endp["endpoints"]:
|
||||
if not endp['endpoints']:
|
||||
self._log.warning(
|
||||
"Skipping service %s as it have no endpoints.", endp["name"]
|
||||
)
|
||||
'Skipping service %s as it have no endpoints.',
|
||||
endp['name'])
|
||||
continue
|
||||
endpoint_urls = endp["endpoints"][0]
|
||||
admin_urlparse = urlparse.urlparse(endpoint_urls.get("adminURL", ""))
|
||||
public_urlparse = urlparse.urlparse(endpoint_urls.get("publicURL", ""))
|
||||
endpoint_urls = endp['endpoints'][0]
|
||||
admin_urlparse = urlparse.urlparse(
|
||||
endpoint_urls.get('adminURL', ''))
|
||||
public_urlparse = urlparse.urlparse(
|
||||
endpoint_urls.get('publicURL', ''))
|
||||
req_url = urlparse.urlparse(req.host_url)
|
||||
if req_url.port and (
|
||||
req_url.netloc == admin_urlparse.netloc
|
||||
or req_url.netloc == public_urlparse.netloc
|
||||
):
|
||||
if req_url.port and (req_url.netloc == admin_urlparse.netloc
|
||||
or req_url.netloc == public_urlparse.netloc):
|
||||
service_info = self._get_service_info(endp)
|
||||
break
|
||||
elif (
|
||||
self._MAP.default_target_endpoint_type
|
||||
and endp["type"] == self._MAP.default_target_endpoint_type
|
||||
):
|
||||
elif (self._MAP.default_target_endpoint_type and
|
||||
endp['type'] == self._MAP.default_target_endpoint_type):
|
||||
default_endpoint = endp
|
||||
else:
|
||||
if default_endpoint:
|
||||
@@ -300,19 +286,16 @@ class OpenStackAuditApi(object):
|
||||
|
||||
initiator = ClientResource(
|
||||
typeURI=taxonomy.ACCOUNT_USER,
|
||||
id=req.environ.get("HTTP_X_USER_ID", taxonomy.UNKNOWN),
|
||||
name=req.environ.get("HTTP_X_USER_NAME", taxonomy.UNKNOWN),
|
||||
id=req.environ.get('HTTP_X_USER_ID', taxonomy.UNKNOWN),
|
||||
name=req.environ.get('HTTP_X_USER_NAME', taxonomy.UNKNOWN),
|
||||
host=host.Host(address=req.client_addr, agent=req.user_agent),
|
||||
credential=KeystoneCredential(
|
||||
token=req.environ.get("HTTP_X_AUTH_TOKEN", ""),
|
||||
identity_status=req.environ.get(
|
||||
"HTTP_X_IDENTITY_STATUS", taxonomy.UNKNOWN
|
||||
),
|
||||
),
|
||||
project_id=req.environ.get("HTTP_X_PROJECT_ID", taxonomy.UNKNOWN),
|
||||
request_id=req.environ.get("openstack.request_id"),
|
||||
global_request_id=req.environ.get("openstack.global_request_id"),
|
||||
)
|
||||
token=req.environ.get('HTTP_X_AUTH_TOKEN', ''),
|
||||
identity_status=req.environ.get('HTTP_X_IDENTITY_STATUS',
|
||||
taxonomy.UNKNOWN)),
|
||||
project_id=req.environ.get('HTTP_X_PROJECT_ID', taxonomy.UNKNOWN),
|
||||
request_id=req.environ.get('openstack.request_id'),
|
||||
global_request_id=req.environ.get('openstack.global_request_id'))
|
||||
|
||||
target = self.get_target_resource(req)
|
||||
|
||||
@@ -322,8 +305,8 @@ class OpenStackAuditApi(object):
|
||||
action=action,
|
||||
initiator=initiator,
|
||||
target=target,
|
||||
observer=resource.Resource(id="target"),
|
||||
)
|
||||
observer=resource.Resource(id='target'))
|
||||
event.requestPath = req.path_qs
|
||||
event.add_tag(tag.generate_name_value_tag("correlation_id", correlation_id))
|
||||
event.add_tag(tag.generate_name_value_tag('correlation_id',
|
||||
correlation_id))
|
||||
return event
|
||||
@@ -20,17 +20,19 @@ except ImportError:
|
||||
|
||||
|
||||
class _LogNotifier(object):
|
||||
|
||||
def __init__(self, log):
|
||||
self._log = log
|
||||
|
||||
def notify(self, context, event_type, payload):
|
||||
self._log.info(
|
||||
"Event type: %(event_type)s, Context: %(context)s, Payload: %(payload)s",
|
||||
{"context": context, "event_type": event_type, "payload": payload},
|
||||
)
|
||||
self._log.info('Event type: %(event_type)s, Context: %(context)s, '
|
||||
'Payload: %(payload)s', {'context': context,
|
||||
'event_type': event_type,
|
||||
'payload': payload})
|
||||
|
||||
|
||||
class _MessagingNotifier(object):
|
||||
|
||||
def __init__(self, notifier):
|
||||
self._notifier = notifier
|
||||
|
||||
@@ -39,17 +41,16 @@ class _MessagingNotifier(object):
|
||||
|
||||
|
||||
def create_notifier(conf, log):
|
||||
if oslo_messaging and conf.get("use_oslo_messaging"):
|
||||
if oslo_messaging and conf.get('use_oslo_messaging'):
|
||||
transport = oslo_messaging.get_notification_transport(
|
||||
conf.oslo_conf_obj, url=conf.get("transport_url")
|
||||
)
|
||||
conf.oslo_conf_obj,
|
||||
url=conf.get('transport_url'))
|
||||
|
||||
notifier = oslo_messaging.Notifier(
|
||||
transport,
|
||||
os.path.basename(sys.argv[0]),
|
||||
driver=conf.get("driver"),
|
||||
topics=conf.get("topics"),
|
||||
)
|
||||
driver=conf.get('driver'),
|
||||
topics=conf.get('topics'))
|
||||
|
||||
return _MessagingNotifier(notifier)
|
||||
|
||||
|
||||
@@ -227,8 +227,6 @@ from keystoneauth1 import discover
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
from keystoneauth1 import loading
|
||||
from keystoneauth1.loading import session as session_loading
|
||||
import oslo_cache
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
import webob.dec
|
||||
@@ -245,10 +243,10 @@ from keystonemiddleware.i18n import _
|
||||
|
||||
|
||||
_LOG = logging.getLogger(__name__)
|
||||
oslo_cache.configure(cfg.CONF)
|
||||
|
||||
AUTH_TOKEN_OPTS = [
|
||||
(_base.AUTHTOKEN_GROUP, _opts._OPTS + loading.get_auth_common_conf_options())
|
||||
(_base.AUTHTOKEN_GROUP,
|
||||
_opts._OPTS + loading.get_auth_common_conf_options())
|
||||
]
|
||||
|
||||
|
||||
@@ -279,31 +277,29 @@ def list_opts():
|
||||
def _path_matches(request_path, path_pattern):
|
||||
# The fnmatch module doesn't provide the ability to match * versus **,
|
||||
# so convert to regex.
|
||||
token_regex = (
|
||||
r"(?P<tag>{[^}]*})|" # {tag} # nosec # noqa: S105
|
||||
r"(?P<wild>\*(?=$|[^\*]))|" # *
|
||||
r"(?P<rec_wild>\*\*)|" # **
|
||||
r"(?P<literal>[^{}\*])"
|
||||
) # anything else
|
||||
path_regex = ""
|
||||
token_regex = (r'(?P<tag>{[^}]*})|' # {tag} # nosec
|
||||
r'(?P<wild>\*(?=$|[^\*]))|' # *
|
||||
r'(?P<rec_wild>\*\*)|' # **
|
||||
r'(?P<literal>[^{}\*])') # anything else
|
||||
path_regex = ''
|
||||
for match in re.finditer(token_regex, path_pattern):
|
||||
token = match.groupdict()
|
||||
if token["tag"] or token["wild"]:
|
||||
path_regex += r"[^\/]+"
|
||||
if token["rec_wild"]:
|
||||
path_regex += ".*"
|
||||
if token["literal"]:
|
||||
path_regex += token["literal"]
|
||||
path_regex = r"^%s$" % path_regex
|
||||
if token['tag'] or token['wild']:
|
||||
path_regex += r'[^\/]+'
|
||||
if token['rec_wild']:
|
||||
path_regex += '.*'
|
||||
if token['literal']:
|
||||
path_regex += token['literal']
|
||||
path_regex = r'^%s$' % path_regex
|
||||
return re.match(path_regex, request_path)
|
||||
|
||||
|
||||
class _BIND_MODE(object):
|
||||
DISABLED = "disabled"
|
||||
PERMISSIVE = "permissive"
|
||||
STRICT = "strict"
|
||||
REQUIRED = "required"
|
||||
KERBEROS = "kerberos"
|
||||
DISABLED = 'disabled'
|
||||
PERMISSIVE = 'permissive'
|
||||
STRICT = 'strict'
|
||||
REQUIRED = 'required'
|
||||
KERBEROS = 'kerberos'
|
||||
|
||||
|
||||
class BaseAuthProtocol(object):
|
||||
@@ -317,15 +313,13 @@ class BaseAuthProtocol(object):
|
||||
perform.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
log=_LOG,
|
||||
enforce_token_bind=_BIND_MODE.PERMISSIVE,
|
||||
service_token_roles=None,
|
||||
service_token_roles_required=True,
|
||||
service_type=None,
|
||||
):
|
||||
def __init__(self,
|
||||
app,
|
||||
log=_LOG,
|
||||
enforce_token_bind=_BIND_MODE.PERMISSIVE,
|
||||
service_token_roles=None,
|
||||
service_token_roles_required=True,
|
||||
service_type=None):
|
||||
self.log = log
|
||||
self._app = app
|
||||
self._enforce_token_bind = enforce_token_bind
|
||||
@@ -364,13 +358,13 @@ class BaseAuthProtocol(object):
|
||||
allow_expired = False
|
||||
|
||||
if request.service_token:
|
||||
self.log.debug("Authenticating service token")
|
||||
self.log.debug('Authenticating service token')
|
||||
try:
|
||||
_, serv_auth_ref = self._do_fetch_token(request.service_token)
|
||||
self._validate_token(serv_auth_ref)
|
||||
self._confirm_token_bind(serv_auth_ref, request)
|
||||
except ksm_exceptions.InvalidToken:
|
||||
self.log.info("Invalid service token")
|
||||
self.log.info('Invalid service token')
|
||||
request.service_token_valid = False
|
||||
else:
|
||||
# FIXME(jamielennox): The new behaviour for service tokens is
|
||||
@@ -391,14 +385,12 @@ class BaseAuthProtocol(object):
|
||||
request.service_token_valid = role_check_passed
|
||||
else:
|
||||
if not self._service_token_warning_emitted:
|
||||
self.log.warning(
|
||||
"A valid token was submitted as "
|
||||
"a service token, but it was not "
|
||||
"a valid service token. This is "
|
||||
"incorrect but backwards "
|
||||
"compatible behaviour. This will "
|
||||
"be removed in future releases."
|
||||
)
|
||||
self.log.warning('A valid token was submitted as '
|
||||
'a service token, but it was not '
|
||||
'a valid service token. This is '
|
||||
'incorrect but backwards '
|
||||
'compatible behaviour. This will '
|
||||
'be removed in future releases.')
|
||||
# prevent log spam on every single request
|
||||
self._service_token_warning_emitted = True
|
||||
|
||||
@@ -408,24 +400,25 @@ class BaseAuthProtocol(object):
|
||||
allow_expired = role_check_passed
|
||||
|
||||
if request.user_token:
|
||||
self.log.debug("Authenticating user token")
|
||||
self.log.debug('Authenticating user token')
|
||||
try:
|
||||
data, user_auth_ref = self._do_fetch_token(
|
||||
request.user_token, allow_expired=allow_expired
|
||||
)
|
||||
self._validate_token(user_auth_ref, allow_expired=allow_expired)
|
||||
if user_auth_ref.version != "v2.0":
|
||||
self.validate_allowed_request(request, data["token"])
|
||||
request.user_token,
|
||||
allow_expired=allow_expired)
|
||||
self._validate_token(user_auth_ref,
|
||||
allow_expired=allow_expired)
|
||||
self.validate_allowed_request(request, data['token'])
|
||||
if not request.service_token:
|
||||
self._confirm_token_bind(user_auth_ref, request)
|
||||
except ksm_exceptions.InvalidToken:
|
||||
self.log.info("Invalid user token")
|
||||
self.log.info('Invalid user token')
|
||||
request.user_token_valid = False
|
||||
else:
|
||||
request.user_token_valid = True
|
||||
request.token_info = data
|
||||
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref, serv_auth_ref)
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref,
|
||||
serv_auth_ref)
|
||||
|
||||
def _validate_token(self, auth_ref, allow_expired=False):
|
||||
"""Perform the validation steps on the token.
|
||||
@@ -437,7 +430,7 @@ class BaseAuthProtocol(object):
|
||||
"""
|
||||
# 0 seconds of validity means it is invalid right now
|
||||
if (not allow_expired) and auth_ref.will_expire_soon(stale_duration=0):
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
|
||||
def _do_fetch_token(self, token, **kwargs):
|
||||
"""Helper method to fetch a token and convert it into an AccessInfo."""
|
||||
@@ -449,8 +442,8 @@ class BaseAuthProtocol(object):
|
||||
try:
|
||||
return data, access.create(body=data, auth_token=token)
|
||||
except Exception:
|
||||
self.log.warning("Invalid token contents.", exc_info=True)
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
self.log.warning('Invalid token contents.', exc_info=True)
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
|
||||
def fetch_token(self, token, **kwargs):
|
||||
"""Fetch the token data based on the value in the header.
|
||||
@@ -485,7 +478,7 @@ class BaseAuthProtocol(object):
|
||||
def _invalid_user_token(self, msg=False):
|
||||
# NOTE(jamielennox): use False as the default so that None is valid
|
||||
if msg is False:
|
||||
msg = _("Token authorization failed")
|
||||
msg = _('Token authorization failed')
|
||||
|
||||
raise ksm_exceptions.InvalidToken(msg)
|
||||
|
||||
@@ -494,17 +487,15 @@ class BaseAuthProtocol(object):
|
||||
return
|
||||
|
||||
# permissive and strict modes don't require there to be a bind
|
||||
permissive = self._enforce_token_bind in (
|
||||
_BIND_MODE.PERMISSIVE,
|
||||
_BIND_MODE.STRICT,
|
||||
)
|
||||
permissive = self._enforce_token_bind in (_BIND_MODE.PERMISSIVE,
|
||||
_BIND_MODE.STRICT)
|
||||
|
||||
if not auth_ref.bind:
|
||||
if permissive:
|
||||
# no bind provided and none required
|
||||
return
|
||||
else:
|
||||
self.log.info("No bind information present in token.")
|
||||
self.log.info('No bind information present in token.')
|
||||
self._invalid_user_token()
|
||||
|
||||
# get the named mode if bind_mode is not one of the predefined
|
||||
@@ -514,147 +505,138 @@ class BaseAuthProtocol(object):
|
||||
name = self._enforce_token_bind
|
||||
|
||||
if name and name not in auth_ref.bind:
|
||||
self.log.info("Named bind mode %s not in bind information", name)
|
||||
self.log.info('Named bind mode %s not in bind information',
|
||||
name)
|
||||
self._invalid_user_token()
|
||||
|
||||
for bind_type, identifier in auth_ref.bind.items():
|
||||
if bind_type == _BIND_MODE.KERBEROS:
|
||||
if req.auth_type != "negotiate":
|
||||
self.log.info("Kerberos credentials required and not present.")
|
||||
if req.auth_type != 'negotiate':
|
||||
self.log.info('Kerberos credentials required and '
|
||||
'not present.')
|
||||
self._invalid_user_token()
|
||||
|
||||
if req.remote_user != identifier:
|
||||
self.log.info("Kerberos credentials do not match those in bind.")
|
||||
self.log.info('Kerberos credentials do not match '
|
||||
'those in bind.')
|
||||
self._invalid_user_token()
|
||||
|
||||
self.log.debug("Kerberos bind authentication successful.")
|
||||
self.log.debug('Kerberos bind authentication successful.')
|
||||
|
||||
elif self._enforce_token_bind == _BIND_MODE.PERMISSIVE:
|
||||
self.log.debug(
|
||||
"Ignoring Unknown bind for permissive mode: "
|
||||
"%(bind_type)s: %(identifier)s.",
|
||||
{"bind_type": bind_type, "identifier": identifier},
|
||||
)
|
||||
self.log.debug('Ignoring Unknown bind for permissive mode: '
|
||||
'%(bind_type)s: %(identifier)s.',
|
||||
{'bind_type': bind_type,
|
||||
'identifier': identifier})
|
||||
|
||||
else:
|
||||
self.log.info(
|
||||
"Couldn`t verify unknown bind: %(bind_type)s: %(identifier)s.",
|
||||
{"bind_type": bind_type, "identifier": identifier},
|
||||
)
|
||||
'Couldn`t verify unknown bind: %(bind_type)s: '
|
||||
'%(identifier)s.',
|
||||
{'bind_type': bind_type, 'identifier': identifier})
|
||||
self._invalid_user_token()
|
||||
|
||||
def validate_allowed_request(self, request, token):
|
||||
self.log.debug("Validating token access rules against request")
|
||||
app_cred = token.get("application_credential")
|
||||
app_cred = token.get('application_credential')
|
||||
if not app_cred:
|
||||
return
|
||||
access_rules = app_cred.get("access_rules")
|
||||
access_rules = app_cred.get('access_rules')
|
||||
if access_rules is None:
|
||||
return
|
||||
if hasattr(self, "_conf"):
|
||||
my_service_type = self._conf.get("service_type")
|
||||
if hasattr(self, '_conf'):
|
||||
my_service_type = self._conf.get('service_type')
|
||||
else:
|
||||
my_service_type = self._service_type
|
||||
if not my_service_type:
|
||||
self.log.warning(
|
||||
"Cannot validate request with restricted"
|
||||
" access rules. Set service_type in"
|
||||
" [keystone_authtoken] to allow access rule"
|
||||
" validation."
|
||||
)
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
self.log.warning('Cannot validate request with restricted'
|
||||
' access rules. Set service_type in'
|
||||
' [keystone_authtoken] to allow access rule'
|
||||
' validation.')
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
# token can always be validated regardless of access rules
|
||||
if (
|
||||
my_service_type == "identity"
|
||||
and request.method == "GET"
|
||||
and request.path.endswith("/v3/auth/tokens")
|
||||
):
|
||||
if (my_service_type == 'identity' and
|
||||
request.method == 'GET' and
|
||||
request.path.endswith('/v3/auth/tokens')):
|
||||
return
|
||||
catalog = token["catalog"]
|
||||
catalog = token['catalog']
|
||||
# validate service type is in catalog
|
||||
catalog_svcs = [s for s in catalog if s["type"] == my_service_type]
|
||||
catalog_svcs = [s for s in catalog if s['type'] == my_service_type]
|
||||
if len(catalog_svcs) == 0:
|
||||
self.log.warning(
|
||||
"Cannot validate request with restricted"
|
||||
" access rules. service_type in"
|
||||
" [keystone_authtoken] is not a valid service"
|
||||
" type in the catalog."
|
||||
)
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
self.log.warning('Cannot validate request with restricted'
|
||||
' access rules. service_type in'
|
||||
' [keystone_authtoken] is not a valid service'
|
||||
' type in the catalog.')
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
if request.service_token:
|
||||
# The request may not match an allowed request, but the presence
|
||||
# of the service token indicates this is a chain of requests and
|
||||
# hence this request was not user-facing
|
||||
return
|
||||
for access_rule in access_rules:
|
||||
method = access_rule["method"]
|
||||
path = access_rule["path"]
|
||||
service = access_rule["service"]
|
||||
if (
|
||||
request.method == method
|
||||
and service == my_service_type
|
||||
and _path_matches(request.path, path)
|
||||
):
|
||||
method = access_rule['method']
|
||||
path = access_rule['path']
|
||||
service = access_rule['service']
|
||||
if request.method == method and \
|
||||
service == my_service_type and \
|
||||
_path_matches(request.path, path):
|
||||
return
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
|
||||
|
||||
class AuthProtocol(BaseAuthProtocol):
|
||||
"""Middleware that handles authenticating client calls."""
|
||||
|
||||
def __init__(self, app, conf):
|
||||
log = logging.getLogger(conf.get("log_name", __name__))
|
||||
log.info("Starting Keystone auth_token middleware")
|
||||
log = logging.getLogger(conf.get('log_name', __name__))
|
||||
log.info('Starting Keystone auth_token middleware')
|
||||
|
||||
self._conf = config.Config(
|
||||
"auth_token", _base.AUTHTOKEN_GROUP, list_opts(), conf
|
||||
)
|
||||
if self._conf.oslo_conf_obj is not cfg.CONF:
|
||||
oslo_cache.configure(self._conf.oslo_conf_obj)
|
||||
self._conf = config.Config('auth_token',
|
||||
_base.AUTHTOKEN_GROUP,
|
||||
list_opts(),
|
||||
conf)
|
||||
|
||||
token_roles_required = self._conf.get("service_token_roles_required")
|
||||
token_roles_required = self._conf.get('service_token_roles_required')
|
||||
|
||||
if not token_roles_required:
|
||||
log.warning(
|
||||
"AuthToken middleware is set with "
|
||||
"keystone_authtoken.service_token_roles_required "
|
||||
"set to False. This allows any valid token to be "
|
||||
"used as a service token, which can bypass access "
|
||||
"rule checks and weaken security. It is strongly "
|
||||
"recommended to set this to True."
|
||||
)
|
||||
log.warning('AuthToken middleware is set with '
|
||||
'keystone_authtoken.service_token_roles_required '
|
||||
'set to False. This allows any valid token to be '
|
||||
'used as a service token, which can bypass access '
|
||||
'rule checks and weaken security. It is strongly '
|
||||
'recommended to set this to True.')
|
||||
|
||||
super(AuthProtocol, self).__init__(
|
||||
app,
|
||||
log=log,
|
||||
enforce_token_bind=self._conf.get("enforce_token_bind"),
|
||||
service_token_roles=self._conf.get("service_token_roles"),
|
||||
service_token_roles_required=token_roles_required,
|
||||
)
|
||||
enforce_token_bind=self._conf.get('enforce_token_bind'),
|
||||
service_token_roles=self._conf.get('service_token_roles'),
|
||||
service_token_roles_required=token_roles_required)
|
||||
|
||||
# delay_auth_decision means we still allow unauthenticated requests
|
||||
# through and we let the downstream service make the final decision
|
||||
self._delay_auth_decision = self._conf.get("delay_auth_decision")
|
||||
self._include_service_catalog = self._conf.get("include_service_catalog")
|
||||
self._interface = self._conf.get("interface")
|
||||
self._delay_auth_decision = self._conf.get('delay_auth_decision')
|
||||
self._include_service_catalog = self._conf.get(
|
||||
'include_service_catalog')
|
||||
self._interface = self._conf.get('interface')
|
||||
self._auth = self._create_auth_plugin()
|
||||
self._session = self._create_session()
|
||||
self._identity_server = self._create_identity_server()
|
||||
|
||||
self._www_authenticate_uri = self._conf.get("www_authenticate_uri")
|
||||
self._www_authenticate_uri = self._conf.get('www_authenticate_uri')
|
||||
if not self._www_authenticate_uri:
|
||||
self.log.warning(
|
||||
"Configuring www_authenticate_uri to point to the public "
|
||||
"identity endpoint is required; clients may not be able to "
|
||||
"authenticate against an admin endpoint"
|
||||
)
|
||||
'Configuring www_authenticate_uri to point to the public '
|
||||
'identity endpoint is required; clients may not be able to '
|
||||
'authenticate against an admin endpoint')
|
||||
|
||||
self._token_cache = self._token_cache_factory()
|
||||
|
||||
@property
|
||||
def www_authenticate_uri(self):
|
||||
if not self._www_authenticate_uri:
|
||||
self._www_authenticate_uri = self._identity_server.www_authenticate_uri
|
||||
self._www_authenticate_uri = \
|
||||
self._identity_server.www_authenticate_uri
|
||||
return self._www_authenticate_uri
|
||||
|
||||
def process_request(self, request):
|
||||
@@ -681,27 +663,26 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
# a valid request. We should find a better way to expose this from the
|
||||
# request object.
|
||||
user_status = request.user_token and request.user_token_valid
|
||||
service_status = request.headers.get("X-Service-Identity-Status", "Confirmed")
|
||||
service_status = request.headers.get('X-Service-Identity-Status',
|
||||
'Confirmed')
|
||||
|
||||
if not (user_status and service_status == "Confirmed"):
|
||||
if not (user_status and service_status == 'Confirmed'):
|
||||
if self._delay_auth_decision:
|
||||
self.log.debug("Deferring reject downstream")
|
||||
self.log.debug('Deferring reject downstream')
|
||||
else:
|
||||
self.log.info("Rejecting request")
|
||||
message = _("The request you have made requires authentication.")
|
||||
body = {
|
||||
"error": {
|
||||
"code": 401,
|
||||
"title": "Unauthorized",
|
||||
"message": message,
|
||||
}
|
||||
}
|
||||
self.log.info('Rejecting request')
|
||||
message = _('The request you have made requires '
|
||||
'authentication.')
|
||||
body = {'error': {
|
||||
'code': 401,
|
||||
'title': 'Unauthorized',
|
||||
'message': message,
|
||||
}}
|
||||
raise webob.exc.HTTPUnauthorized(
|
||||
body=jsonutils.dumps(body),
|
||||
headers=self._reject_auth_headers,
|
||||
charset="UTF-8",
|
||||
content_type="application/json",
|
||||
)
|
||||
charset='UTF-8',
|
||||
content_type='application/json')
|
||||
|
||||
if request.user_token_valid:
|
||||
request.set_user_headers(request.token_auth.user)
|
||||
@@ -717,7 +698,8 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
request.set_service_headers(request.token_auth.service)
|
||||
|
||||
if self.log.isEnabledFor(logging.DEBUG):
|
||||
self.log.debug("Received request from %s", request.token_auth._log_format)
|
||||
self.log.debug('Received request from %s',
|
||||
request.token_auth._log_format)
|
||||
|
||||
def process_response(self, response):
|
||||
"""Process Response.
|
||||
@@ -734,7 +716,7 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
@property
|
||||
def _reject_auth_headers(self):
|
||||
header_val = 'Keystone uri="%s"' % self.www_authenticate_uri
|
||||
return [("WWW-Authenticate", header_val)]
|
||||
return [('WWW-Authenticate', header_val)]
|
||||
|
||||
def fetch_token(self, token, allow_expired=False):
|
||||
"""Retrieve a token from either a PKI bundle or the identity server.
|
||||
@@ -758,30 +740,26 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
data = cached
|
||||
else:
|
||||
data = self._identity_server.verify_token(
|
||||
token, allow_expired=allow_expired
|
||||
)
|
||||
token,
|
||||
allow_expired=allow_expired)
|
||||
|
||||
self._token_cache.set(token, data)
|
||||
|
||||
except (
|
||||
ksa_exceptions.ConnectFailure,
|
||||
ksa_exceptions.DiscoveryFailure,
|
||||
ksa_exceptions.RequestTimeout,
|
||||
ksm_exceptions.ServiceError,
|
||||
) as e:
|
||||
self.log.critical("Unable to validate token: %s", e)
|
||||
except (ksa_exceptions.ConnectFailure,
|
||||
ksa_exceptions.DiscoveryFailure,
|
||||
ksa_exceptions.RequestTimeout,
|
||||
ksm_exceptions.ServiceError) as e:
|
||||
self.log.critical('Unable to validate token: %s', e)
|
||||
if self._delay_auth_decision:
|
||||
self.log.debug(
|
||||
"Keystone unavailable; marking token as "
|
||||
"invalid and deferring auth decision."
|
||||
)
|
||||
raise ksm_exceptions.InvalidToken("Keystone unavailable: %s" % e)
|
||||
self.log.debug('Keystone unavailable; marking token as '
|
||||
'invalid and deferring auth decision.')
|
||||
raise ksm_exceptions.InvalidToken(
|
||||
'Keystone unavailable: %s' % e)
|
||||
raise webob.exc.HTTPServiceUnavailable(
|
||||
"The Keystone service is temporarily unavailable."
|
||||
)
|
||||
'The Keystone service is temporarily unavailable.')
|
||||
except ksm_exceptions.InvalidToken:
|
||||
self.log.debug("Token validation failure.", exc_info=True)
|
||||
self.log.warning("Authorization failed for token")
|
||||
self.log.debug('Token validation failure.', exc_info=True)
|
||||
self.log.warning('Authorization failed for token')
|
||||
raise
|
||||
except ksa_exceptions.EndpointNotFound:
|
||||
# Invalidate auth in adapter for identity endpoint update
|
||||
@@ -790,22 +768,15 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
|
||||
return data
|
||||
|
||||
def _validate_token(self, auth_ref, **kwargs):
|
||||
super(AuthProtocol, self)._validate_token(auth_ref, **kwargs)
|
||||
|
||||
if auth_ref.version == "v2.0" and not auth_ref.project_id:
|
||||
msg = _("Unable to determine service tenancy.")
|
||||
raise ksm_exceptions.InvalidToken(msg)
|
||||
|
||||
def _create_auth_plugin(self):
|
||||
# NOTE(jamielennox): Ideally this would use load_from_conf_options
|
||||
# however that is not possible because we have to support the override
|
||||
# pattern we use in _conf.get. This function therefore does a manual
|
||||
# version of load_from_conf_options with the fallback plugin inline.
|
||||
|
||||
group = self._conf.get("auth_section") or _base.AUTHTOKEN_GROUP
|
||||
group = self._conf.get('auth_section') or _base.AUTHTOKEN_GROUP
|
||||
|
||||
plugin_name = self._conf.get("auth_type", group=group)
|
||||
plugin_name = self._conf.get('auth_type', group=group)
|
||||
|
||||
if not plugin_name:
|
||||
return None
|
||||
@@ -825,12 +796,12 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
# same as calling Session.load_from_conf_options(CONF, GROUP)
|
||||
# however we can't do that because we have to use _conf.get to
|
||||
# support the paste.ini options.
|
||||
kwargs.setdefault("cert", self._conf.get("certfile"))
|
||||
kwargs.setdefault("key", self._conf.get("keyfile"))
|
||||
kwargs.setdefault("cacert", self._conf.get("cafile"))
|
||||
kwargs.setdefault("insecure", self._conf.get("insecure"))
|
||||
kwargs.setdefault("timeout", self._conf.get("http_connect_timeout"))
|
||||
kwargs.setdefault("user_agent", self._conf.user_agent)
|
||||
kwargs.setdefault('cert', self._conf.get('certfile'))
|
||||
kwargs.setdefault('key', self._conf.get('keyfile'))
|
||||
kwargs.setdefault('cacert', self._conf.get('cafile'))
|
||||
kwargs.setdefault('insecure', self._conf.get('insecure'))
|
||||
kwargs.setdefault('timeout', self._conf.get('http_connect_timeout'))
|
||||
kwargs.setdefault('user_agent', self._conf.user_agent)
|
||||
|
||||
return session_loading.Session().load_from_options(**kwargs)
|
||||
|
||||
@@ -838,13 +809,12 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
adap = adapter.Adapter(
|
||||
self._session,
|
||||
auth=self._auth,
|
||||
service_type="identity",
|
||||
service_type='identity',
|
||||
interface=self._interface,
|
||||
region_name=self._conf.get("region_name"),
|
||||
connect_retries=self._conf.get("http_request_max_retries"),
|
||||
)
|
||||
region_name=self._conf.get('region_name'),
|
||||
connect_retries=self._conf.get('http_request_max_retries'))
|
||||
|
||||
auth_version = self._conf.get("auth_version")
|
||||
auth_version = self._conf.get('auth_version')
|
||||
if auth_version is not None:
|
||||
auth_version = discover.normalize_version_number(auth_version)
|
||||
return _identity.IdentityServer(
|
||||
@@ -852,39 +822,34 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
adap,
|
||||
include_service_catalog=self._include_service_catalog,
|
||||
requested_auth_version=auth_version,
|
||||
requested_auth_interface=self._interface,
|
||||
)
|
||||
|
||||
def _create_oslo_cache(self):
|
||||
# having this as a function makes test mocking easier
|
||||
region = oslo_cache.create_region()
|
||||
oslo_cache.configure_cache_region(self._conf.oslo_conf_obj, region)
|
||||
return region
|
||||
requested_auth_interface=self._interface)
|
||||
|
||||
def _token_cache_factory(self):
|
||||
security_strategy = self._conf.get("memcache_security_strategy")
|
||||
|
||||
security_strategy = self._conf.get('memcache_security_strategy')
|
||||
|
||||
cache_kwargs = dict(
|
||||
cache_time=int(self._conf.get("token_cache_time")),
|
||||
env_cache_name=self._conf.get("cache"),
|
||||
memcached_servers=self._conf.get("memcached_servers"),
|
||||
use_advanced_pool=self._conf.get("memcache_use_advanced_pool"),
|
||||
dead_retry=self._conf.get("memcache_pool_dead_retry"),
|
||||
maxsize=self._conf.get("memcache_pool_maxsize"),
|
||||
unused_timeout=self._conf.get("memcache_pool_unused_timeout"),
|
||||
conn_get_timeout=self._conf.get("memcache_pool_conn_get_timeout"),
|
||||
socket_timeout=self._conf.get("memcache_pool_socket_timeout"),
|
||||
sasl_enabled=self._conf.get("memcache_sasl_enabled"),
|
||||
username=self._conf.get("memcache_username"),
|
||||
password=self._conf.get("memcache_password"),
|
||||
tls_enabled=self._conf.get("memcache_tls_enabled"),
|
||||
cache_time=int(self._conf.get('token_cache_time')),
|
||||
env_cache_name=self._conf.get('cache'),
|
||||
memcached_servers=self._conf.get('memcached_servers'),
|
||||
use_advanced_pool=self._conf.get('memcache_use_advanced_pool'),
|
||||
dead_retry=self._conf.get('memcache_pool_dead_retry'),
|
||||
maxsize=self._conf.get('memcache_pool_maxsize'),
|
||||
unused_timeout=self._conf.get('memcache_pool_unused_timeout'),
|
||||
conn_get_timeout=self._conf.get('memcache_pool_conn_get_timeout'),
|
||||
socket_timeout=self._conf.get('memcache_pool_socket_timeout'),
|
||||
sasl_enabled=self._conf.get('memcache_sasl_enabled'),
|
||||
username=self._conf.get('memcache_username'),
|
||||
password=self._conf.get('memcache_password'),
|
||||
tls_enabled=self._conf.get('memcache_tls_enabled'),
|
||||
)
|
||||
|
||||
if self._conf.get("memcache_tls_enabled"):
|
||||
tls_cafile = self._conf.get("memcache_tls_cafile")
|
||||
tls_certfile = self._conf.get("memcache_tls_certfile")
|
||||
tls_keyfile = self._conf.get("memcache_tls_keyfile")
|
||||
tls_allowed_ciphers = self._conf.get("memcache_tls_allowed_ciphers")
|
||||
if self._conf.get('memcache_tls_enabled'):
|
||||
tls_cafile = self._conf.get('memcache_tls_cafile')
|
||||
tls_certfile = self._conf.get('memcache_tls_certfile')
|
||||
tls_keyfile = self._conf.get('memcache_tls_keyfile')
|
||||
tls_allowed_ciphers = self._conf.get(
|
||||
'memcache_tls_allowed_ciphers')
|
||||
|
||||
tls_context = ssl.create_default_context(cafile=tls_cafile)
|
||||
|
||||
@@ -894,13 +859,14 @@ class AuthProtocol(BaseAuthProtocol):
|
||||
if tls_allowed_ciphers:
|
||||
tls_context.set_ciphers(tls_allowed_ciphers)
|
||||
|
||||
cache_kwargs["tls_context"] = tls_context
|
||||
cache_kwargs['tls_context'] = tls_context
|
||||
|
||||
if security_strategy.lower() != "none":
|
||||
secret_key = self._conf.get("memcache_secret_key")
|
||||
return _cache.SecureTokenCache(
|
||||
self.log, security_strategy, secret_key, **cache_kwargs
|
||||
)
|
||||
if security_strategy.lower() != 'none':
|
||||
secret_key = self._conf.get('memcache_secret_key')
|
||||
return _cache.SecureTokenCache(self.log,
|
||||
security_strategy,
|
||||
secret_key,
|
||||
**cache_kwargs)
|
||||
else:
|
||||
return _cache.TokenCache(self.log, **cache_kwargs)
|
||||
|
||||
@@ -912,7 +878,6 @@ def filter_factory(global_conf, **local_conf):
|
||||
|
||||
def auth_filter(app):
|
||||
return AuthProtocol(app, conf)
|
||||
|
||||
return auth_filter
|
||||
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
AUTHTOKEN_GROUP = "keystone_authtoken"
|
||||
AUTHTOKEN_GROUP = 'keystone_authtoken'
|
||||
@@ -33,7 +33,7 @@ def _hash_key(key):
|
||||
# get a text_type (unicode). In python 3.4 all strings are
|
||||
# text_type and not bytes by default. This encode coerces the
|
||||
# text_type to the appropriate bytes values.
|
||||
key = key.encode("utf-8")
|
||||
key = key.encode('utf-8')
|
||||
return hashlib.sha256(key).hexdigest()
|
||||
|
||||
|
||||
@@ -66,8 +66,7 @@ class _CachePool(list):
|
||||
"is removed the auth_token middleware will not cache tokens "
|
||||
"by default which may result in performance issues. It is "
|
||||
"recommended to use memcache for the auth_token token cache "
|
||||
"by setting the memcached_servers option."
|
||||
)
|
||||
"by setting the memcached_servers option.")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def reserve(self):
|
||||
@@ -79,13 +78,10 @@ class _CachePool(list):
|
||||
if self._memcached_servers:
|
||||
if self._sasl_enabled:
|
||||
import bmemcached
|
||||
|
||||
c = bmemcached.Client(
|
||||
self._memcached_servers, self._username, self._password
|
||||
)
|
||||
c = bmemcached.Client(self._memcached_servers,
|
||||
self._username, self._password)
|
||||
else:
|
||||
import memcache
|
||||
|
||||
c = memcache.Client(self._memcached_servers, debug=0)
|
||||
else:
|
||||
c = _FakeClient()
|
||||
@@ -107,18 +103,16 @@ class _MemcacheClientPool(object):
|
||||
self._tls_enabled = arguments.pop("tls_enabled", False)
|
||||
if self._tls_enabled or self._sasl_enabled:
|
||||
from oslo_cache import _bmemcache_pool
|
||||
|
||||
self._pool = _bmemcache_pool.BMemcacheClientPool(
|
||||
memcache_servers, arguments, **kwargs
|
||||
)
|
||||
self._pool = _bmemcache_pool.BMemcacheClientPool(memcache_servers,
|
||||
arguments,
|
||||
**kwargs)
|
||||
else:
|
||||
from oslo_cache import _memcache_pool
|
||||
|
||||
arguments.pop("username", None)
|
||||
arguments.pop("password", None)
|
||||
self._pool = _memcache_pool.MemcacheClientPool(
|
||||
memcache_servers, arguments, **kwargs
|
||||
)
|
||||
self._pool = _memcache_pool.MemcacheClientPool(memcache_servers,
|
||||
arguments,
|
||||
**kwargs)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def reserve(self):
|
||||
@@ -144,20 +138,12 @@ class TokenCache(object):
|
||||
|
||||
"""
|
||||
|
||||
_CACHE_KEY_TEMPLATE = "tokens/%s"
|
||||
_CACHE_KEY_TEMPLATE = 'tokens/%s'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
log,
|
||||
cache_time=None,
|
||||
env_cache_name=None,
|
||||
memcached_servers=None,
|
||||
tls_context=None,
|
||||
use_advanced_pool=True,
|
||||
dead_retry=None,
|
||||
socket_timeout=None,
|
||||
**kwargs,
|
||||
):
|
||||
def __init__(self, log, cache_time=None,
|
||||
env_cache_name=None, memcached_servers=None, tls_context=None,
|
||||
use_advanced_pool=True, dead_retry=None, socket_timeout=None,
|
||||
**kwargs):
|
||||
self._LOG = log
|
||||
self._cache_time = cache_time
|
||||
self._env_cache_name = env_cache_name
|
||||
@@ -165,13 +151,13 @@ class TokenCache(object):
|
||||
self._tls_context = tls_context
|
||||
self._use_advanced_pool = use_advanced_pool
|
||||
self._arguments = {
|
||||
"dead_retry": dead_retry,
|
||||
"socket_timeout": socket_timeout,
|
||||
"sasl_enabled": kwargs.pop("sasl_enabled", False),
|
||||
"username": kwargs.pop("username", None),
|
||||
"password": kwargs.pop("password", None),
|
||||
"tls_enabled": kwargs.pop("tls_enabled", False),
|
||||
"tls_context": tls_context,
|
||||
'dead_retry': dead_retry,
|
||||
'socket_timeout': socket_timeout,
|
||||
'sasl_enabled': kwargs.pop("sasl_enabled", False),
|
||||
'username': kwargs.pop("username", None),
|
||||
'password': kwargs.pop("password", None),
|
||||
'tls_enabled': kwargs.pop("tls_enabled", False),
|
||||
'tls_context': tls_context
|
||||
}
|
||||
self._memcache_pool_options = kwargs
|
||||
|
||||
@@ -183,20 +169,13 @@ class TokenCache(object):
|
||||
return _EnvCachePool(cache)
|
||||
|
||||
elif self._use_advanced_pool and self._memcached_servers:
|
||||
return _MemcacheClientPool(
|
||||
self._memcached_servers, self._arguments, **self._memcache_pool_options
|
||||
)
|
||||
return _MemcacheClientPool(self._memcached_servers,
|
||||
self._arguments,
|
||||
**self._memcache_pool_options)
|
||||
|
||||
else:
|
||||
if not self._use_advanced_pool:
|
||||
self._LOG.warning(
|
||||
"Using the eventlet-unsafe cache pool is deprecated."
|
||||
"It is recommended to use eventlet-safe cache pool"
|
||||
"implementation from oslo.cache. This can be enabled"
|
||||
"through config option memcache_use_advanced_pool = True"
|
||||
)
|
||||
|
||||
return _CachePool(self._memcached_servers, self._LOG, self._arguments)
|
||||
return _CachePool(self._memcached_servers, self._LOG,
|
||||
self._arguments)
|
||||
|
||||
def initialize(self, env):
|
||||
if self._initialized:
|
||||
@@ -276,24 +255,18 @@ class TokenCache(object):
|
||||
return None
|
||||
|
||||
if isinstance(serialized, str):
|
||||
serialized = serialized.encode("utf8")
|
||||
serialized = serialized.encode('utf8')
|
||||
data = self._deserialize(serialized, context)
|
||||
|
||||
if data is None:
|
||||
# In case decryption fails, e.g. data corrupted in memcached.
|
||||
return None
|
||||
|
||||
if not isinstance(data, str):
|
||||
data = data.decode("utf-8")
|
||||
|
||||
return jsonutils.loads(data)
|
||||
|
||||
def set(self, token_id, data):
|
||||
"""Store value into memcache."""
|
||||
data = jsonutils.dumps(data)
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
|
||||
data = jsonutils.dumps(data).encode('utf-8')
|
||||
cache_key, context = self._get_cache_key(token_id)
|
||||
data_to_store = self._serialize(data, context)
|
||||
|
||||
@@ -312,24 +285,22 @@ class SecureTokenCache(TokenCache):
|
||||
super(SecureTokenCache, self).__init__(log, **kwargs)
|
||||
|
||||
if not secret_key:
|
||||
msg = _(
|
||||
"memcache_secret_key must be defined when a "
|
||||
"memcache_security_strategy is defined"
|
||||
)
|
||||
msg = _('memcache_secret_key must be defined when a '
|
||||
'memcache_security_strategy is defined')
|
||||
raise exc.ConfigurationError(msg)
|
||||
|
||||
if isinstance(security_strategy, str):
|
||||
security_strategy = security_strategy.encode("utf-8")
|
||||
security_strategy = security_strategy.encode('utf-8')
|
||||
if isinstance(secret_key, str):
|
||||
secret_key = secret_key.encode("utf-8")
|
||||
secret_key = secret_key.encode('utf-8')
|
||||
|
||||
self._security_strategy = security_strategy
|
||||
self._secret_key = secret_key
|
||||
|
||||
def _get_cache_key(self, token_id):
|
||||
context = memcache_crypt.derive_keys(
|
||||
token_id, self._secret_key, self._security_strategy
|
||||
)
|
||||
context = memcache_crypt.derive_keys(token_id,
|
||||
self._secret_key,
|
||||
self._security_strategy)
|
||||
key = self._CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(context)
|
||||
return key, context
|
||||
|
||||
@@ -337,8 +308,10 @@ class SecureTokenCache(TokenCache):
|
||||
try:
|
||||
# unprotect_data will return None if raw_cached is None
|
||||
return memcache_crypt.unprotect_data(context, data)
|
||||
except memcache_crypt.InvalidMacError as e:
|
||||
self._LOG.info("Unable to deserialize, %s", e)
|
||||
except Exception:
|
||||
msg = "Failed to decrypt/verify cache data"
|
||||
msg = 'Failed to decrypt/verify cache data'
|
||||
self._LOG.exception(msg)
|
||||
|
||||
# this should have the same effect as data not
|
||||
|
||||
@@ -18,15 +18,15 @@ from keystoneclient.v3 import client as v3_client
|
||||
from keystonemiddleware.auth_token import _exceptions as ksm_exceptions
|
||||
from keystonemiddleware.i18n import _
|
||||
|
||||
ACCESS_RULES_SUPPORT = "1"
|
||||
ACCESS_RULES_SUPPORT = '1'
|
||||
|
||||
|
||||
class _RequestStrategy(object):
|
||||
|
||||
AUTH_VERSION = None
|
||||
|
||||
def __init__(
|
||||
self, adap, include_service_catalog=None, requested_auth_interface=None
|
||||
):
|
||||
def __init__(self, adap, include_service_catalog=None,
|
||||
requested_auth_interface=None):
|
||||
self._include_service_catalog = include_service_catalog
|
||||
self._requested_auth_interface = requested_auth_interface
|
||||
|
||||
@@ -35,13 +35,14 @@ class _RequestStrategy(object):
|
||||
|
||||
|
||||
class _V3RequestStrategy(_RequestStrategy):
|
||||
|
||||
AUTH_VERSION = (3, 0)
|
||||
|
||||
def __init__(self, adap, **kwargs):
|
||||
super(_V3RequestStrategy, self).__init__(adap, **kwargs)
|
||||
client_args = {"session": adap}
|
||||
client_args = {'session': adap}
|
||||
if self._requested_auth_interface:
|
||||
client_args["interface"] = self._requested_auth_interface
|
||||
client_args['interface'] = self._requested_auth_interface
|
||||
self._client = v3_client.Client(**client_args)
|
||||
|
||||
def verify_token(self, token, allow_expired=False):
|
||||
@@ -49,14 +50,13 @@ class _V3RequestStrategy(_RequestStrategy):
|
||||
token,
|
||||
include_catalog=self._include_service_catalog,
|
||||
allow_expired=allow_expired,
|
||||
access_rules_support=ACCESS_RULES_SUPPORT,
|
||||
)
|
||||
access_rules_support=ACCESS_RULES_SUPPORT)
|
||||
|
||||
if not auth_ref:
|
||||
msg = _("Failed to fetch token data from identity server")
|
||||
msg = _('Failed to fetch token data from identity server')
|
||||
raise ksm_exceptions.InvalidToken(msg)
|
||||
|
||||
return {"token": auth_ref}
|
||||
return {'token': auth_ref}
|
||||
|
||||
|
||||
_REQUEST_STRATEGIES = [_V3RequestStrategy]
|
||||
@@ -71,14 +71,8 @@ class IdentityServer(object):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
log,
|
||||
adap,
|
||||
include_service_catalog=None,
|
||||
requested_auth_version=None,
|
||||
requested_auth_interface=None,
|
||||
):
|
||||
def __init__(self, log, adap, include_service_catalog=None,
|
||||
requested_auth_version=None, requested_auth_interface=None):
|
||||
self._LOG = log
|
||||
self._adapter = adap
|
||||
self._include_service_catalog = include_service_catalog
|
||||
@@ -105,20 +99,16 @@ class IdentityServer(object):
|
||||
self._request_strategy_obj = strategy_class(
|
||||
self._adapter,
|
||||
include_service_catalog=self._include_service_catalog,
|
||||
requested_auth_interface=self._requested_auth_interface,
|
||||
)
|
||||
requested_auth_interface=self._requested_auth_interface)
|
||||
|
||||
return self._request_strategy_obj
|
||||
|
||||
def _get_strategy_class(self):
|
||||
if self._requested_auth_version:
|
||||
if not discover.version_match(
|
||||
_V3RequestStrategy.AUTH_VERSION, self._requested_auth_version
|
||||
):
|
||||
self._LOG.info(
|
||||
"A version other than v3 was requested: %s",
|
||||
self._requested_auth_interface,
|
||||
)
|
||||
if not discover.version_match(_V3RequestStrategy.AUTH_VERSION,
|
||||
self._requested_auth_version):
|
||||
self._LOG.info('A version other than v3 was requested: %s',
|
||||
self._requested_auth_interface)
|
||||
# Return v3, even if the request is unknown
|
||||
return _V3RequestStrategy
|
||||
|
||||
@@ -126,17 +116,15 @@ class IdentityServer(object):
|
||||
# discovering available versions from the server
|
||||
for klass in _REQUEST_STRATEGIES:
|
||||
if self._adapter.get_endpoint(version=klass.AUTH_VERSION):
|
||||
self._LOG.debug(
|
||||
"Auth Token confirmed use of %s apis", klass.AUTH_VERSION
|
||||
)
|
||||
self._LOG.debug('Auth Token confirmed use of %s apis',
|
||||
klass.AUTH_VERSION)
|
||||
return klass
|
||||
|
||||
versions = ["v%d.%d" % s.AUTH_VERSION for s in _REQUEST_STRATEGIES]
|
||||
self._LOG.error(
|
||||
"No attempted versions [%s] supported by server", ", ".join(versions)
|
||||
)
|
||||
versions = ['v%d.%d' % s.AUTH_VERSION for s in _REQUEST_STRATEGIES]
|
||||
self._LOG.error('No attempted versions [%s] supported by server',
|
||||
', '.join(versions))
|
||||
|
||||
msg = _("No compatible apis supported by server")
|
||||
msg = _('No compatible apis supported by server')
|
||||
raise ksm_exceptions.ServiceError(msg)
|
||||
|
||||
def verify_token(self, user_token, retry=True, allow_expired=False):
|
||||
@@ -155,31 +143,28 @@ class IdentityServer(object):
|
||||
"""
|
||||
try:
|
||||
auth_ref = self._request_strategy.verify_token(
|
||||
user_token, allow_expired=allow_expired
|
||||
)
|
||||
user_token,
|
||||
allow_expired=allow_expired)
|
||||
except ksa_exceptions.NotFound as e:
|
||||
self._LOG.info("Authorization failed for token")
|
||||
self._LOG.info("Identity response: %s", e.response.text)
|
||||
raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
|
||||
self._LOG.info('Authorization failed for token')
|
||||
self._LOG.info('Identity response: %s', e.response.text)
|
||||
raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
|
||||
except ksa_exceptions.Unauthorized as e:
|
||||
self._LOG.info("Identity server rejected authorization")
|
||||
self._LOG.warning("Identity response: %s", e.response.text)
|
||||
self._LOG.info('Identity server rejected authorization')
|
||||
self._LOG.warning('Identity response: %s', e.response.text)
|
||||
if retry:
|
||||
self._LOG.info("Retrying validation")
|
||||
self._LOG.info('Retrying validation')
|
||||
return self.verify_token(user_token, False)
|
||||
msg = _(
|
||||
"Identity server rejected authorization necessary to fetch token data"
|
||||
)
|
||||
msg = _('Identity server rejected authorization necessary to '
|
||||
'fetch token data')
|
||||
raise ksm_exceptions.ServiceError(msg)
|
||||
except ksa_exceptions.HttpError as e:
|
||||
self._LOG.error(
|
||||
"Bad response code while validating token: %s %s",
|
||||
e.http_status,
|
||||
e.message,
|
||||
)
|
||||
if hasattr(e.response, "text"):
|
||||
self._LOG.warning("Identity response: %s", e.response.text)
|
||||
msg = _("Failed to fetch token data from identity server")
|
||||
'Bad response code while validating token: %s %s',
|
||||
e.http_status, e.message)
|
||||
if hasattr(e.response, 'text'):
|
||||
self._LOG.warning('Identity response: %s', e.response.text)
|
||||
msg = _('Failed to fetch token data from identity server')
|
||||
raise ksm_exceptions.ServiceError(msg)
|
||||
else:
|
||||
return auth_ref
|
||||
|
||||
@@ -76,13 +76,11 @@ class CryptoUnavailableError(Exception):
|
||||
|
||||
def assert_crypto_availability(f):
|
||||
"""Ensure cryptography module is available."""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwds):
|
||||
if ciphers is None:
|
||||
raise CryptoUnavailableError()
|
||||
return f(*args, **kwds)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -109,12 +107,10 @@ def derive_keys(token, secret, strategy):
|
||||
strategy = strategy.encode()
|
||||
|
||||
digest = hmac.new(secret, token + strategy, HASH_FUNCTION).digest()
|
||||
return {
|
||||
"CACHE_KEY": digest[:DIGEST_SPLIT],
|
||||
"MAC": digest[DIGEST_SPLIT : 2 * DIGEST_SPLIT],
|
||||
"ENCRYPTION": digest[2 * DIGEST_SPLIT :],
|
||||
"strategy": strategy,
|
||||
}
|
||||
return {'CACHE_KEY': digest[:DIGEST_SPLIT],
|
||||
'MAC': digest[DIGEST_SPLIT: 2 * DIGEST_SPLIT],
|
||||
'ENCRYPTION': digest[2 * DIGEST_SPLIT:],
|
||||
'strategy': strategy}
|
||||
|
||||
|
||||
def sign_data(key, data):
|
||||
@@ -137,8 +133,9 @@ def encrypt_data(key, data):
|
||||
"""
|
||||
iv = os.urandom(16)
|
||||
cipher = ciphers.Cipher(
|
||||
algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()
|
||||
)
|
||||
algorithms.AES(key),
|
||||
modes.CBC(iv),
|
||||
backend=crypto_backends.default_backend())
|
||||
|
||||
# AES algorithm uses block size of 16 bytes = 128 bits, defined in
|
||||
# algorithms.AES.block_size. Previously, we manually padded this using
|
||||
@@ -155,13 +152,14 @@ def decrypt_data(key, data):
|
||||
"""Decrypt the data with the given secret key."""
|
||||
iv = data[:16]
|
||||
cipher = ciphers.Cipher(
|
||||
algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()
|
||||
)
|
||||
algorithms.AES(key),
|
||||
modes.CBC(iv),
|
||||
backend=crypto_backends.default_backend())
|
||||
try:
|
||||
decryptor = cipher.decryptor()
|
||||
result = decryptor.update(data[16:]) + decryptor.finalize()
|
||||
except Exception:
|
||||
raise DecryptError(_("Encrypted data appears to be corrupted."))
|
||||
raise DecryptError(_('Encrypted data appears to be corrupted.'))
|
||||
|
||||
# Strip the last n padding bytes where n is the last value in
|
||||
# the plaintext
|
||||
@@ -176,12 +174,12 @@ def protect_data(keys, data):
|
||||
suitable for storage in the cache.
|
||||
|
||||
"""
|
||||
if keys["strategy"] == b"ENCRYPT":
|
||||
data = encrypt_data(keys["ENCRYPTION"], data)
|
||||
if keys['strategy'] == b'ENCRYPT':
|
||||
data = encrypt_data(keys['ENCRYPTION'], data)
|
||||
|
||||
encoded_data = base64.b64encode(data)
|
||||
|
||||
signature = sign_data(keys["MAC"], encoded_data)
|
||||
signature = sign_data(keys['MAC'], encoded_data)
|
||||
return signature + encoded_data
|
||||
|
||||
|
||||
@@ -199,17 +197,19 @@ def unprotect_data(keys, signed_data):
|
||||
|
||||
# First we calculate the signature
|
||||
provided_mac = signed_data[:DIGEST_LENGTH_B64]
|
||||
calculated_mac = sign_data(keys["MAC"], signed_data[DIGEST_LENGTH_B64:])
|
||||
calculated_mac = sign_data(
|
||||
keys['MAC'],
|
||||
signed_data[DIGEST_LENGTH_B64:])
|
||||
|
||||
# Then verify that it matches the provided value
|
||||
if not hmac.compare_digest(provided_mac, calculated_mac):
|
||||
raise InvalidMacError(_("Invalid MAC; data appears to be corrupted."))
|
||||
raise InvalidMacError(_('Invalid MAC; data appears to be corrupted.'))
|
||||
|
||||
data = base64.b64decode(signed_data[DIGEST_LENGTH_B64:])
|
||||
|
||||
# then if necessary decrypt the data
|
||||
if keys["strategy"] == b"ENCRYPT":
|
||||
data = decrypt_data(keys["ENCRYPTION"], data)
|
||||
if keys['strategy'] == b'ENCRYPT':
|
||||
data = decrypt_data(keys['ENCRYPTION'], data)
|
||||
|
||||
return data
|
||||
|
||||
@@ -221,4 +221,4 @@ def get_cache_key(keys):
|
||||
suitable for use as a cache key in memcached.
|
||||
|
||||
"""
|
||||
return base64.b64encode(keys["CACHE_KEY"])
|
||||
return base64.b64encode(keys['CACHE_KEY'])
|
||||
@@ -28,221 +28,167 @@ from keystonemiddleware.auth_token import _base
|
||||
# options via CONF.
|
||||
|
||||
_OPTS = [
|
||||
cfg.StrOpt(
|
||||
"www_authenticate_uri",
|
||||
help='Complete "public" Identity API endpoint. This endpoint'
|
||||
' should not be an "admin" endpoint, as it should be accessible'
|
||||
" by all end users. Unauthenticated clients are redirected to"
|
||||
" this endpoint to authenticate. Although this endpoint should"
|
||||
" ideally be unversioned, client support in the wild varies.",
|
||||
),
|
||||
cfg.StrOpt("auth_version", help="API version of the Identity API endpoint."),
|
||||
cfg.StrOpt(
|
||||
"interface",
|
||||
default="internal",
|
||||
help="Interface to use for the Identity API endpoint. Valid"
|
||||
' values are "public", "internal" (default) or "admin".',
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"delay_auth_decision",
|
||||
default=False,
|
||||
help="Do not handle authorization requests within the"
|
||||
" middleware, but delegate the authorization decision to"
|
||||
" downstream WSGI components.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"http_connect_timeout",
|
||||
help="Request timeout value for communicating with Identity API server.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"http_request_max_retries",
|
||||
default=3,
|
||||
help="How many times are we trying to reconnect when"
|
||||
" communicating with Identity API Server.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"cache",
|
||||
help="Request environment key where the Swift cache object is"
|
||||
" stored. When auth_token middleware is deployed with a Swift"
|
||||
" cache, use this option to have the middleware share a caching"
|
||||
" backend with swift. Otherwise, use the ``memcached_servers``"
|
||||
" option instead.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"certfile", help="Required if identity server requires client certificate"
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"keyfile", help="Required if identity server requires client certificate"
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"cafile",
|
||||
help="A PEM encoded Certificate Authority to use when "
|
||||
"verifying HTTPs connections. Defaults to system CAs.",
|
||||
),
|
||||
cfg.BoolOpt("insecure", default=False, help="Verify HTTPS connections."),
|
||||
cfg.StrOpt(
|
||||
"region_name", help="The region in which the identity server can be found."
|
||||
),
|
||||
cfg.ListOpt(
|
||||
"memcached_servers",
|
||||
deprecated_name="memcache_servers",
|
||||
help="Optionally specify a list of memcached server(s) to"
|
||||
" use for caching. If left undefined, tokens will instead be"
|
||||
" cached in-process.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"token_cache_time",
|
||||
default=300,
|
||||
help="In order to prevent excessive effort spent validating"
|
||||
" tokens, the middleware caches previously-seen tokens for a"
|
||||
" configurable duration (in seconds). Set to -1 to disable"
|
||||
" caching completely.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_security_strategy",
|
||||
default="None",
|
||||
choices=("None", "MAC", "ENCRYPT"),
|
||||
ignore_case=True,
|
||||
help="(Optional) If defined, indicate whether token data"
|
||||
" should be authenticated or authenticated and encrypted."
|
||||
" If MAC, token data is authenticated (with HMAC) in the cache."
|
||||
" If ENCRYPT, token data is encrypted and authenticated in the"
|
||||
" cache. If the value is not one of these options or empty,"
|
||||
" auth_token will raise an exception on initialization.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_secret_key",
|
||||
secret=True,
|
||||
help="(Optional, mandatory if memcache_security_strategy is"
|
||||
" defined) This string is used for key derivation.",
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"memcache_tls_enabled",
|
||||
default=False,
|
||||
help="(Optional) Global toggle for TLS usage when comunicating"
|
||||
" with the caching servers.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_tls_cafile",
|
||||
help="(Optional) Path to a file of concatenated CA certificates"
|
||||
" in PEM format necessary to establish the caching server's"
|
||||
" authenticity. If tls_enabled is False, this option is"
|
||||
" ignored.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_tls_certfile",
|
||||
help="(Optional) Path to a single file in PEM format containing"
|
||||
" the client's certificate as well as any number of CA"
|
||||
" certificates needed to establish the certificate's"
|
||||
" authenticity. This file is only required when client side"
|
||||
" authentication is necessary. If tls_enabled is False, this"
|
||||
" option is ignored.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_tls_keyfile",
|
||||
help="(Optional) Path to a single file containing the client's"
|
||||
" private key in. Otherwhise the private key will be taken from"
|
||||
" the file specified in tls_certfile. If tls_enabled is False,"
|
||||
" this option is ignored.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"memcache_tls_allowed_ciphers",
|
||||
help="(Optional) Set the available ciphers for sockets created"
|
||||
" with the TLS context. It should be a string in the OpenSSL"
|
||||
" cipher list format. If not specified, all OpenSSL enabled"
|
||||
" ciphers will be available.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"memcache_pool_dead_retry",
|
||||
default=5 * 60,
|
||||
help="(Optional) Number of seconds memcached server is"
|
||||
" considered dead before it is tried again.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"memcache_pool_maxsize",
|
||||
default=10,
|
||||
help="(Optional) Maximum total number of open connections to"
|
||||
" every memcached server.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"memcache_pool_socket_timeout",
|
||||
default=3,
|
||||
help="(Optional) Socket timeout in seconds for communicating "
|
||||
"with a memcached server.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"memcache_pool_unused_timeout",
|
||||
default=60,
|
||||
help="(Optional) Number of seconds a connection to memcached"
|
||||
" is held unused in the pool before it is closed.",
|
||||
),
|
||||
cfg.IntOpt(
|
||||
"memcache_pool_conn_get_timeout",
|
||||
default=10,
|
||||
help="(Optional) Number of seconds that an operation will wait "
|
||||
"to get a memcached client connection from the pool.",
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"memcache_use_advanced_pool",
|
||||
default=True,
|
||||
help="(Optional) Use the advanced (eventlet safe) memcached client pool.",
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"include_service_catalog",
|
||||
default=True,
|
||||
help="(Optional) Indicate whether to set the X-Service-Catalog"
|
||||
" header. If False, middleware will not ask for service"
|
||||
" catalog on token validation and will not set the"
|
||||
" X-Service-Catalog header.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"enforce_token_bind",
|
||||
default="permissive",
|
||||
help="Used to control the use and type of token binding. Can"
|
||||
' be set to: "disabled" to not check token binding.'
|
||||
' "permissive" (default) to validate binding information if the'
|
||||
" bind type is of a form known to the server and ignore it if"
|
||||
' not. "strict" like "permissive" but if the bind type is'
|
||||
' unknown the token will be rejected. "required" any form of'
|
||||
" token binding is needed to be allowed. Finally the name of a"
|
||||
" binding method that must be present in tokens.",
|
||||
),
|
||||
cfg.ListOpt(
|
||||
"service_token_roles",
|
||||
default=["service"],
|
||||
help="A choice of roles that must be present in a service"
|
||||
" token. Service tokens are allowed to request that an expired"
|
||||
" token can be used and so this check should tightly control"
|
||||
" that only actual services should be sending this token."
|
||||
" Roles here are applied as an ANY check so any role in this"
|
||||
" list must be present. For backwards compatibility reasons"
|
||||
" this currently only affects the allow_expired check.",
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"service_token_roles_required",
|
||||
default=True,
|
||||
help="When set to True, service tokens must contain a role"
|
||||
" from the service_token_roles list to be considered valid."
|
||||
" This prevents end-user tokens from being used as service"
|
||||
" tokens and ensures proper service-to-service authentication."
|
||||
" Setting this to False is NOT RECOMMENDED as it allows any"
|
||||
" valid token to be used as a service token, which can bypass"
|
||||
" access-rule checks and weaken composite-auth protections.",
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"service_type",
|
||||
help="The name or type of the service as it appears in the"
|
||||
" service catalog. This is used to validate tokens that have"
|
||||
" restricted access rules.",
|
||||
),
|
||||
cfg.BoolOpt(
|
||||
"memcache_sasl_enabled",
|
||||
default=False,
|
||||
help="Enable the SASL(Simple Authentication and Security"
|
||||
" Layer) if the SASL_enable is true, else disable.",
|
||||
),
|
||||
cfg.StrOpt("memcache_username", default="", help="the user name for the SASL"),
|
||||
cfg.StrOpt("memcache_password", default="", help="the username password for SASL"),
|
||||
cfg.StrOpt('www_authenticate_uri',
|
||||
help='Complete "public" Identity API endpoint. This endpoint'
|
||||
' should not be an "admin" endpoint, as it should be accessible'
|
||||
' by all end users. Unauthenticated clients are redirected to'
|
||||
' this endpoint to authenticate. Although this endpoint should'
|
||||
' ideally be unversioned, client support in the wild varies.'),
|
||||
cfg.StrOpt('auth_version',
|
||||
help='API version of the Identity API endpoint.'),
|
||||
cfg.StrOpt('interface',
|
||||
default='internal',
|
||||
choices=('public', 'internal', 'admin'),
|
||||
help='Interface to use for the Identity API endpoint.'),
|
||||
cfg.BoolOpt('delay_auth_decision',
|
||||
default=False,
|
||||
help='Do not handle authorization requests within the'
|
||||
' middleware, but delegate the authorization decision to'
|
||||
' downstream WSGI components.'),
|
||||
cfg.IntOpt('http_connect_timeout',
|
||||
help='Request timeout value for communicating with Identity'
|
||||
' API server.'),
|
||||
cfg.IntOpt('http_request_max_retries',
|
||||
default=3,
|
||||
help='How many times are we trying to reconnect when'
|
||||
' communicating with Identity API Server.'),
|
||||
cfg.StrOpt('cache',
|
||||
help='Request environment key where the Swift cache object is'
|
||||
' stored. When auth_token middleware is deployed with a Swift'
|
||||
' cache, use this option to have the middleware share a caching'
|
||||
' backend with swift. Otherwise, use the ``memcached_servers``'
|
||||
' option instead.'),
|
||||
cfg.StrOpt('certfile',
|
||||
help='Required if identity server requires client certificate'),
|
||||
cfg.StrOpt('keyfile',
|
||||
help='Required if identity server requires client certificate'),
|
||||
cfg.StrOpt('cafile',
|
||||
help='A PEM encoded Certificate Authority to use when '
|
||||
'verifying HTTPs connections. Defaults to system CAs.'),
|
||||
cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'),
|
||||
cfg.StrOpt('region_name',
|
||||
help='The region in which the identity server can be found.'),
|
||||
cfg.ListOpt('memcached_servers',
|
||||
deprecated_name='memcache_servers',
|
||||
help='Optionally specify a list of memcached server(s) to'
|
||||
' use for caching. If left undefined, tokens will instead be'
|
||||
' cached in-process.'),
|
||||
cfg.IntOpt('token_cache_time',
|
||||
default=300,
|
||||
help='In order to prevent excessive effort spent validating'
|
||||
' tokens, the middleware caches previously-seen tokens for a'
|
||||
' configurable duration (in seconds). Set to -1 to disable'
|
||||
' caching completely.'),
|
||||
cfg.StrOpt('memcache_security_strategy',
|
||||
default='None',
|
||||
choices=(('None',
|
||||
'Token data is not authenticated or encrypted.'),
|
||||
('MAC',
|
||||
'Token data is authenticated (with HMAC).'),
|
||||
('ENCRYPT',
|
||||
'Token data is encrypted and authenticated.')),
|
||||
ignore_case=True,
|
||||
help='(Optional) If defined, indicate whether token data'
|
||||
' should be authenticated or authenticated and encrypted.'),
|
||||
cfg.StrOpt('memcache_secret_key',
|
||||
secret=True,
|
||||
help='(Optional, mandatory if memcache_security_strategy is'
|
||||
' defined) This string is used for key derivation.'),
|
||||
cfg.BoolOpt('memcache_tls_enabled',
|
||||
default=False,
|
||||
help='(Optional) Global toggle for TLS usage when comunicating'
|
||||
' with the caching servers.'),
|
||||
cfg.StrOpt('memcache_tls_cafile',
|
||||
help='(Optional) Path to a file of concatenated CA certificates'
|
||||
' in PEM format necessary to establish the caching server\'s'
|
||||
' authenticity. If tls_enabled is False, this option is'
|
||||
' ignored.'),
|
||||
cfg.StrOpt('memcache_tls_certfile',
|
||||
help='(Optional) Path to a single file in PEM format containing'
|
||||
' the client\'s certificate as well as any number of CA'
|
||||
' certificates needed to establish the certificate\'s'
|
||||
' authenticity. This file is only required when client side'
|
||||
' authentication is necessary. If tls_enabled is False, this'
|
||||
' option is ignored.'),
|
||||
cfg.StrOpt('memcache_tls_keyfile',
|
||||
help='(Optional) Path to a single file containing the client\'s'
|
||||
' private key in. Otherwhise the private key will be taken from'
|
||||
' the file specified in tls_certfile. If tls_enabled is False,'
|
||||
' this option is ignored.'),
|
||||
cfg.StrOpt('memcache_tls_allowed_ciphers',
|
||||
help='(Optional) Set the available ciphers for sockets created'
|
||||
' with the TLS context. It should be a string in the OpenSSL'
|
||||
' cipher list format. If not specified, all OpenSSL enabled'
|
||||
' ciphers will be available.'),
|
||||
cfg.IntOpt('memcache_pool_dead_retry',
|
||||
default=5 * 60,
|
||||
help='(Optional) Number of seconds memcached server is'
|
||||
' considered dead before it is tried again.'),
|
||||
cfg.IntOpt('memcache_pool_maxsize',
|
||||
default=10,
|
||||
help='(Optional) Maximum total number of open connections to'
|
||||
' every memcached server.'),
|
||||
cfg.IntOpt('memcache_pool_socket_timeout',
|
||||
default=3,
|
||||
help='(Optional) Socket timeout in seconds for communicating '
|
||||
'with a memcached server.'),
|
||||
cfg.IntOpt('memcache_pool_unused_timeout',
|
||||
default=60,
|
||||
help='(Optional) Number of seconds a connection to memcached'
|
||||
' is held unused in the pool before it is closed.'),
|
||||
cfg.IntOpt('memcache_pool_conn_get_timeout',
|
||||
default=10,
|
||||
help='(Optional) Number of seconds that an operation will wait '
|
||||
'to get a memcached client connection from the pool.'),
|
||||
cfg.BoolOpt('memcache_use_advanced_pool',
|
||||
default=True,
|
||||
help='(Optional) Use the advanced (eventlet safe) memcached '
|
||||
'client pool.'),
|
||||
cfg.BoolOpt('include_service_catalog',
|
||||
default=True,
|
||||
help='(Optional) Indicate whether to set the X-Service-Catalog'
|
||||
' header. If False, middleware will not ask for service'
|
||||
' catalog on token validation and will not set the'
|
||||
' X-Service-Catalog header.'),
|
||||
cfg.StrOpt('enforce_token_bind',
|
||||
default='permissive',
|
||||
help='Used to control the use and type of token binding. Can'
|
||||
' be set to: "disabled" to not check token binding.'
|
||||
' "permissive" (default) to validate binding information if the'
|
||||
' bind type is of a form known to the server and ignore it if'
|
||||
' not. "strict" like "permissive" but if the bind type is'
|
||||
' unknown the token will be rejected. "required" any form of'
|
||||
' token binding is needed to be allowed. Finally the name of a'
|
||||
' binding method that must be present in tokens.'),
|
||||
cfg.ListOpt('service_token_roles', default=['service'],
|
||||
help='A choice of roles that must be present in a service'
|
||||
' token. Service tokens are allowed to request that an expired'
|
||||
' token can be used and so this check should tightly control'
|
||||
' that only actual services should be sending this token.'
|
||||
' Roles here are applied as an ANY check so any role in this'
|
||||
' list must be present. For backwards compatibility reasons'
|
||||
' this currently only affects the allow_expired check.'),
|
||||
cfg.BoolOpt('service_token_roles_required', default=True,
|
||||
help='When set to True, service tokens must contain a role'
|
||||
' from the service_token_roles list to be considered valid.'
|
||||
' This prevents end-user tokens from being used as service'
|
||||
' tokens and ensures proper service-to-service authentication.'
|
||||
' Setting this to False is NOT RECOMMENDED as it allows any'
|
||||
' valid token to be used as a service token, which can bypass'
|
||||
' access-rule checks and weaken composite-auth protections.'),
|
||||
cfg.StrOpt('service_type',
|
||||
help='The name or type of the service as it appears in the'
|
||||
' service catalog. This is used to validate tokens that have'
|
||||
' restricted access rules.'),
|
||||
cfg.BoolOpt('memcache_sasl_enabled',
|
||||
default=False,
|
||||
help='Enable the SASL(Simple Authentication and Security'
|
||||
' Layer) if the SASL_enable is true, else disable.'),
|
||||
cfg.StrOpt('memcache_username',
|
||||
help='the user name for the SASL'),
|
||||
cfg.StrOpt('memcache_password',
|
||||
secret=True,
|
||||
help='the username password for SASL'),
|
||||
]
|
||||
|
||||
|
||||
@@ -255,7 +201,9 @@ auth_token_opts = [
|
||||
(_base.AUTHTOKEN_GROUP, _OPTS + loading.get_auth_common_conf_options()),
|
||||
]
|
||||
|
||||
__all__ = ("list_opts",)
|
||||
__all__ = (
|
||||
'list_opts',
|
||||
)
|
||||
|
||||
|
||||
def list_opts():
|
||||
@@ -279,6 +227,12 @@ def list_opts():
|
||||
|
||||
:returns: a list of (group_name, opts) tuples
|
||||
"""
|
||||
auth_token_opts = _OPTS + loading.get_auth_common_conf_options()
|
||||
auth_token_opts = (
|
||||
_OPTS +
|
||||
loading.get_auth_common_conf_options() +
|
||||
loading.get_auth_plugin_conf_options('password') +
|
||||
loading.get_auth_plugin_conf_options('v2password') +
|
||||
loading.get_auth_plugin_conf_options('v3password')
|
||||
)
|
||||
|
||||
return [(_base.AUTHTOKEN_GROUP, copy.deepcopy(auth_token_opts))]
|
||||
@@ -21,9 +21,9 @@ def _normalize_catalog(catalog):
|
||||
services = []
|
||||
for v3_service in catalog:
|
||||
# first copy over the entries we allow for the service
|
||||
service = {"type": v3_service["type"]}
|
||||
service = {'type': v3_service['type']}
|
||||
try:
|
||||
service["name"] = v3_service["name"]
|
||||
service['name'] = v3_service['name']
|
||||
except KeyError: # nosec
|
||||
# v3 service doesn't have a name, move on.
|
||||
pass
|
||||
@@ -32,18 +32,18 @@ def _normalize_catalog(catalog):
|
||||
# URL not per group we have to collect all the entries of the same
|
||||
# region together before adding it to the new service.
|
||||
regions = {}
|
||||
for v3_endpoint in v3_service.get("endpoints", []):
|
||||
region_name = v3_endpoint.get("region")
|
||||
for v3_endpoint in v3_service.get('endpoints', []):
|
||||
region_name = v3_endpoint.get('region')
|
||||
try:
|
||||
region = regions[region_name]
|
||||
except KeyError:
|
||||
region = {"region": region_name} if region_name else {}
|
||||
region = {'region': region_name} if region_name else {}
|
||||
regions[region_name] = region
|
||||
|
||||
interface_name = v3_endpoint["interface"].lower() + "URL"
|
||||
region[interface_name] = v3_endpoint["url"]
|
||||
interface_name = v3_endpoint['interface'].lower() + 'URL'
|
||||
region[interface_name] = v3_endpoint['url']
|
||||
|
||||
service["endpoints"] = list(regions.values())
|
||||
service['endpoints'] = list(regions.values())
|
||||
services.append(service)
|
||||
|
||||
return services
|
||||
@@ -55,63 +55,65 @@ def _is_admin_project(auth_ref):
|
||||
Headers must be strings so we can't simply pass a boolean value through so
|
||||
return a True or False string to signal the admin project.
|
||||
"""
|
||||
return "True" if auth_ref.is_admin_project else "False"
|
||||
return 'True' if auth_ref.is_admin_project else 'False'
|
||||
|
||||
|
||||
def _get_system_scope(auth_ref):
|
||||
"""Return the scope information of a system scoped token."""
|
||||
if auth_ref.system_scoped:
|
||||
if auth_ref.system.get("all"):
|
||||
return "all"
|
||||
if auth_ref.system.get('all'):
|
||||
return 'all'
|
||||
|
||||
|
||||
# NOTE(jamielennox): this should probably be moved into its own file, but at
|
||||
# the moment there's no real logic here so just keep it locally.
|
||||
class _AuthTokenResponse(webob.Response):
|
||||
|
||||
default_content_type = None # prevents webob assigning a content type
|
||||
|
||||
|
||||
class _AuthTokenRequest(webob.Request):
|
||||
|
||||
ResponseClass = _AuthTokenResponse
|
||||
|
||||
_HEADER_TEMPLATE = {
|
||||
"X%s-Domain-Id": "domain_id",
|
||||
"X%s-Domain-Name": "domain_name",
|
||||
"X%s-Project-Id": "project_id",
|
||||
"X%s-Project-Name": "project_name",
|
||||
"X%s-Project-Domain-Id": "project_domain_id",
|
||||
"X%s-Project-Domain-Name": "project_domain_name",
|
||||
"X%s-User-Id": "user_id",
|
||||
"X%s-User-Name": "username",
|
||||
"X%s-User-Domain-Id": "user_domain_id",
|
||||
"X%s-User-Domain-Name": "user_domain_name",
|
||||
'X%s-Domain-Id': 'domain_id',
|
||||
'X%s-Domain-Name': 'domain_name',
|
||||
'X%s-Project-Id': 'project_id',
|
||||
'X%s-Project-Name': 'project_name',
|
||||
'X%s-Project-Domain-Id': 'project_domain_id',
|
||||
'X%s-Project-Domain-Name': 'project_domain_name',
|
||||
'X%s-User-Id': 'user_id',
|
||||
'X%s-User-Name': 'username',
|
||||
'X%s-User-Domain-Id': 'user_domain_id',
|
||||
'X%s-User-Domain-Name': 'user_domain_name',
|
||||
}
|
||||
|
||||
_ROLES_TEMPLATE = "X%s-Roles"
|
||||
_ROLES_TEMPLATE = 'X%s-Roles'
|
||||
|
||||
_USER_HEADER_PREFIX = ""
|
||||
_SERVICE_HEADER_PREFIX = "-Service"
|
||||
_USER_HEADER_PREFIX = ''
|
||||
_SERVICE_HEADER_PREFIX = '-Service'
|
||||
|
||||
_USER_STATUS_HEADER = "X-Identity-Status"
|
||||
_SERVICE_STATUS_HEADER = "X-Service-Identity-Status"
|
||||
_USER_STATUS_HEADER = 'X-Identity-Status'
|
||||
_SERVICE_STATUS_HEADER = 'X-Service-Identity-Status'
|
||||
|
||||
_ADMIN_PROJECT_HEADER = "X-Is-Admin-Project"
|
||||
_SYSTEM_SCOPE_HEADER = "OpenStack-System-Scope"
|
||||
_ADMIN_PROJECT_HEADER = 'X-Is-Admin-Project'
|
||||
_SYSTEM_SCOPE_HEADER = 'OpenStack-System-Scope'
|
||||
|
||||
_SERVICE_CATALOG_HEADER = "X-Service-Catalog"
|
||||
_TOKEN_AUTH = "keystone.token_auth" # nosec # noqa: S105
|
||||
_TOKEN_INFO = "keystone.token_info" # nosec # noqa: S105
|
||||
_SERVICE_CATALOG_HEADER = 'X-Service-Catalog'
|
||||
_TOKEN_AUTH = 'keystone.token_auth' # nosec
|
||||
_TOKEN_INFO = 'keystone.token_info' # nosec
|
||||
|
||||
_CONFIRMED = "Confirmed"
|
||||
_INVALID = "Invalid"
|
||||
_CONFIRMED = 'Confirmed'
|
||||
_INVALID = 'Invalid'
|
||||
|
||||
# header names that have been deprecated in favour of something else.
|
||||
_DEPRECATED_HEADER_MAP = {
|
||||
"X-Role": "X-Roles",
|
||||
"X-User": "X-User-Name",
|
||||
"X-Tenant-Id": "X-Project-Id",
|
||||
"X-Tenant-Name": "X-Project-Name",
|
||||
"X-Tenant": "X-Project-Name",
|
||||
'X-Role': 'X-Roles',
|
||||
'X-User': 'X-User-Name',
|
||||
'X-Tenant-Id': 'X-Project-Id',
|
||||
'X-Tenant-Name': 'X-Project-Name',
|
||||
'X-Tenant': 'X-Project-Name',
|
||||
}
|
||||
|
||||
def _confirmed(cls, value):
|
||||
@@ -132,7 +134,8 @@ class _AuthTokenRequest(webob.Request):
|
||||
|
||||
@property
|
||||
def user_token(self):
|
||||
return self.headers.get("X-Auth-Token", self.headers.get("X-Storage-Token"))
|
||||
return self.headers.get('X-Auth-Token',
|
||||
self.headers.get('X-Storage-Token'))
|
||||
|
||||
@property
|
||||
def service_token_valid(self):
|
||||
@@ -150,10 +153,10 @@ class _AuthTokenRequest(webob.Request):
|
||||
|
||||
@property
|
||||
def service_token(self):
|
||||
return self.headers.get("X-Service-Token")
|
||||
return self.headers.get('X-Service-Token')
|
||||
|
||||
def _set_auth_headers(self, auth_ref, prefix):
|
||||
names = ",".join(auth_ref.role_names)
|
||||
names = ','.join(auth_ref.role_names)
|
||||
self.headers[self._ROLES_TEMPLATE % prefix] = names
|
||||
self.headers[self._SYSTEM_SCOPE_HEADER] = _get_system_scope(auth_ref)
|
||||
|
||||
@@ -186,7 +189,7 @@ class _AuthTokenRequest(webob.Request):
|
||||
return
|
||||
|
||||
catalog = auth_ref.service_catalog.catalog
|
||||
if auth_ref.version == "v3":
|
||||
if auth_ref.version == 'v3':
|
||||
catalog = _normalize_catalog(catalog)
|
||||
|
||||
c = jsonutils.dumps(catalog)
|
||||
@@ -233,7 +236,7 @@ class _AuthTokenRequest(webob.Request):
|
||||
:rtype: str or None
|
||||
"""
|
||||
try:
|
||||
auth_type = self.environ["AUTH_TYPE"]
|
||||
auth_type = self.environ['AUTH_TYPE']
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
|
||||
@@ -14,12 +14,10 @@ from keystoneauth1.identity import base as base_identity
|
||||
|
||||
|
||||
def _log_format(auth_ref):
|
||||
roles = ",".join(auth_ref.role_names)
|
||||
return "user_id %s, project_id %s, roles %s" % (
|
||||
auth_ref.user_id,
|
||||
auth_ref.project_id,
|
||||
roles,
|
||||
)
|
||||
roles = ','.join(auth_ref.role_names)
|
||||
return 'user_id %s, project_id %s, roles %s' % (auth_ref.user_id,
|
||||
auth_ref.project_id,
|
||||
roles)
|
||||
|
||||
|
||||
class UserAuthPlugin(base_identity.BaseIdentityPlugin):
|
||||
@@ -65,12 +63,12 @@ class UserAuthPlugin(base_identity.BaseIdentityPlugin):
|
||||
msg = []
|
||||
|
||||
if self.has_user_token:
|
||||
msg.append("user: %s" % _log_format(self.user))
|
||||
msg.append('user: %s' % _log_format(self.user))
|
||||
|
||||
if self.has_service_token:
|
||||
msg.append("service: %s" % _log_format(self.service))
|
||||
msg.append('service: %s' % _log_format(self.service))
|
||||
|
||||
return " ".join(msg)
|
||||
return ' '.join(msg)
|
||||
|
||||
def get_headers(self, session, **kwargs):
|
||||
headers = super(UserAuthPlugin, self).get_headers(session, **kwargs)
|
||||
@@ -79,6 +77,6 @@ class UserAuthPlugin(base_identity.BaseIdentityPlugin):
|
||||
token = self._session.get_token(auth=self._auth)
|
||||
|
||||
if token:
|
||||
headers["X-Service-Token"] = token
|
||||
headers['X-Service-Token'] = token
|
||||
|
||||
return headers
|
||||
@@ -32,8 +32,9 @@ from keystonemiddleware import auth_token
|
||||
|
||||
def echo_app(environ, start_response):
|
||||
"""A WSGI application that echoes the CGI environment back to the user."""
|
||||
start_response("200 OK", [("Content-Type", "application/json")])
|
||||
environment = dict((k, v) for k, v in environ.items() if k.startswith("HTTP_X_"))
|
||||
start_response('200 OK', [('Content-Type', 'application/json')])
|
||||
environment = dict((k, v) for k, v in environ.items()
|
||||
if k.startswith('HTTP_X_'))
|
||||
yield jsonutils.dumps(environment)
|
||||
|
||||
|
||||
@@ -42,8 +43,8 @@ class EchoService(object):
|
||||
|
||||
def __init__(self):
|
||||
# hardcode any non-default configuration here
|
||||
conf = {"auth_protocol": "http", "admin_token": "ADMIN"} # nosec: B105
|
||||
conf = {'auth_protocol': 'http', 'admin_token': 'ADMIN'} # nosec: B105
|
||||
app = auth_token.AuthProtocol(echo_app, conf)
|
||||
server = simple_server.make_server("", 8000, app)
|
||||
print("Serving on port 8000 (Ctrl+C to end)...")
|
||||
server = simple_server.make_server('', 8000, app)
|
||||
print('Serving on port 8000 (Ctrl+C to end)...')
|
||||
server.serve_forever()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -32,32 +32,26 @@ class AuthTokenFixture(fixtures.Fixture):
|
||||
# Ensure that the initialized token data is cleaned up
|
||||
self._token_data = {}
|
||||
self.addCleanup(self._token_data.clear)
|
||||
_LOG.info("Using Testing AuthTokenFixture...")
|
||||
self.useFixture(
|
||||
fixtures.MockPatchObject(
|
||||
auth_token.AuthProtocol, "fetch_token", self.fetch_token
|
||||
)
|
||||
)
|
||||
_LOG.info('Using Testing AuthTokenFixture...')
|
||||
self.useFixture(fixtures.MockPatchObject(
|
||||
auth_token.AuthProtocol,
|
||||
'fetch_token',
|
||||
self.fetch_token))
|
||||
self.useFixture(fixtures.MockPatchObject(
|
||||
auth_token.AuthProtocol,
|
||||
'www_authenticate_uri',
|
||||
'http://localhost/identity'))
|
||||
|
||||
@property
|
||||
def tokens(self):
|
||||
return self._token_data.keys()
|
||||
|
||||
def add_token_data(
|
||||
self,
|
||||
token_id=None,
|
||||
expires=None,
|
||||
user_id=None,
|
||||
user_name=None,
|
||||
user_domain_id=None,
|
||||
user_domain_name=None,
|
||||
project_id=None,
|
||||
project_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
role_list=None,
|
||||
is_v2=False,
|
||||
):
|
||||
def add_token_data(self, token_id=None, expires=None,
|
||||
user_id=None, user_name=None,
|
||||
user_domain_id=None, user_domain_name=None,
|
||||
project_id=None, project_name=None,
|
||||
project_domain_id=None, project_domain_name=None,
|
||||
role_list=None):
|
||||
"""Add token data to the auth_token fixture."""
|
||||
if not token_id:
|
||||
token_id = uuid.uuid4().hex
|
||||
@@ -65,27 +59,13 @@ class AuthTokenFixture(fixtures.Fixture):
|
||||
if not role_list:
|
||||
role_list = []
|
||||
|
||||
if is_v2:
|
||||
token = client_fixtures.V2Token(
|
||||
token_id=token_id,
|
||||
expires=expires,
|
||||
tenant_id=project_id,
|
||||
tenant_name=project_name,
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
)
|
||||
else:
|
||||
token = client_fixtures.V3Token(
|
||||
expires=expires,
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
user_domain_id=user_domain_id,
|
||||
project_id=project_id,
|
||||
project_name=project_name,
|
||||
project_domain_id=project_domain_id,
|
||||
user_domain_name=user_domain_name,
|
||||
project_domain_name=project_domain_name,
|
||||
)
|
||||
token = client_fixtures.V3Token(
|
||||
expires=expires, user_id=user_id, user_name=user_name,
|
||||
user_domain_id=user_domain_id, project_id=project_id,
|
||||
project_name=project_name,
|
||||
project_domain_id=project_domain_id,
|
||||
user_domain_name=user_domain_name,
|
||||
project_domain_name=project_domain_name)
|
||||
for role in role_list:
|
||||
token.add_role(name=role)
|
||||
self.add_token(token, token_id=token_id)
|
||||
|
||||
@@ -21,7 +21,7 @@ See https://docs.openstack.org/oslo.i18n/latest/user/usage.html .
|
||||
import oslo_i18n as i18n
|
||||
|
||||
|
||||
_translators = i18n.TranslatorFactory(domain="keystonemiddleware")
|
||||
_translators = i18n.TranslatorFactory(domain='keystonemiddleware')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
@@ -29,8 +29,8 @@ class OAuth2mTlsProtocol(AuthProtocol):
|
||||
"""Middleware that handles OAuth2.0 mutual-TLS client authentication."""
|
||||
|
||||
def __init__(self, app, conf):
|
||||
log = logging.getLogger(conf.get("log_name", __name__))
|
||||
log.info("Starting Keystone oauth2_mls_token middleware")
|
||||
log = logging.getLogger(conf.get('log_name', __name__))
|
||||
log.info('Starting Keystone oauth2_mls_token middleware')
|
||||
super(OAuth2mTlsProtocol, self).__init__(app, conf)
|
||||
|
||||
def _confirm_certificate_thumbprint(self, token_thumb, peer_cert):
|
||||
@@ -40,12 +40,12 @@ class OAuth2mTlsProtocol(AuthProtocol):
|
||||
"""
|
||||
try:
|
||||
cert_pem = ssl.DER_cert_to_PEM_cert(peer_cert)
|
||||
thumb_sha256 = hashlib.sha256(cert_pem.encode("ascii")).digest()
|
||||
cert_thumb = base64.urlsafe_b64encode(thumb_sha256).decode("ascii")
|
||||
thumb_sha256 = hashlib.sha256(cert_pem.encode('ascii')).digest()
|
||||
cert_thumb = base64.urlsafe_b64encode(thumb_sha256).decode('ascii')
|
||||
if cert_thumb == token_thumb:
|
||||
is_valid = True
|
||||
else:
|
||||
self.log.info("The two thumbprints do not match.")
|
||||
self.log.info('The two thumbprints do not match.')
|
||||
is_valid = False
|
||||
except Exception as error:
|
||||
self.log.exception(error)
|
||||
@@ -61,56 +61,57 @@ class OAuth2mTlsProtocol(AuthProtocol):
|
||||
try:
|
||||
wsgi_input = request.environ.get("wsgi.input")
|
||||
if not wsgi_input:
|
||||
self.log.warning("Unable to obtain the client certificate.")
|
||||
self.log.warning('Unable to obtain the client certificate.')
|
||||
return False
|
||||
sock = wsgi_input.get_socket()
|
||||
if not sock:
|
||||
self.log.warning("Unable to obtain the client certificate.")
|
||||
self.log.warning('Unable to obtain the client certificate.')
|
||||
return False
|
||||
peer_cert = sock.getpeercert(binary_form=True)
|
||||
if not peer_cert:
|
||||
self.log.warning("Unable to obtain the client certificate.")
|
||||
self.log.warning('Unable to obtain the client certificate.')
|
||||
return False
|
||||
except Exception as error:
|
||||
self.log.warning("Unable to obtain the client certificate. %s" % str(error))
|
||||
self.log.warning('Unable to obtain the client certificate. %s' %
|
||||
str(error))
|
||||
return False
|
||||
|
||||
access_token = None
|
||||
if request.authorization and request.authorization.authtype == "Bearer":
|
||||
if (request.authorization and
|
||||
request.authorization.authtype == 'Bearer'):
|
||||
access_token = request.authorization.params
|
||||
|
||||
if not access_token:
|
||||
self.log.info("Unable to obtain the token.")
|
||||
self.log.info('Unable to obtain the token.')
|
||||
return False
|
||||
|
||||
try:
|
||||
token_data, user_auth_ref = self._do_fetch_token(
|
||||
access_token, allow_expired=False
|
||||
)
|
||||
access_token, allow_expired=False)
|
||||
self._validate_token(user_auth_ref, allow_expired=False)
|
||||
token = token_data.get("token")
|
||||
oauth2_cred = token.get("oauth2_credential")
|
||||
token = token_data.get('token')
|
||||
oauth2_cred = token.get('oauth2_credential')
|
||||
if not oauth2_cred:
|
||||
self.log.info(
|
||||
"Invalid OAuth2.0 certificate-bound access token: "
|
||||
"The token is not an OAuth2.0 credential access token."
|
||||
)
|
||||
'Invalid OAuth2.0 certificate-bound access token: '
|
||||
'The token is not an OAuth2.0 credential access token.')
|
||||
return False
|
||||
|
||||
token_thumb = oauth2_cred.get("x5t#S256")
|
||||
if self._confirm_certificate_thumbprint(token_thumb, peer_cert):
|
||||
self._confirm_token_bind(user_auth_ref, request)
|
||||
request.token_info = token_data
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref, None)
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(
|
||||
user_auth_ref, None)
|
||||
return True
|
||||
else:
|
||||
self.log.info(
|
||||
"Invalid OAuth2.0 certificate-bound access token: "
|
||||
"the access token dose not match the client certificate."
|
||||
)
|
||||
'Invalid OAuth2.0 certificate-bound access token: '
|
||||
'the access token dose not match the client certificate.')
|
||||
return False
|
||||
except exceptions.KeystoneMiddlewareException as err:
|
||||
self.log.info("Invalid OAuth2.0 certificate-bound access token: %s", err)
|
||||
self.log.info(
|
||||
'Invalid OAuth2.0 certificate-bound access token: %s', err)
|
||||
return False
|
||||
|
||||
def process_request(self, request):
|
||||
@@ -121,32 +122,27 @@ class OAuth2mTlsProtocol(AuthProtocol):
|
||||
"""
|
||||
request.remove_auth_headers()
|
||||
self._token_cache.initialize(request.environ)
|
||||
if (
|
||||
not self._is_valid_access_token(request)
|
||||
or "keystone.token_info" not in request.environ
|
||||
or "token" not in request.environ["keystone.token_info"]
|
||||
):
|
||||
self.log.info("Rejecting request")
|
||||
message = _("The request you have made requires authentication.")
|
||||
body = {
|
||||
"error": {
|
||||
"code": 401,
|
||||
"title": "Unauthorized",
|
||||
"message": message,
|
||||
}
|
||||
}
|
||||
if (not self._is_valid_access_token(request)
|
||||
or "keystone.token_info" not in request.environ
|
||||
or "token" not in request.environ["keystone.token_info"]):
|
||||
self.log.info('Rejecting request')
|
||||
message = _('The request you have made requires authentication.')
|
||||
body = {'error': {
|
||||
'code': 401,
|
||||
'title': 'Unauthorized',
|
||||
'message': message,
|
||||
}}
|
||||
raise webob.exc.HTTPUnauthorized(
|
||||
body=jsonutils.dumps(body),
|
||||
headers=self._reject_auth_headers,
|
||||
charset="UTF-8",
|
||||
content_type="application/json",
|
||||
)
|
||||
charset='UTF-8',
|
||||
content_type='application/json')
|
||||
|
||||
request.set_user_headers(request.token_auth.user)
|
||||
request.set_service_catalog_headers(request.token_auth.user)
|
||||
request.token_auth._auth = self._auth
|
||||
request.token_auth._session = self._session
|
||||
self.log.debug("Accepting request and inited all env fields.")
|
||||
self.log.debug('Accepting request and inited all env fields.')
|
||||
|
||||
|
||||
def filter_factory(global_conf, **local_conf):
|
||||
|
||||
@@ -30,8 +30,8 @@ class OAuth2Protocol(AuthProtocol):
|
||||
"""Middleware that handles OAuth2.0 client credentials authentication."""
|
||||
|
||||
def __init__(self, app, conf):
|
||||
log = logging.getLogger(conf.get("log_name", __name__))
|
||||
log.info("Starting Keystone oauth2_token middleware")
|
||||
log = logging.getLogger(conf.get('log_name', __name__))
|
||||
log.info('Starting Keystone oauth2_token middleware')
|
||||
super(OAuth2Protocol, self).__init__(app, conf)
|
||||
|
||||
def _is_valid_access_token(self, request):
|
||||
@@ -41,23 +41,25 @@ class OAuth2Protocol(AuthProtocol):
|
||||
:type request: _request.AuthTokenRequest
|
||||
"""
|
||||
access_token = None
|
||||
if request.authorization and request.authorization.authtype == "Bearer":
|
||||
if (request.authorization and
|
||||
request.authorization.authtype == 'Bearer'):
|
||||
access_token = request.authorization.params
|
||||
|
||||
if access_token:
|
||||
try:
|
||||
token_data, user_auth_ref = self._do_fetch_token(
|
||||
access_token, allow_expired=False
|
||||
)
|
||||
self._validate_token(user_auth_ref, allow_expired=False)
|
||||
token = token_data["token"]
|
||||
access_token, allow_expired=False)
|
||||
self._validate_token(user_auth_ref,
|
||||
allow_expired=False)
|
||||
token = token_data['token']
|
||||
self.validate_allowed_request(request, token)
|
||||
self._confirm_token_bind(user_auth_ref, request)
|
||||
request.token_info = token_data
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref, None)
|
||||
request.token_auth = _user_plugin.UserAuthPlugin(
|
||||
user_auth_ref, None)
|
||||
return True
|
||||
except exceptions.KeystoneMiddlewareException as err:
|
||||
_LOG.info("Invalid OAuth2.0 access token: %s" % str(err))
|
||||
_LOG.info('Invalid OAuth2.0 access token: %s' % str(err))
|
||||
return False
|
||||
|
||||
def process_request(self, request):
|
||||
@@ -68,26 +70,21 @@ class OAuth2Protocol(AuthProtocol):
|
||||
"""
|
||||
request.remove_auth_headers()
|
||||
self._token_cache.initialize(request.environ)
|
||||
if (
|
||||
not self._is_valid_access_token(request)
|
||||
or "keystone.token_info" not in request.environ
|
||||
or "token" not in request.environ["keystone.token_info"]
|
||||
):
|
||||
_LOG.info("Rejecting request")
|
||||
message = _("The request you have made requires authentication.")
|
||||
body = {
|
||||
"error": {
|
||||
"code": 401,
|
||||
"title": "Unauthorized",
|
||||
"message": message,
|
||||
}
|
||||
}
|
||||
if (not self._is_valid_access_token(request)
|
||||
or "keystone.token_info" not in request.environ
|
||||
or "token" not in request.environ["keystone.token_info"]):
|
||||
_LOG.info('Rejecting request')
|
||||
message = _('The request you have made requires authentication.')
|
||||
body = {'error': {
|
||||
'code': 401,
|
||||
'title': 'Unauthorized',
|
||||
'message': message,
|
||||
}}
|
||||
raise webob.exc.HTTPUnauthorized(
|
||||
body=jsonutils.dumps(body),
|
||||
headers=self._reject_auth_headers,
|
||||
charset="UTF-8",
|
||||
content_type="application/json",
|
||||
)
|
||||
charset='UTF-8',
|
||||
content_type='application/json')
|
||||
|
||||
request.set_user_headers(request.token_auth.user)
|
||||
request.set_service_catalog_headers(request.token_auth.user)
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__all__ = ("list_auth_token_opts",)
|
||||
__all__ = (
|
||||
'list_auth_token_opts',
|
||||
)
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
|
||||
|
||||
@@ -38,14 +38,13 @@ compute = service/compute
|
||||
|
||||
|
||||
class BaseAuditMiddlewareTest(utils.MiddlewareTestCase):
|
||||
PROJECT_NAME = "keystonemiddleware"
|
||||
PROJECT_NAME = 'keystonemiddleware'
|
||||
|
||||
def setUp(self):
|
||||
super(BaseAuditMiddlewareTest, self).setUp()
|
||||
|
||||
self.audit_map_file_fixture = self.useFixture(
|
||||
createfile.CreateFileWithContent("audit", audit_map_content)
|
||||
)
|
||||
createfile.CreateFileWithContent('audit', audit_map_content))
|
||||
|
||||
self.cfg = self.useFixture(cfg_fixture.Config())
|
||||
self.msg = self.useFixture(msg_fixture.ConfFixture(self.cfg.conf))
|
||||
@@ -53,12 +52,13 @@ class BaseAuditMiddlewareTest(utils.MiddlewareTestCase):
|
||||
self.cfg.conf([], project=self.PROJECT_NAME)
|
||||
|
||||
def create_middleware(self, cb, **kwargs):
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
return cb(req)
|
||||
|
||||
kwargs.setdefault("audit_map_file", self.audit_map)
|
||||
kwargs.setdefault("service_name", "pycadf")
|
||||
kwargs.setdefault('audit_map_file', self.audit_map)
|
||||
kwargs.setdefault('service_name', 'pycadf')
|
||||
|
||||
return audit.AuditMiddleware(_do_cb, **kwargs)
|
||||
|
||||
@@ -68,8 +68,8 @@ class BaseAuditMiddlewareTest(utils.MiddlewareTestCase):
|
||||
|
||||
@staticmethod
|
||||
def get_environ_header(req_type=None):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"adminURL":
|
||||
"http://admin_host:8774",
|
||||
"region": "RegionOne",
|
||||
@@ -79,13 +79,12 @@ class BaseAuditMiddlewareTest(utils.MiddlewareTestCase):
|
||||
"http://internal_host:8774",
|
||||
"id": "resource_id"}],
|
||||
"type": "compute",
|
||||
"name": "nova"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
}
|
||||
"name": "nova"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed'}
|
||||
if req_type:
|
||||
env_headers["REQUEST_METHOD"] = req_type
|
||||
env_headers['REQUEST_METHOD'] = req_type
|
||||
return env_headers
|
||||
@@ -20,72 +20,75 @@ from keystonemiddleware.tests.unit.audit import base
|
||||
|
||||
|
||||
class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
|
||||
def get_payload(self, method, url, audit_map=None, body=None, environ=None):
|
||||
|
||||
def get_payload(self, method, url,
|
||||
audit_map=None, body=None, environ=None):
|
||||
audit_map = audit_map or self.audit_map
|
||||
environ = environ or self.get_environ_header()
|
||||
|
||||
req = webob.Request.blank(
|
||||
url, body=body, method=method, environ=environ, remote_addr="192.168.0.1"
|
||||
)
|
||||
req = webob.Request.blank(url,
|
||||
body=body,
|
||||
method=method,
|
||||
environ=environ,
|
||||
remote_addr='192.168.0.1')
|
||||
|
||||
middleware = audit.OpenStackAuditApi(audit_map)
|
||||
return middleware._create_event(req).as_dict()
|
||||
|
||||
def test_get_list(self):
|
||||
path = "/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
url = "http://admin_host:8774" + path
|
||||
payload = self.get_payload("GET", url)
|
||||
path = '/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
url = 'http://admin_host:8774' + path
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(payload["action"], "read/list")
|
||||
self.assertEqual(
|
||||
payload["typeURI"], "http://schemas.dmtf.org/cloud/audit/1.0/event"
|
||||
)
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload["eventType"], "activity")
|
||||
self.assertEqual(payload["target"]["name"], "nova")
|
||||
self.assertEqual(payload["target"]["id"], "resource_id")
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(len(payload["target"]["addresses"]), 3)
|
||||
self.assertEqual(payload["target"]["addresses"][0]["name"], "admin")
|
||||
self.assertEqual(
|
||||
payload["target"]["addresses"][0]["url"], "http://admin_host:8774"
|
||||
)
|
||||
self.assertEqual(payload["initiator"]["id"], "user_id")
|
||||
self.assertEqual(payload["initiator"]["name"], "user_name")
|
||||
self.assertEqual(payload["initiator"]["project_id"], "tenant_id")
|
||||
self.assertEqual(payload["initiator"]["host"]["address"], "192.168.0.1")
|
||||
self.assertEqual(
|
||||
payload["initiator"]["typeURI"], "service/security/account/user"
|
||||
)
|
||||
self.assertNotEqual(payload["initiator"]["credential"]["token"], "token")
|
||||
self.assertEqual(
|
||||
payload["initiator"]["credential"]["identity_status"], "Confirmed"
|
||||
)
|
||||
self.assertNotIn("reason", payload)
|
||||
self.assertNotIn("reporterchain", payload)
|
||||
self.assertEqual(payload["observer"]["id"], "target")
|
||||
self.assertEqual(path, payload["requestPath"])
|
||||
self.assertEqual(payload['action'], 'read/list')
|
||||
self.assertEqual(payload['typeURI'],
|
||||
'http://schemas.dmtf.org/cloud/audit/1.0/event')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
self.assertEqual(payload['eventType'], 'activity')
|
||||
self.assertEqual(payload['target']['name'], 'nova')
|
||||
self.assertEqual(payload['target']['id'], 'resource_id')
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
self.assertEqual(len(payload['target']['addresses']), 3)
|
||||
self.assertEqual(payload['target']['addresses'][0]['name'], 'admin')
|
||||
self.assertEqual(payload['target']['addresses'][0]['url'],
|
||||
'http://admin_host:8774')
|
||||
self.assertEqual(payload['initiator']['id'], 'user_id')
|
||||
self.assertEqual(payload['initiator']['name'], 'user_name')
|
||||
self.assertEqual(payload['initiator']['project_id'],
|
||||
'tenant_id')
|
||||
self.assertEqual(payload['initiator']['host']['address'],
|
||||
'192.168.0.1')
|
||||
self.assertEqual(payload['initiator']['typeURI'],
|
||||
'service/security/account/user')
|
||||
self.assertNotEqual(payload['initiator']['credential']['token'],
|
||||
'token')
|
||||
self.assertEqual(payload['initiator']['credential']['identity_status'],
|
||||
'Confirmed')
|
||||
self.assertNotIn('reason', payload)
|
||||
self.assertNotIn('reporterchain', payload)
|
||||
self.assertEqual(payload['observer']['id'], 'target')
|
||||
self.assertEqual(path, payload['requestPath'])
|
||||
|
||||
def test_get_read(self):
|
||||
url = "http://admin_host:8774/v2/%s/servers/%s" % (
|
||||
uuid.uuid4().hex,
|
||||
uuid.uuid4().hex,
|
||||
)
|
||||
payload = self.get_payload("GET", url)
|
||||
url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex,
|
||||
uuid.uuid4().hex)
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers/server")
|
||||
self.assertEqual(payload["action"], "read")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers/server')
|
||||
self.assertEqual(payload['action'], 'read')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_get_unknown_endpoint(self):
|
||||
url = "http://unknown:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url)
|
||||
url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(payload["action"], "read/list")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload["target"]["name"], "unknown")
|
||||
self.assertEqual(payload["target"]["id"], "unknown")
|
||||
self.assertEqual(payload["target"]["typeURI"], "unknown")
|
||||
self.assertEqual(payload['action'], 'read/list')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
self.assertEqual(payload['target']['name'], 'unknown')
|
||||
self.assertEqual(payload['target']['id'], 'unknown')
|
||||
self.assertEqual(payload['target']['typeURI'], 'unknown')
|
||||
|
||||
def test_get_unknown_endpoint_default_set(self):
|
||||
with open(self.audit_map, "w") as f:
|
||||
@@ -96,123 +99,127 @@ class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
|
||||
f.write("[service_endpoints]\n")
|
||||
f.write("compute = service/compute")
|
||||
|
||||
url = "http://unknown:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url)
|
||||
url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(payload["action"], "read/list")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload["target"]["name"], "nova")
|
||||
self.assertEqual(payload["target"]["id"], "resource_id")
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(payload['action'], 'read/list')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
self.assertEqual(payload['target']['name'], 'nova')
|
||||
self.assertEqual(payload['target']['id'], 'resource_id')
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
|
||||
def test_put(self):
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("PUT", url)
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('PUT', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(payload["action"], "update")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
self.assertEqual(payload['action'], 'update')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_delete(self):
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("DELETE", url)
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('DELETE', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(payload["action"], "delete")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
self.assertEqual(payload['action'], 'delete')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_head(self):
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("HEAD", url)
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('HEAD', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(payload["action"], "read")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
self.assertEqual(payload['action'], 'read')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_post_update(self):
|
||||
url = "http://admin_host:8774/v2/%s/servers/%s" % (
|
||||
uuid.uuid4().hex,
|
||||
uuid.uuid4().hex,
|
||||
)
|
||||
payload = self.get_payload("POST", url)
|
||||
url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex,
|
||||
uuid.uuid4().hex)
|
||||
payload = self.get_payload('POST', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers/server")
|
||||
self.assertEqual(payload["action"], "update")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers/server')
|
||||
self.assertEqual(payload['action'], 'update')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_post_create(self):
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("POST", url)
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('POST', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers")
|
||||
self.assertEqual(payload["action"], "create")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers')
|
||||
self.assertEqual(payload['action'], 'create')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_post_action(self):
|
||||
url = "http://admin_host:8774/v2/%s/servers/action" % uuid.uuid4().hex
|
||||
body = (
|
||||
b'{"createImage" : {"name" : "new-image","metadata": '
|
||||
b'{"ImageType": "Gold","ImageVersion": "2.0"}}}'
|
||||
)
|
||||
payload = self.get_payload("POST", url, body=body)
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers/action")
|
||||
self.assertEqual(payload["action"], "update/createImage")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex
|
||||
body = b'{"createImage" : {"name" : "new-image","metadata": ' \
|
||||
b'{"ImageType": "Gold","ImageVersion": "2.0"}}}'
|
||||
payload = self.get_payload('POST', url, body=body)
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers/action')
|
||||
self.assertEqual(payload['action'], 'update/createImage')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_post_empty_body_action(self):
|
||||
url = "http://admin_host:8774/v2/%s/servers/action" % uuid.uuid4().hex
|
||||
payload = self.get_payload("POST", url)
|
||||
url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex
|
||||
payload = self.get_payload('POST', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/servers/action")
|
||||
self.assertEqual(payload["action"], "create")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/servers/action')
|
||||
self.assertEqual(payload['action'], 'create')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_custom_action(self):
|
||||
url = "http://admin_host:8774/v2/%s/os-hosts/%s/reboot" % (
|
||||
uuid.uuid4().hex,
|
||||
uuid.uuid4().hex,
|
||||
)
|
||||
payload = self.get_payload("GET", url)
|
||||
url = 'http://admin_host:8774/v2/%s/os-hosts/%s/reboot' % (
|
||||
uuid.uuid4().hex, uuid.uuid4().hex)
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(
|
||||
payload["target"]["typeURI"], "service/compute/os-hosts/host/reboot"
|
||||
)
|
||||
self.assertEqual(payload["action"], "start/reboot")
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/os-hosts/host/reboot')
|
||||
self.assertEqual(payload['action'], 'start/reboot')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
|
||||
def test_custom_action_complex(self):
|
||||
url = "http://admin_host:8774/v2/%s/os-migrations" % uuid.uuid4().hex
|
||||
payload = self.get_payload("GET", url)
|
||||
url = 'http://admin_host:8774/v2/%s/os-migrations' % uuid.uuid4().hex
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/os-migrations")
|
||||
self.assertEqual(payload["action"], "read")
|
||||
payload = self.get_payload("POST", url)
|
||||
self.assertEqual(payload["target"]["typeURI"], "service/compute/os-migrations")
|
||||
self.assertEqual(payload["action"], "create")
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/os-migrations')
|
||||
self.assertEqual(payload['action'], 'read')
|
||||
payload = self.get_payload('POST', url)
|
||||
self.assertEqual(payload['target']['typeURI'],
|
||||
'service/compute/os-migrations')
|
||||
self.assertEqual(payload['action'], 'create')
|
||||
|
||||
def test_response_mod_msg(self):
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
req = webob.Request.blank(
|
||||
url, environ=self.get_environ_header("GET"), remote_addr="192.168.0.1"
|
||||
)
|
||||
req.environ["audit.context"] = {}
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
req = webob.Request.blank(url,
|
||||
environ=self.get_environ_header('GET'),
|
||||
remote_addr='192.168.0.1')
|
||||
req.environ['audit.context'] = {}
|
||||
middleware = self.create_simple_middleware()
|
||||
middleware._process_request(req)
|
||||
payload = req.environ["cadf_event"].as_dict()
|
||||
payload = req.environ['cadf_event'].as_dict()
|
||||
middleware._process_response(req, webob.Response())
|
||||
payload2 = req.environ["cadf_event"].as_dict()
|
||||
self.assertEqual(payload["id"], payload2["id"])
|
||||
self.assertEqual(payload["tags"], payload2["tags"])
|
||||
self.assertEqual(payload2["outcome"], "success")
|
||||
self.assertEqual(payload2["reason"]["reasonType"], "HTTP")
|
||||
self.assertEqual(payload2["reason"]["reasonCode"], "200")
|
||||
self.assertEqual(len(payload2["reporterchain"]), 1)
|
||||
self.assertEqual(payload2["reporterchain"][0]["role"], "modifier")
|
||||
self.assertEqual(payload2["reporterchain"][0]["reporter"]["id"], "target")
|
||||
payload2 = req.environ['cadf_event'].as_dict()
|
||||
self.assertEqual(payload['id'], payload2['id'])
|
||||
self.assertEqual(payload['tags'], payload2['tags'])
|
||||
self.assertEqual(payload2['outcome'], 'success')
|
||||
self.assertEqual(payload2['reason']['reasonType'], 'HTTP')
|
||||
self.assertEqual(payload2['reason']['reasonCode'], '200')
|
||||
self.assertEqual(len(payload2['reporterchain']), 1)
|
||||
self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier')
|
||||
self.assertEqual(payload2['reporterchain'][0]['reporter']['id'],
|
||||
'target')
|
||||
|
||||
def test_missing_catalog_endpoint_id(self):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"adminURL":
|
||||
"http://admin_host:8774",
|
||||
"region": "RegionOne",
|
||||
@@ -221,102 +228,97 @@ class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
|
||||
"internalURL":
|
||||
"http://internal_host:8774"}],
|
||||
"type": "compute",
|
||||
"name": "nova"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "nova"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual(payload["target"]["id"], "nova")
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual(payload['target']['id'], 'nova')
|
||||
|
||||
def test_endpoint_missing_internal_url(self):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"adminURL":
|
||||
"http://admin_host:8774",
|
||||
"region": "RegionOne",
|
||||
"publicURL":
|
||||
"http://public_host:8774"}],
|
||||
"type": "compute",
|
||||
"name": "nova"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "nova"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual((payload["target"]["addresses"][1]["url"]), "unknown")
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual((payload['target']['addresses'][1]['url']), "unknown")
|
||||
|
||||
def test_endpoint_missing_public_url(self):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"adminURL":
|
||||
"http://admin_host:8774",
|
||||
"region": "RegionOne",
|
||||
"internalURL":
|
||||
"http://internal_host:8774"}],
|
||||
"type": "compute",
|
||||
"name": "nova"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "nova"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual((payload["target"]["addresses"][2]["url"]), "unknown")
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual((payload['target']['addresses'][2]['url']), "unknown")
|
||||
|
||||
def test_endpoint_missing_admin_url(self):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"region": "RegionOne",
|
||||
"publicURL":
|
||||
"http://public_host:8774",
|
||||
"internalURL":
|
||||
"http://internal_host:8774"}],
|
||||
"type": "compute",
|
||||
"name": "nova"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "nova"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://public_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual((payload["target"]["addresses"][0]["url"]), "unknown")
|
||||
url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual((payload['target']['addresses'][0]['url']), "unknown")
|
||||
|
||||
def test_service_with_no_endpoints(self):
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [],
|
||||
"type": "foo",
|
||||
"name": "bar"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "bar"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://public_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual(payload["target"]["name"], "unknown")
|
||||
url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual(payload['target']['name'], "unknown")
|
||||
|
||||
def test_endpoint_no_service_port(self):
|
||||
with open(self.audit_map, "w") as f:
|
||||
@@ -327,8 +329,8 @@ class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
|
||||
f.write("[service_endpoints]\n")
|
||||
f.write("load-balancer = service/load-balancer")
|
||||
|
||||
env_headers = {
|
||||
"HTTP_X_SERVICE_CATALOG": """[{"endpoints_links": [],
|
||||
env_headers = {'HTTP_X_SERVICE_CATALOG':
|
||||
'''[{"endpoints_links": [],
|
||||
"endpoints": [{"adminURL":
|
||||
"http://admin_host/compute",
|
||||
"region": "RegionOne",
|
||||
@@ -343,73 +345,75 @@ class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
|
||||
"publicURL":
|
||||
"http://public_host/load-balancer"}],
|
||||
"type": "load-balancer",
|
||||
"name": "octavia"}]""",
|
||||
"HTTP_X_USER_ID": "user_id",
|
||||
"HTTP_X_USER_NAME": "user_name",
|
||||
"HTTP_X_AUTH_TOKEN": "token",
|
||||
"HTTP_X_PROJECT_ID": "tenant_id",
|
||||
"HTTP_X_IDENTITY_STATUS": "Confirmed",
|
||||
"REQUEST_METHOD": "GET",
|
||||
}
|
||||
"name": "octavia"}]''',
|
||||
'HTTP_X_USER_ID': 'user_id',
|
||||
'HTTP_X_USER_NAME': 'user_name',
|
||||
'HTTP_X_AUTH_TOKEN': 'token',
|
||||
'HTTP_X_PROJECT_ID': 'tenant_id',
|
||||
'HTTP_X_IDENTITY_STATUS': 'Confirmed',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
url = "http://admin_host/load-balancer/v2/loadbalancers/" + str(uuid.uuid4())
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
self.assertEqual(payload["target"]["id"], "octavia")
|
||||
url = ('http://admin_host/load-balancer/v2/loadbalancers/' +
|
||||
str(uuid.uuid4()))
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
self.assertEqual(payload['target']['id'], 'octavia')
|
||||
|
||||
def test_no_auth_token(self):
|
||||
# Test cases where API requests such as Swift list public containers
|
||||
# which does not require an auth token. In these cases, CADF event
|
||||
# should have the defaults (i.e taxonomy.UNKNOWN) instead of raising
|
||||
# an exception.
|
||||
env_headers = {"HTTP_X_IDENTITY_STATUS": "Invalid", "REQUEST_METHOD": "GET"}
|
||||
env_headers = {'HTTP_X_IDENTITY_STATUS': 'Invalid',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
|
||||
path = "/v1/" + str(uuid.uuid4())
|
||||
url = "https://23.253.72.207" + path
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
path = '/v1/' + str(uuid.uuid4())
|
||||
url = 'https://23.253.72.207' + path
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
|
||||
self.assertEqual(payload["action"], "read")
|
||||
self.assertEqual(
|
||||
payload["typeURI"], "http://schemas.dmtf.org/cloud/audit/1.0/event"
|
||||
)
|
||||
self.assertEqual(payload["outcome"], "pending")
|
||||
self.assertEqual(payload["eventType"], "activity")
|
||||
self.assertEqual(payload["target"]["name"], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload["target"]["id"], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload["target"]["typeURI"], taxonomy.UNKNOWN)
|
||||
self.assertNotIn("addresses", payload["target"])
|
||||
self.assertEqual(payload["initiator"]["id"], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload["initiator"]["name"], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload["initiator"]["project_id"], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload["initiator"]["host"]["address"], "192.168.0.1")
|
||||
self.assertEqual(
|
||||
payload["initiator"]["typeURI"], "service/security/account/user"
|
||||
)
|
||||
self.assertNotEqual(payload["initiator"]["credential"]["token"], None)
|
||||
self.assertEqual(
|
||||
payload["initiator"]["credential"]["identity_status"], "Invalid"
|
||||
)
|
||||
self.assertNotIn("reason", payload)
|
||||
self.assertNotIn("reporterchain", payload)
|
||||
self.assertEqual(payload["observer"]["id"], "target")
|
||||
self.assertEqual(path, payload["requestPath"])
|
||||
self.assertEqual(payload['action'], 'read')
|
||||
self.assertEqual(payload['typeURI'],
|
||||
'http://schemas.dmtf.org/cloud/audit/1.0/event')
|
||||
self.assertEqual(payload['outcome'], 'pending')
|
||||
self.assertEqual(payload['eventType'], 'activity')
|
||||
self.assertEqual(payload['target']['name'], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload['target']['id'], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload['target']['typeURI'], taxonomy.UNKNOWN)
|
||||
self.assertNotIn('addresses', payload['target'])
|
||||
self.assertEqual(payload['initiator']['id'], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload['initiator']['name'], taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload['initiator']['project_id'],
|
||||
taxonomy.UNKNOWN)
|
||||
self.assertEqual(payload['initiator']['host']['address'],
|
||||
'192.168.0.1')
|
||||
self.assertEqual(payload['initiator']['typeURI'],
|
||||
'service/security/account/user')
|
||||
self.assertNotEqual(payload['initiator']['credential']['token'],
|
||||
None)
|
||||
self.assertEqual(payload['initiator']['credential']['identity_status'],
|
||||
'Invalid')
|
||||
self.assertNotIn('reason', payload)
|
||||
self.assertNotIn('reporterchain', payload)
|
||||
self.assertEqual(payload['observer']['id'], 'target')
|
||||
self.assertEqual(path, payload['requestPath'])
|
||||
|
||||
def test_request_and_global_request_id(self):
|
||||
path = "/v1/" + str(uuid.uuid4())
|
||||
url = "https://23.253.72.207" + path
|
||||
path = '/v1/' + str(uuid.uuid4())
|
||||
url = 'https://23.253.72.207' + path
|
||||
|
||||
request_id = "req-%s" % uuid.uuid4()
|
||||
global_request_id = "req-%s" % uuid.uuid4()
|
||||
request_id = 'req-%s' % uuid.uuid4()
|
||||
global_request_id = 'req-%s' % uuid.uuid4()
|
||||
|
||||
env_headers = self.get_environ_header("GET")
|
||||
env_headers["openstack.request_id"] = request_id
|
||||
env_headers["openstack.global_request_id"] = global_request_id
|
||||
env_headers = self.get_environ_header('GET')
|
||||
env_headers['openstack.request_id'] = request_id
|
||||
env_headers['openstack.global_request_id'] = global_request_id
|
||||
|
||||
payload = self.get_payload("GET", url, environ=env_headers)
|
||||
payload = self.get_payload('GET', url, environ=env_headers)
|
||||
|
||||
self.assertEqual(payload["initiator"]["request_id"], request_id)
|
||||
self.assertEqual(payload["initiator"]["global_request_id"], global_request_id)
|
||||
self.assertEqual(payload['initiator']['request_id'], request_id)
|
||||
self.assertEqual(payload['initiator']['global_request_id'],
|
||||
global_request_id)
|
||||
|
||||
payload = self.get_payload("GET", url)
|
||||
payload = self.get_payload('GET', url)
|
||||
|
||||
self.assertNotIn("request_id", payload["initiator"])
|
||||
self.assertNotIn("global_request_id", payload["initiator"])
|
||||
self.assertNotIn('request_id', payload['initiator'])
|
||||
self.assertNotIn('global_request_id', payload['initiator'])
|
||||
@@ -21,105 +21,108 @@ from keystonemiddleware.tests.unit.audit import base
|
||||
|
||||
|
||||
class AuditMiddlewareTest(base.BaseAuditMiddlewareTest):
|
||||
|
||||
def setUp(self):
|
||||
self.notifier = mock.MagicMock()
|
||||
|
||||
p = "keystonemiddleware.audit._notifier.create_notifier"
|
||||
p = 'keystonemiddleware.audit._notifier.create_notifier'
|
||||
f = fixtures.MockPatch(p, return_value=self.notifier)
|
||||
self.notifier_fixture = self.useFixture(f)
|
||||
|
||||
super(AuditMiddlewareTest, self).setUp()
|
||||
|
||||
def test_api_request(self):
|
||||
self.create_simple_app().get(
|
||||
"/foo/bar", extra_environ=self.get_environ_header()
|
||||
)
|
||||
self.create_simple_app().get('/foo/bar',
|
||||
extra_environ=self.get_environ_header())
|
||||
|
||||
# Check first notification with only 'request'
|
||||
call_args = self.notifier.notify.call_args_list[0][0]
|
||||
self.assertEqual("audit.http.request", call_args[1])
|
||||
self.assertEqual("/foo/bar", call_args[2]["requestPath"])
|
||||
self.assertEqual("pending", call_args[2]["outcome"])
|
||||
self.assertNotIn("reason", call_args[2])
|
||||
self.assertNotIn("reporterchain", call_args[2])
|
||||
self.assertEqual('audit.http.request', call_args[1])
|
||||
self.assertEqual('/foo/bar', call_args[2]['requestPath'])
|
||||
self.assertEqual('pending', call_args[2]['outcome'])
|
||||
self.assertNotIn('reason', call_args[2])
|
||||
self.assertNotIn('reporterchain', call_args[2])
|
||||
|
||||
# Check second notification with request + response
|
||||
call_args = self.notifier.notify.call_args_list[1][0]
|
||||
self.assertEqual("audit.http.response", call_args[1])
|
||||
self.assertEqual("/foo/bar", call_args[2]["requestPath"])
|
||||
self.assertEqual("success", call_args[2]["outcome"])
|
||||
self.assertIn("reason", call_args[2])
|
||||
self.assertIn("reporterchain", call_args[2])
|
||||
self.assertEqual('audit.http.response', call_args[1])
|
||||
self.assertEqual('/foo/bar', call_args[2]['requestPath'])
|
||||
self.assertEqual('success', call_args[2]['outcome'])
|
||||
self.assertIn('reason', call_args[2])
|
||||
self.assertIn('reporterchain', call_args[2])
|
||||
|
||||
def test_api_request_failure(self):
|
||||
|
||||
class CustomException(Exception):
|
||||
pass
|
||||
|
||||
def cb(req):
|
||||
raise CustomException("It happens!")
|
||||
raise CustomException('It happens!')
|
||||
|
||||
try:
|
||||
self.create_app(cb).get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
self.create_app(cb).get('/foo/bar',
|
||||
extra_environ=self.get_environ_header())
|
||||
|
||||
self.fail("Application exception has not been re-raised")
|
||||
self.fail('Application exception has not been re-raised')
|
||||
except CustomException:
|
||||
pass
|
||||
|
||||
# Check first notification with only 'request'
|
||||
call_args = self.notifier.notify.call_args_list[0][0]
|
||||
self.assertEqual("audit.http.request", call_args[1])
|
||||
self.assertEqual("/foo/bar", call_args[2]["requestPath"])
|
||||
self.assertEqual("pending", call_args[2]["outcome"])
|
||||
self.assertNotIn("reporterchain", call_args[2])
|
||||
self.assertEqual('audit.http.request', call_args[1])
|
||||
self.assertEqual('/foo/bar', call_args[2]['requestPath'])
|
||||
self.assertEqual('pending', call_args[2]['outcome'])
|
||||
self.assertNotIn('reporterchain', call_args[2])
|
||||
|
||||
# Check second notification with request + response
|
||||
call_args = self.notifier.notify.call_args_list[1][0]
|
||||
self.assertEqual("audit.http.response", call_args[1])
|
||||
self.assertEqual("/foo/bar", call_args[2]["requestPath"])
|
||||
self.assertEqual("unknown", call_args[2]["outcome"])
|
||||
self.assertIn("reporterchain", call_args[2])
|
||||
self.assertEqual('audit.http.response', call_args[1])
|
||||
self.assertEqual('/foo/bar', call_args[2]['requestPath'])
|
||||
self.assertEqual('unknown', call_args[2]['outcome'])
|
||||
self.assertIn('reporterchain', call_args[2])
|
||||
|
||||
def test_process_request_fail(self):
|
||||
req = webob.Request.blank("/foo/bar", environ=self.get_environ_header("GET"))
|
||||
req.environ["audit.context"] = {}
|
||||
req = webob.Request.blank('/foo/bar',
|
||||
environ=self.get_environ_header('GET'))
|
||||
req.environ['audit.context'] = {}
|
||||
|
||||
self.create_simple_middleware()._process_request(req)
|
||||
self.assertTrue(self.notifier.notify.called)
|
||||
|
||||
def test_process_response_fail(self):
|
||||
req = webob.Request.blank("/foo/bar", environ=self.get_environ_header("GET"))
|
||||
req.environ["audit.context"] = {}
|
||||
req = webob.Request.blank('/foo/bar',
|
||||
environ=self.get_environ_header('GET'))
|
||||
req.environ['audit.context'] = {}
|
||||
|
||||
middleware = self.create_simple_middleware()
|
||||
middleware._process_response(req, webob.response.Response())
|
||||
self.assertTrue(self.notifier.notify.called)
|
||||
|
||||
def test_ignore_req_opt(self):
|
||||
app = self.create_simple_app(ignore_req_list="get, PUT")
|
||||
app = self.create_simple_app(ignore_req_list='get, PUT')
|
||||
|
||||
# Check GET/PUT request does not send notification
|
||||
app.get("/skip/foo", extra_environ=self.get_environ_header())
|
||||
app.put("/skip/foo", extra_environ=self.get_environ_header())
|
||||
app.get('/skip/foo', extra_environ=self.get_environ_header())
|
||||
app.put('/skip/foo', extra_environ=self.get_environ_header())
|
||||
|
||||
self.assertFalse(self.notifier.notify.called)
|
||||
|
||||
# Check non-GET/PUT request does send notification
|
||||
app.post("/accept/foo", extra_environ=self.get_environ_header())
|
||||
app.post('/accept/foo', extra_environ=self.get_environ_header())
|
||||
|
||||
self.assertEqual(2, self.notifier.notify.call_count)
|
||||
|
||||
call_args = self.notifier.notify.call_args_list[0][0]
|
||||
self.assertEqual("audit.http.request", call_args[1])
|
||||
self.assertEqual("/accept/foo", call_args[2]["requestPath"])
|
||||
self.assertEqual('audit.http.request', call_args[1])
|
||||
self.assertEqual('/accept/foo', call_args[2]['requestPath'])
|
||||
|
||||
call_args = self.notifier.notify.call_args_list[1][0]
|
||||
self.assertEqual("audit.http.response", call_args[1])
|
||||
self.assertEqual("/accept/foo", call_args[2]["requestPath"])
|
||||
self.assertEqual('audit.http.response', call_args[1])
|
||||
self.assertEqual('/accept/foo', call_args[2]['requestPath'])
|
||||
|
||||
def test_cadf_event_context_scoped(self):
|
||||
self.create_simple_app().get(
|
||||
"/foo/bar", extra_environ=self.get_environ_header()
|
||||
)
|
||||
self.create_simple_app().get('/foo/bar',
|
||||
extra_environ=self.get_environ_header())
|
||||
|
||||
self.assertEqual(2, self.notifier.notify.call_count)
|
||||
first, second = [a[0] for a in self.notifier.notify.call_args_list]
|
||||
@@ -132,40 +135,38 @@ class AuditMiddlewareTest(base.BaseAuditMiddlewareTest):
|
||||
|
||||
def test_cadf_event_scoped_to_request(self):
|
||||
app = self.create_simple_app()
|
||||
resp = app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
self.assertIsNotNone(resp.request.environ.get("cadf_event"))
|
||||
resp = app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
self.assertIsNotNone(resp.request.environ.get('cadf_event'))
|
||||
|
||||
# ensure exact same event is used between request and response
|
||||
self.assertEqual(
|
||||
self.notifier.calls[0].payload["id"], self.notifier.calls[1].payload["id"]
|
||||
)
|
||||
self.assertEqual(self.notifier.calls[0].payload['id'],
|
||||
self.notifier.calls[1].payload['id'])
|
||||
|
||||
def test_cadf_event_scoped_to_request_on_error(self):
|
||||
middleware = self.create_simple_middleware()
|
||||
|
||||
req = webob.Request.blank("/foo/bar", environ=self.get_environ_header("GET"))
|
||||
req.environ["audit.context"] = {}
|
||||
self.notifier.notify.side_effect = Exception("error")
|
||||
req = webob.Request.blank('/foo/bar',
|
||||
environ=self.get_environ_header('GET'))
|
||||
req.environ['audit.context'] = {}
|
||||
self.notifier.notify.side_effect = Exception('error')
|
||||
|
||||
middleware(req)
|
||||
self.assertTrue(self.notifier.notify.called)
|
||||
|
||||
req2 = webob.Request.blank("/foo/bar", environ=self.get_environ_header("GET"))
|
||||
req2.environ["audit.context"] = {}
|
||||
req2 = webob.Request.blank('/foo/bar',
|
||||
environ=self.get_environ_header('GET'))
|
||||
req2.environ['audit.context'] = {}
|
||||
self.notifier.reset_mock()
|
||||
|
||||
middleware._process_response(req2, webob.response.Response())
|
||||
self.assertTrue(self.notifier.notify.called)
|
||||
# ensure event is not the same across requests
|
||||
self.assertNotEqual(
|
||||
req.environ["cadf_event"].id,
|
||||
self.notifier.notify.call_args_list[0][0][2]["id"],
|
||||
)
|
||||
self.assertNotEqual(req.environ['cadf_event'].id,
|
||||
self.notifier.notify.call_args_list[0][0][2]['id'])
|
||||
|
||||
def test_project_name_from_oslo_config(self):
|
||||
self.assertEqual(
|
||||
self.PROJECT_NAME, self.create_simple_middleware()._conf.project
|
||||
)
|
||||
self.assertEqual(self.PROJECT_NAME,
|
||||
self.create_simple_middleware()._conf.project)
|
||||
|
||||
def test_project_name_from_local_config(self):
|
||||
project_name = uuid.uuid4().hex
|
||||
@@ -174,35 +175,36 @@ class AuditMiddlewareTest(base.BaseAuditMiddlewareTest):
|
||||
|
||||
def test_no_response(self):
|
||||
middleware = self.create_simple_middleware()
|
||||
url = "http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers"
|
||||
req = webob.Request.blank(
|
||||
url, environ=self.get_environ_header("GET"), remote_addr="192.168.0.1"
|
||||
)
|
||||
req.environ["audit.context"] = {}
|
||||
url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
|
||||
req = webob.Request.blank(url,
|
||||
environ=self.get_environ_header('GET'),
|
||||
remote_addr='192.168.0.1')
|
||||
req.environ['audit.context'] = {}
|
||||
middleware._process_request(req)
|
||||
payload = req.environ["cadf_event"].as_dict()
|
||||
payload = req.environ['cadf_event'].as_dict()
|
||||
middleware._process_response(req, None)
|
||||
payload2 = req.environ["cadf_event"].as_dict()
|
||||
self.assertEqual(payload["id"], payload2["id"])
|
||||
self.assertEqual(payload["tags"], payload2["tags"])
|
||||
self.assertEqual(payload2["outcome"], "unknown")
|
||||
self.assertNotIn("reason", payload2)
|
||||
self.assertEqual(len(payload2["reporterchain"]), 1)
|
||||
self.assertEqual(payload2["reporterchain"][0]["role"], "modifier")
|
||||
self.assertEqual(payload2["reporterchain"][0]["reporter"]["id"], "target")
|
||||
payload2 = req.environ['cadf_event'].as_dict()
|
||||
self.assertEqual(payload['id'], payload2['id'])
|
||||
self.assertEqual(payload['tags'], payload2['tags'])
|
||||
self.assertEqual(payload2['outcome'], 'unknown')
|
||||
self.assertNotIn('reason', payload2)
|
||||
self.assertEqual(len(payload2['reporterchain']), 1)
|
||||
self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier')
|
||||
self.assertEqual(payload2['reporterchain'][0]['reporter']['id'],
|
||||
'target')
|
||||
|
||||
def test_missing_req(self):
|
||||
req = webob.Request.blank(
|
||||
"http://admin_host:8774/v2/" + str(uuid.uuid4()) + "/servers",
|
||||
environ=self.get_environ_header("GET"),
|
||||
)
|
||||
req.environ["audit.context"] = {}
|
||||
self.assertNotIn("cadf_event", req.environ)
|
||||
req = webob.Request.blank('http://admin_host:8774/v2/'
|
||||
+ str(uuid.uuid4()) + '/servers',
|
||||
environ=self.get_environ_header('GET'))
|
||||
req.environ['audit.context'] = {}
|
||||
self.assertNotIn('cadf_event', req.environ)
|
||||
|
||||
self.create_simple_middleware()._process_response(req, webob.Response())
|
||||
self.assertIn("cadf_event", req.environ)
|
||||
payload = req.environ["cadf_event"].as_dict()
|
||||
self.assertEqual(payload["outcome"], "success")
|
||||
self.assertEqual(payload["reason"]["reasonType"], "HTTP")
|
||||
self.assertEqual(payload["reason"]["reasonCode"], "200")
|
||||
self.assertEqual(payload["observer"]["id"], "target")
|
||||
self.create_simple_middleware()._process_response(req,
|
||||
webob.Response())
|
||||
self.assertIn('cadf_event', req.environ)
|
||||
payload = req.environ['cadf_event'].as_dict()
|
||||
self.assertEqual(payload['outcome'], 'success')
|
||||
self.assertEqual(payload['reason']['reasonType'], 'HTTP')
|
||||
self.assertEqual(payload['reason']['reasonCode'], '200')
|
||||
self.assertEqual(payload['observer']['id'], 'target')
|
||||
@@ -17,78 +17,76 @@ from keystonemiddleware.tests.unit.audit import base
|
||||
|
||||
|
||||
class AuditNotifierConfigTest(base.BaseAuditMiddlewareTest):
|
||||
|
||||
def test_conf_middleware_log_and_default_as_messaging(self):
|
||||
self.cfg.config(driver="log", group="audit_middleware_notifications")
|
||||
self.cfg.config(driver='log',
|
||||
group='audit_middleware_notifications')
|
||||
app = self.create_simple_app()
|
||||
with mock.patch(
|
||||
"oslo_messaging.notify._impl_log.LogDriver.notify",
|
||||
side_effect=Exception("error"),
|
||||
) as driver:
|
||||
app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify',
|
||||
side_effect=Exception('error')) as driver:
|
||||
app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
# audit middleware conf has 'log' make sure that driver is invoked
|
||||
# and not the one specified in DEFAULT section
|
||||
self.assertTrue(driver.called)
|
||||
|
||||
def test_conf_middleware_log_and_oslo_msg_as_messaging(self):
|
||||
self.cfg.config(driver=["messaging"], group="oslo_messaging_notifications")
|
||||
self.cfg.config(driver="log", group="audit_middleware_notifications")
|
||||
self.cfg.config(driver=['messaging'],
|
||||
group='oslo_messaging_notifications')
|
||||
self.cfg.config(driver='log',
|
||||
group='audit_middleware_notifications')
|
||||
|
||||
app = self.create_simple_app()
|
||||
with mock.patch(
|
||||
"oslo_messaging.notify._impl_log.LogDriver.notify",
|
||||
side_effect=Exception("error"),
|
||||
) as driver:
|
||||
app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify',
|
||||
side_effect=Exception('error')) as driver:
|
||||
app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
# audit middleware conf has 'log' make sure that driver is invoked
|
||||
# and not the one specified in oslo_messaging_notifications section
|
||||
self.assertTrue(driver.called)
|
||||
|
||||
def test_conf_middleware_messaging_and_oslo_msg_as_log(self):
|
||||
self.cfg.config(driver=["log"], group="oslo_messaging_notifications")
|
||||
self.cfg.config(driver="messaging", group="audit_middleware_notifications")
|
||||
self.cfg.config(driver=['log'], group='oslo_messaging_notifications')
|
||||
self.cfg.config(driver='messaging',
|
||||
group='audit_middleware_notifications')
|
||||
app = self.create_simple_app()
|
||||
with mock.patch(
|
||||
"oslo_messaging.notify.messaging.MessagingDriver.notify",
|
||||
side_effect=Exception("error"),
|
||||
) as driver:
|
||||
with mock.patch('oslo_messaging.notify.messaging.MessagingDriver'
|
||||
'.notify',
|
||||
side_effect=Exception('error')) as driver:
|
||||
# audit middleware has 'messaging' make sure that driver is invoked
|
||||
# and not the one specified in oslo_messaging_notifications section
|
||||
app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
self.assertTrue(driver.called)
|
||||
|
||||
def test_with_no_middleware_notification_conf(self):
|
||||
self.cfg.config(driver=["messaging"], group="oslo_messaging_notifications")
|
||||
self.cfg.config(driver=None, group="audit_middleware_notifications")
|
||||
self.cfg.config(driver=['messaging'],
|
||||
group='oslo_messaging_notifications')
|
||||
self.cfg.config(driver=None, group='audit_middleware_notifications')
|
||||
|
||||
app = self.create_simple_app()
|
||||
with mock.patch(
|
||||
"oslo_messaging.notify.messaging.MessagingDriver.notify",
|
||||
side_effect=Exception("error"),
|
||||
) as driver:
|
||||
with mock.patch('oslo_messaging.notify.messaging.MessagingDriver'
|
||||
'.notify',
|
||||
side_effect=Exception('error')) as driver:
|
||||
# audit middleware section is not set. So driver needs to be
|
||||
# invoked from oslo_messaging_notifications section.
|
||||
app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
self.assertTrue(driver.called)
|
||||
|
||||
@mock.patch("oslo_messaging.get_notification_transport")
|
||||
@mock.patch('oslo_messaging.get_notification_transport')
|
||||
def test_conf_middleware_messaging_and_transport_set(self, m):
|
||||
transport_url = "rabbit://me:passwd@host:5672/virtual_host"
|
||||
self.cfg.config(
|
||||
driver="messaging",
|
||||
transport_url=transport_url,
|
||||
group="audit_middleware_notifications",
|
||||
)
|
||||
transport_url = 'rabbit://me:passwd@host:5672/virtual_host'
|
||||
self.cfg.config(driver='messaging',
|
||||
transport_url=transport_url,
|
||||
group='audit_middleware_notifications')
|
||||
|
||||
self.create_simple_middleware()
|
||||
self.assertTrue(m.called)
|
||||
# make sure first call kwarg 'url' is same as provided transport_url
|
||||
self.assertEqual(transport_url, m.call_args_list[0][1]["url"])
|
||||
self.assertEqual(transport_url, m.call_args_list[0][1]['url'])
|
||||
|
||||
def test_do_not_use_oslo_messaging(self):
|
||||
self.cfg.config(
|
||||
use_oslo_messaging=False, group="audit_middleware_notifications"
|
||||
)
|
||||
self.cfg.config(use_oslo_messaging=False,
|
||||
group='audit_middleware_notifications')
|
||||
audit_middleware = self.create_simple_middleware()
|
||||
|
||||
# make sure it is using a local notifier instead of oslo_messaging
|
||||
self.assertIsInstance(audit_middleware._notifier, audit._notifier._LogNotifier)
|
||||
self.assertIsInstance(audit_middleware._notifier,
|
||||
audit._notifier._LogNotifier)
|
||||
@@ -18,26 +18,26 @@ from keystonemiddleware.tests.unit.audit import base
|
||||
|
||||
|
||||
class TestLoggingNotifier(base.BaseAuditMiddlewareTest):
|
||||
|
||||
def setUp(self):
|
||||
p = "keystonemiddleware.audit._notifier.oslo_messaging"
|
||||
p = 'keystonemiddleware.audit._notifier.oslo_messaging'
|
||||
f = fixtures.MockPatch(p, None)
|
||||
self.messaging_fixture = self.useFixture(f)
|
||||
|
||||
super(TestLoggingNotifier, self).setUp()
|
||||
|
||||
def test_api_request_no_messaging(self):
|
||||
self.cfg.config(
|
||||
use_oslo_messaging=False, group="audit_middleware_notifications"
|
||||
)
|
||||
self.cfg.config(use_oslo_messaging=False,
|
||||
group='audit_middleware_notifications')
|
||||
app = self.create_simple_app()
|
||||
|
||||
with mock.patch("keystonemiddleware.audit._LOG.info") as log:
|
||||
app.get("/foo/bar", extra_environ=self.get_environ_header())
|
||||
with mock.patch('keystonemiddleware.audit._LOG.info') as log:
|
||||
app.get('/foo/bar', extra_environ=self.get_environ_header())
|
||||
|
||||
# Check first notification with only 'request'
|
||||
call_args = log.call_args_list[0][0]
|
||||
self.assertEqual("audit.http.request", call_args[1]["event_type"])
|
||||
self.assertEqual('audit.http.request', call_args[1]['event_type'])
|
||||
|
||||
# Check second notification with request + response
|
||||
call_args = log.call_args_list[1][0]
|
||||
self.assertEqual("audit.http.response", call_args[1]["event_type"])
|
||||
self.assertEqual('audit.http.response', call_args[1]['event_type'])
|
||||
@@ -24,6 +24,7 @@ from keystonemiddleware.tests.unit import utils
|
||||
|
||||
|
||||
class BaseAuthTokenTestCase(utils.MiddlewareTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseAuthTokenTestCase, self).setUp()
|
||||
self.requests_mock = self.useFixture(rm_fixture.Fixture())
|
||||
@@ -32,6 +33,7 @@ class BaseAuthTokenTestCase(utils.MiddlewareTestCase):
|
||||
self.cfg.conf(args=[])
|
||||
|
||||
def create_middleware(self, cb, conf=None, use_global_conf=False):
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
return cb(req)
|
||||
@@ -40,21 +42,15 @@ class BaseAuthTokenTestCase(utils.MiddlewareTestCase):
|
||||
opts = conf or {}
|
||||
else:
|
||||
opts = {
|
||||
"oslo_config_config": self.cfg.conf,
|
||||
'oslo_config_config': self.cfg.conf,
|
||||
}
|
||||
opts.update(conf or {})
|
||||
|
||||
return auth_token.AuthProtocol(_do_cb, opts)
|
||||
|
||||
def call(
|
||||
self,
|
||||
middleware,
|
||||
method="GET",
|
||||
path="/",
|
||||
headers=None,
|
||||
expected_status=http_client.OK,
|
||||
expected_body_string=None,
|
||||
):
|
||||
def call(self, middleware, method='GET', path='/', headers=None,
|
||||
expected_status=http_client.OK,
|
||||
expected_body_string=None):
|
||||
req = webob.Request.blank(path)
|
||||
req.method = method
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -23,12 +23,14 @@ from keystonemiddleware.auth_token import _request
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
return webob.Response()
|
||||
|
||||
|
||||
class FetchingMiddleware(auth_token.BaseAuthProtocol):
|
||||
|
||||
def __init__(self, app, token_dict={}, **kwargs):
|
||||
super(FetchingMiddleware, self).__init__(app, **kwargs)
|
||||
self.token_dict = token_dict
|
||||
@@ -41,32 +43,30 @@ class FetchingMiddleware(auth_token.BaseAuthProtocol):
|
||||
|
||||
|
||||
class BaseAuthProtocolTests(testtools.TestCase):
|
||||
@mock.patch.multiple(
|
||||
auth_token.BaseAuthProtocol,
|
||||
process_request=mock.DEFAULT,
|
||||
process_response=mock.DEFAULT,
|
||||
)
|
||||
|
||||
@mock.patch.multiple(auth_token.BaseAuthProtocol,
|
||||
process_request=mock.DEFAULT,
|
||||
process_response=mock.DEFAULT)
|
||||
def test_process_flow(self, process_request, process_response):
|
||||
m = auth_token.BaseAuthProtocol(FakeApp())
|
||||
|
||||
process_request.return_value = None
|
||||
process_response.side_effect = lambda x: x
|
||||
|
||||
req = webob.Request.blank("/", method="GET")
|
||||
req = webob.Request.blank('/', method='GET')
|
||||
resp = req.get_response(m)
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
self.assertEqual(1, process_request.call_count)
|
||||
self.assertIsInstance(
|
||||
process_request.call_args[0][0], _request._AuthTokenRequest
|
||||
)
|
||||
self.assertIsInstance(process_request.call_args[0][0],
|
||||
_request._AuthTokenRequest)
|
||||
|
||||
self.assertEqual(1, process_response.call_count)
|
||||
self.assertIsInstance(process_response.call_args[0][0], webob.Response)
|
||||
|
||||
@classmethod
|
||||
def call(cls, middleware, method="GET", path="/", headers=None):
|
||||
def call(cls, middleware, method='GET', path='/', headers=None):
|
||||
req = webob.Request.blank(path)
|
||||
req.method = method
|
||||
|
||||
@@ -87,12 +87,12 @@ class BaseAuthProtocolTests(testtools.TestCase):
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual(token_id, req.headers["X-Auth-Token"].strip())
|
||||
self.assertEqual(token_id, req.headers['X-Auth-Token'].strip())
|
||||
|
||||
self.assertEqual("Confirmed", req.headers["X-Identity-Status"])
|
||||
self.assertNotIn("X-Service-Token", req.headers)
|
||||
self.assertEqual('Confirmed', req.headers['X-Identity-Status'])
|
||||
self.assertNotIn('X-Service-Token', req.headers)
|
||||
|
||||
p = req.environ["keystone.token_auth"]
|
||||
p = req.environ['keystone.token_auth']
|
||||
|
||||
self.assertTrue(p.has_user_token)
|
||||
self.assertFalse(p.has_service_token)
|
||||
@@ -101,64 +101,65 @@ class BaseAuthProtocolTests(testtools.TestCase):
|
||||
self.assertEqual(t.project_domain_id, p.user.project_domain_id)
|
||||
self.assertEqual(t.user_id, p.user.user_id)
|
||||
self.assertEqual(t.user_domain_id, p.user.user_domain_id)
|
||||
self.assertIn(role["name"], p.user.role_names)
|
||||
self.assertIn(role['name'], p.user.role_names)
|
||||
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb, token_dict)
|
||||
self.call(m, headers={"X-Auth-Token": token_id})
|
||||
self.call(m, headers={'X-Auth-Token': token_id})
|
||||
|
||||
# also try with whitespace in the token
|
||||
self.call(m, headers={"X-Auth-Token": token_id + " "})
|
||||
self.call(m, headers={"X-Auth-Token": token_id + "\r"})
|
||||
self.call(m, headers={'X-Auth-Token': token_id + ' '})
|
||||
self.call(m, headers={'X-Auth-Token': token_id + '\r'})
|
||||
|
||||
def test_invalid_user_token(self):
|
||||
token_id = uuid.uuid4().hex
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual("Invalid", req.headers["X-Identity-Status"])
|
||||
self.assertEqual(token_id, req.headers["X-Auth-Token"])
|
||||
self.assertEqual('Invalid', req.headers['X-Identity-Status'])
|
||||
self.assertEqual(token_id, req.headers['X-Auth-Token'])
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb)
|
||||
self.call(m, headers={"X-Auth-Token": token_id})
|
||||
self.call(m, headers={'X-Auth-Token': token_id})
|
||||
|
||||
def test_expired_user_token(self):
|
||||
t = fixture.V3Token()
|
||||
t.set_project_scope()
|
||||
t.expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
|
||||
minutes=10
|
||||
)
|
||||
t.expires = (
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
- datetime.timedelta(minutes=10))
|
||||
|
||||
token_id = uuid.uuid4().hex
|
||||
token_dict = {token_id: t}
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual("Invalid", req.headers["X-Identity-Status"])
|
||||
self.assertEqual(token_id, req.headers["X-Auth-Token"])
|
||||
self.assertEqual('Invalid', req.headers['X-Identity-Status'])
|
||||
self.assertEqual(token_id, req.headers['X-Auth-Token'])
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb, token_dict=token_dict)
|
||||
self.call(m, headers={"X-Auth-Token": token_id})
|
||||
self.call(m, headers={'X-Auth-Token': token_id})
|
||||
|
||||
def test_good_v3_service_token(self):
|
||||
t = fixture.V3Token()
|
||||
t.set_project_scope()
|
||||
role = t.add_role(name="service")
|
||||
role = t.add_role(name='service')
|
||||
|
||||
token_id = uuid.uuid4().hex
|
||||
token_dict = {token_id: t}
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual(token_id, req.headers["X-Service-Token"].strip())
|
||||
self.assertEqual(token_id, req.headers['X-Service-Token'].strip())
|
||||
|
||||
self.assertEqual("Confirmed", req.headers["X-Service-Identity-Status"])
|
||||
self.assertNotIn("X-Auth-Token", req.headers)
|
||||
self.assertEqual('Confirmed',
|
||||
req.headers['X-Service-Identity-Status'])
|
||||
self.assertNotIn('X-Auth-Token', req.headers)
|
||||
|
||||
p = req.environ["keystone.token_auth"]
|
||||
p = req.environ['keystone.token_auth']
|
||||
|
||||
self.assertFalse(p.has_user_token)
|
||||
self.assertTrue(p.has_service_token)
|
||||
@@ -167,47 +168,50 @@ class BaseAuthProtocolTests(testtools.TestCase):
|
||||
self.assertEqual(t.project_domain_id, p.service.project_domain_id)
|
||||
self.assertEqual(t.user_id, p.service.user_id)
|
||||
self.assertEqual(t.user_domain_id, p.service.user_domain_id)
|
||||
self.assertIn(role["name"], p.service.role_names)
|
||||
self.assertIn(role['name'], p.service.role_names)
|
||||
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb, token_dict, service_token_roles=["service"])
|
||||
self.call(m, headers={"X-Service-Token": token_id})
|
||||
m = FetchingMiddleware(_do_cb, token_dict,
|
||||
service_token_roles=['service'])
|
||||
self.call(m, headers={'X-Service-Token': token_id})
|
||||
|
||||
# also try with whitespace in the token
|
||||
self.call(m, headers={"X-Service-Token": token_id + " "})
|
||||
self.call(m, headers={"X-Service-Token": token_id + "\r"})
|
||||
self.call(m, headers={'X-Service-Token': token_id + ' '})
|
||||
self.call(m, headers={'X-Service-Token': token_id + '\r'})
|
||||
|
||||
def test_invalid_service_token(self):
|
||||
token_id = uuid.uuid4().hex
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual("Invalid", req.headers["X-Service-Identity-Status"])
|
||||
self.assertEqual(token_id, req.headers["X-Service-Token"])
|
||||
self.assertEqual('Invalid',
|
||||
req.headers['X-Service-Identity-Status'])
|
||||
self.assertEqual(token_id, req.headers['X-Service-Token'])
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb)
|
||||
self.call(m, headers={"X-Service-Token": token_id})
|
||||
self.call(m, headers={'X-Service-Token': token_id})
|
||||
|
||||
def test_expired_service_token(self):
|
||||
t = fixture.V3Token()
|
||||
t.set_project_scope()
|
||||
t.expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
|
||||
minutes=10
|
||||
)
|
||||
t.expires = (
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
- datetime.timedelta(minutes=10))
|
||||
|
||||
token_id = uuid.uuid4().hex
|
||||
token_dict = {token_id: t}
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual("Invalid", req.headers["X-Service-Identity-Status"])
|
||||
self.assertEqual(token_id, req.headers["X-Service-Token"])
|
||||
self.assertEqual('Invalid',
|
||||
req.headers['X-Service-Identity-Status'])
|
||||
self.assertEqual(token_id, req.headers['X-Service-Token'])
|
||||
return webob.Response()
|
||||
|
||||
m = FetchingMiddleware(_do_cb, token_dict=token_dict)
|
||||
self.call(m, headers={"X-Service-Token": token_id})
|
||||
self.call(m, headers={'X-Service-Token': token_id})
|
||||
|
||||
def test_base_doesnt_block_project_id(self):
|
||||
# X-Project-Id and X-Domain-Id must pass for tokenless/x509 auth
|
||||
@@ -217,12 +221,11 @@ class BaseAuthProtocolTests(testtools.TestCase):
|
||||
|
||||
@webob.dec.wsgify
|
||||
def _do_cb(req):
|
||||
self.assertEqual(project_id, req.headers["X-Project-Id"])
|
||||
self.assertEqual(domain_id, req.headers["X-Domain-Id"])
|
||||
self.assertEqual(project_id, req.headers['X-Project-Id'])
|
||||
self.assertEqual(domain_id, req.headers['X-Domain-Id'])
|
||||
return webob.Response(body, 200)
|
||||
|
||||
m = FetchingMiddleware(_do_cb)
|
||||
resp = self.call(
|
||||
m, headers={"X-Project-Id": project_id, "X-Domain-Id": domain_id}
|
||||
)
|
||||
resp = self.call(m, headers={'X-Project-Id': project_id,
|
||||
'X-Domain-Id': domain_id})
|
||||
self.assertEqual(body, resp.text)
|
||||
@@ -17,74 +17,76 @@ from unittest import mock
|
||||
|
||||
from keystonemiddleware.auth_token import _cache
|
||||
from keystonemiddleware.auth_token import _exceptions as exc
|
||||
from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt
|
||||
from keystonemiddleware.tests.unit.auth_token import base
|
||||
from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware import BASE_URI
|
||||
from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware import (
|
||||
FAKE_ADMIN_TOKEN_ID,
|
||||
)
|
||||
from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \
|
||||
import BASE_URI
|
||||
from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \
|
||||
import FAKE_ADMIN_TOKEN_ID
|
||||
from keystonemiddleware.tests.unit import utils
|
||||
|
||||
MEMCACHED_SERVERS = ["localhost:11211"]
|
||||
MEMCACHED_SERVERS = ['localhost:11211']
|
||||
MEMCACHED_AVAILABLE = None
|
||||
|
||||
|
||||
class TestCacheSetup(base.BaseAuthTokenTestCase):
|
||||
|
||||
def test_assert_valid_memcache_protection_config(self):
|
||||
# test missing memcache_secret_key
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "Encrypt",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'Encrypt'
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.ConfigurationError, self.create_simple_middleware, conf=conf
|
||||
)
|
||||
self.assertRaises(exc.ConfigurationError,
|
||||
self.create_simple_middleware,
|
||||
conf=conf)
|
||||
# test invalue memcache_security_strategy
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "whatever",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'whatever'
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.ConfigurationError, self.create_simple_middleware, conf=conf
|
||||
)
|
||||
self.assertRaises(exc.ConfigurationError,
|
||||
self.create_simple_middleware,
|
||||
conf=conf)
|
||||
# test missing memcache_secret_key
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "mac",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'mac'
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.ConfigurationError, self.create_simple_middleware, conf=conf
|
||||
)
|
||||
self.assertRaises(exc.ConfigurationError,
|
||||
self.create_simple_middleware,
|
||||
conf=conf)
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "Encrypt",
|
||||
"memcache_secret_key": "",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'Encrypt',
|
||||
'memcache_secret_key': ''
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.ConfigurationError, self.create_simple_middleware, conf=conf
|
||||
)
|
||||
self.assertRaises(exc.ConfigurationError,
|
||||
self.create_simple_middleware,
|
||||
conf=conf)
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "mAc",
|
||||
"memcache_secret_key": "",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'mAc',
|
||||
'memcache_secret_key': ''
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.ConfigurationError, self.create_simple_middleware, conf=conf
|
||||
)
|
||||
self.assertRaises(exc.ConfigurationError,
|
||||
self.create_simple_middleware,
|
||||
conf=conf)
|
||||
|
||||
|
||||
class NoMemcacheAuthToken(base.BaseAuthTokenTestCase):
|
||||
@@ -92,21 +94,22 @@ class NoMemcacheAuthToken(base.BaseAuthTokenTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NoMemcacheAuthToken, self).setUp()
|
||||
self.useFixture(utils.DisableModuleFixture("memcache"))
|
||||
self.useFixture(utils.DisableModuleFixture('memcache'))
|
||||
|
||||
def test_nomemcache(self):
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"www_authenticate_uri": "https://keystone.example.com:1234",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'www_authenticate_uri': 'https://keystone.example.com:1234',
|
||||
}
|
||||
|
||||
self.create_simple_middleware(conf=conf)
|
||||
|
||||
|
||||
class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLiveMemcache, self).setUp()
|
||||
|
||||
@@ -115,24 +118,23 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
if MEMCACHED_AVAILABLE is None:
|
||||
try:
|
||||
import memcache
|
||||
|
||||
c = memcache.Client(MEMCACHED_SERVERS)
|
||||
c.set("ping", "pong", time=1)
|
||||
MEMCACHED_AVAILABLE = c.get("ping") == "pong"
|
||||
c.set('ping', 'pong', time=1)
|
||||
MEMCACHED_AVAILABLE = c.get('ping') == 'pong'
|
||||
except ImportError:
|
||||
MEMCACHED_AVAILABLE = False
|
||||
|
||||
if not MEMCACHED_AVAILABLE:
|
||||
self.skipTest("memcached not available")
|
||||
self.skipTest('memcached not available')
|
||||
|
||||
def test_encrypt_cache_data(self):
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "encrypt",
|
||||
"memcache_secret_key": "mysecret",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'encrypt',
|
||||
'memcache_secret_key': 'mysecret'
|
||||
}
|
||||
|
||||
token = uuid.uuid4().hex.encode()
|
||||
@@ -146,15 +148,16 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
|
||||
@mock.patch("keystonemiddleware.auth_token._memcache_crypt.unprotect_data")
|
||||
def test_corrupted_cache_data(self, mocked_decrypt_data):
|
||||
mocked_decrypt_data.side_effect = Exception("corrupted")
|
||||
mocked_decrypt_data.side_effect = memcache_crypt.InvalidMacError(
|
||||
"corrupted")
|
||||
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "encrypt",
|
||||
"memcache_secret_key": "mysecret",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'encrypt',
|
||||
'memcache_secret_key': 'mysecret'
|
||||
}
|
||||
|
||||
token = uuid.uuid4().hex.encode()
|
||||
@@ -168,12 +171,12 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
|
||||
def test_sign_cache_data(self):
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_security_strategy": "mac",
|
||||
"memcache_secret_key": "mysecret",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_security_strategy': 'mac',
|
||||
'memcache_secret_key': 'mysecret'
|
||||
}
|
||||
|
||||
token = uuid.uuid4().hex.encode()
|
||||
@@ -187,11 +190,11 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
|
||||
def test_no_memcache_protection(self):
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_secret_key": "mysecret",
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_secret_key': 'mysecret'
|
||||
}
|
||||
|
||||
token = uuid.uuid4().hex.encode()
|
||||
@@ -204,11 +207,11 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
|
||||
def test_memcache_pool(self):
|
||||
conf = {
|
||||
"auth_type": "admin_token",
|
||||
"endpoint": "%s/v3" % BASE_URI,
|
||||
"token": FAKE_ADMIN_TOKEN_ID,
|
||||
"memcached_servers": ",".join(MEMCACHED_SERVERS),
|
||||
"memcache_use_advanced_pool": True,
|
||||
'auth_type': 'admin_token',
|
||||
'endpoint': '%s/v3' % BASE_URI,
|
||||
'token': FAKE_ADMIN_TOKEN_ID,
|
||||
'memcached_servers': ','.join(MEMCACHED_SERVERS),
|
||||
'memcache_use_advanced_pool': True
|
||||
}
|
||||
|
||||
token = uuid.uuid4().hex.encode()
|
||||
@@ -224,12 +227,12 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase):
|
||||
class TestMemcachePoolAbstraction(utils.TestCase):
|
||||
def setUp(self):
|
||||
super(TestMemcachePoolAbstraction, self).setUp()
|
||||
self.useFixture(fixtures.MockPatch("oslo_cache._memcache_pool._MemcacheClient"))
|
||||
self.useFixture(fixtures.MockPatch(
|
||||
'oslo_cache._memcache_pool._MemcacheClient'))
|
||||
|
||||
def test_abstraction_layer_reserve_places_connection_back_in_pool(self):
|
||||
cache_pool = _cache._MemcacheClientPool(
|
||||
memcache_servers=[], arguments={}, maxsize=1, unused_timeout=10
|
||||
)
|
||||
memcache_servers=[], arguments={}, maxsize=1, unused_timeout=10)
|
||||
conn = None
|
||||
with cache_pool.reserve() as client:
|
||||
self.assertEqual(cache_pool._pool._acquired, 1)
|
||||
|
||||
@@ -25,6 +25,7 @@ def conf_get(app, *args, **kwargs):
|
||||
|
||||
|
||||
class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAuthPluginLocalOsloConfig, self).setUp()
|
||||
self.project = uuid.uuid4().hex
|
||||
@@ -34,37 +35,37 @@ class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase):
|
||||
# in the 'keystone_authtoken' group. Additional options, from
|
||||
# plugins, are registered dynamically so must not be used here.
|
||||
self.oslo_options = {
|
||||
"www_authenticate_uri": uuid.uuid4().hex,
|
||||
'www_authenticate_uri': uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
self.local_oslo_config = cfg.ConfigOpts()
|
||||
self.local_oslo_config.register_group(cfg.OptGroup(name="keystone_authtoken"))
|
||||
self.local_oslo_config.register_group(
|
||||
cfg.OptGroup(name='keystone_authtoken'))
|
||||
|
||||
self.local_oslo_config.register_opts(_opts._OPTS, group="keystone_authtoken")
|
||||
self.local_oslo_config.register_opts(_opts._OPTS,
|
||||
group='keystone_authtoken')
|
||||
|
||||
for option, value in self.oslo_options.items():
|
||||
self.local_oslo_config.set_override(option, value, "keystone_authtoken")
|
||||
self.local_oslo_config.set_override(option, value,
|
||||
'keystone_authtoken')
|
||||
self.local_oslo_config(args=[], project=self.project)
|
||||
|
||||
self.file_options = {
|
||||
"auth_type": "password",
|
||||
"www_authenticate_uri": uuid.uuid4().hex,
|
||||
"password": uuid.uuid4().hex,
|
||||
'auth_type': 'password',
|
||||
'www_authenticate_uri': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
content = (
|
||||
"[DEFAULT]\n"
|
||||
"test_opt=15\n"
|
||||
"[keystone_authtoken]\n"
|
||||
"auth_type=%(auth_type)s\n"
|
||||
"www_authenticate_uri=%(www_authenticate_uri)s\n"
|
||||
"auth_url=%(www_authenticate_uri)s\n"
|
||||
"password=%(password)s\n" % self.file_options
|
||||
)
|
||||
content = ("[DEFAULT]\n"
|
||||
"test_opt=15\n"
|
||||
"[keystone_authtoken]\n"
|
||||
"auth_type=%(auth_type)s\n"
|
||||
"www_authenticate_uri=%(www_authenticate_uri)s\n"
|
||||
"auth_url=%(www_authenticate_uri)s\n"
|
||||
"password=%(password)s\n" % self.file_options)
|
||||
|
||||
self.conf_file_fixture = self.useFixture(
|
||||
createfile.CreateFileWithContent(self.project, content)
|
||||
)
|
||||
createfile.CreateFileWithContent(self.project, content))
|
||||
|
||||
def _create_app(self, conf, project_version=None):
|
||||
if not project_version:
|
||||
@@ -74,49 +75,46 @@ class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase):
|
||||
fake_im.return_value = project_version
|
||||
|
||||
body = uuid.uuid4().hex
|
||||
at_im = "keystonemiddleware._common.config.importlib.metadata.version"
|
||||
at_im = 'keystonemiddleware._common.config.importlib.metadata.version'
|
||||
with mock.patch(at_im, new=fake_im):
|
||||
# use_global_conf is poorly named. What it means is
|
||||
# don't use the config created in test setUp.
|
||||
return self.create_simple_middleware(
|
||||
body=body, conf=conf, use_global_conf=True
|
||||
)
|
||||
return self.create_simple_middleware(body=body, conf=conf,
|
||||
use_global_conf=True)
|
||||
|
||||
def test_project_in_local_oslo_configuration(self):
|
||||
conf = {
|
||||
"oslo_config_project": self.project,
|
||||
"oslo_config_file": self.conf_file_fixture.path,
|
||||
}
|
||||
conf = {'oslo_config_project': self.project,
|
||||
'oslo_config_file': self.conf_file_fixture.path}
|
||||
app = self._create_app(conf)
|
||||
for option in self.file_options:
|
||||
self.assertEqual(self.file_options[option], conf_get(app, option), option)
|
||||
self.assertEqual(self.file_options[option],
|
||||
conf_get(app, option), option)
|
||||
|
||||
def test_passed_oslo_configuration(self):
|
||||
conf = {"oslo_config_config": self.local_oslo_config}
|
||||
conf = {'oslo_config_config': self.local_oslo_config}
|
||||
app = self._create_app(conf)
|
||||
for option in self.oslo_options:
|
||||
self.assertEqual(self.oslo_options[option], conf_get(app, option))
|
||||
self.assertEqual(self.oslo_options[option],
|
||||
conf_get(app, option))
|
||||
|
||||
def test_passed_oslo_configuration_with_deprecated_ones(self):
|
||||
deprecated_opt = cfg.IntOpt("test_opt", deprecated_for_removal=True)
|
||||
deprecated_opt = cfg.IntOpt('test_opt', deprecated_for_removal=True)
|
||||
cfg.CONF.register_opt(deprecated_opt)
|
||||
cfg.CONF(args=[], default_config_files=[self.conf_file_fixture.path])
|
||||
conf = {"oslo_config_config": cfg.CONF}
|
||||
cfg.CONF(args=[],
|
||||
default_config_files=[self.conf_file_fixture.path])
|
||||
conf = {'oslo_config_config': cfg.CONF}
|
||||
|
||||
# success to init AuthProtocol
|
||||
self._create_app(conf)
|
||||
|
||||
def test_passed_oslo_configuration_wins(self):
|
||||
"""oslo_config_config has precedence over oslo_config_project."""
|
||||
conf = {
|
||||
"oslo_config_project": self.project,
|
||||
"oslo_config_config": self.local_oslo_config,
|
||||
"oslo_config_file": self.conf_file_fixture.path,
|
||||
}
|
||||
conf = {'oslo_config_project': self.project,
|
||||
'oslo_config_config': self.local_oslo_config,
|
||||
'oslo_config_file': self.conf_file_fixture.path}
|
||||
app = self._create_app(conf)
|
||||
for option in self.oslo_options:
|
||||
self.assertEqual(self.oslo_options[option], conf_get(app, option))
|
||||
self.assertNotEqual(
|
||||
self.file_options["www_authenticate_uri"],
|
||||
conf_get(app, "www_authenticate_uri"),
|
||||
)
|
||||
self.assertEqual(self.oslo_options[option],
|
||||
conf_get(app, option))
|
||||
self.assertNotEqual(self.file_options['www_authenticate_uri'],
|
||||
conf_get(app, 'www_authenticate_uri'))
|
||||
@@ -22,7 +22,7 @@ from keystonemiddleware.tests.unit import utils
|
||||
|
||||
|
||||
class _TestConnectionPool(_memcache_pool.ConnectionPool):
|
||||
destroyed_value = "destroyed"
|
||||
destroyed_value = 'destroyed'
|
||||
|
||||
def _create_connection(self):
|
||||
return mock.MagicMock()
|
||||
@@ -37,8 +37,8 @@ class TestConnectionPool(utils.TestCase):
|
||||
self.unused_timeout = 10
|
||||
self.maxsize = 2
|
||||
self.connection_pool = _TestConnectionPool(
|
||||
maxsize=self.maxsize, unused_timeout=self.unused_timeout
|
||||
)
|
||||
maxsize=self.maxsize,
|
||||
unused_timeout=self.unused_timeout)
|
||||
|
||||
def test_get_context_manager(self):
|
||||
self.assertThat(self.connection_pool.queue, matchers.HasLength(0))
|
||||
@@ -52,15 +52,16 @@ class TestConnectionPool(utils.TestCase):
|
||||
self.test_get_context_manager()
|
||||
newtime = time.time() + self.unused_timeout * 2
|
||||
non_expired_connection = _memcache_pool._PoolItem(
|
||||
ttl=(newtime * 2), connection=mock.MagicMock()
|
||||
)
|
||||
ttl=(newtime * 2),
|
||||
connection=mock.MagicMock())
|
||||
self.connection_pool.queue.append(non_expired_connection)
|
||||
self.assertThat(self.connection_pool.queue, matchers.HasLength(2))
|
||||
with mock.patch.object(time, "time", return_value=newtime):
|
||||
with mock.patch.object(time, 'time', return_value=newtime):
|
||||
conn = self.connection_pool.queue[0].connection
|
||||
with self.connection_pool.acquire():
|
||||
pass
|
||||
conn.assert_has_calls([mock.call(self.connection_pool.destroyed_value)])
|
||||
conn.assert_has_calls(
|
||||
[mock.call(self.connection_pool.destroyed_value)])
|
||||
self.assertThat(self.connection_pool.queue, matchers.HasLength(1))
|
||||
self.assertEqual(0, non_expired_connection.connection.call_count)
|
||||
|
||||
@@ -68,13 +69,13 @@ class TestConnectionPool(utils.TestCase):
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
with mock.patch.object(
|
||||
_TestConnectionPool, "_create_connection", side_effect=TestException
|
||||
):
|
||||
with mock.patch.object(_TestConnectionPool, '_create_connection',
|
||||
side_effect=TestException):
|
||||
with testtools.ExpectedException(TestException):
|
||||
with self.connection_pool.acquire():
|
||||
pass
|
||||
self.assertThat(self.connection_pool.queue, matchers.HasLength(0))
|
||||
self.assertThat(self.connection_pool.queue,
|
||||
matchers.HasLength(0))
|
||||
self.assertEqual(0, self.connection_pool._acquired)
|
||||
|
||||
def test_connection_pool_limits_maximum_connections(self):
|
||||
@@ -97,8 +98,9 @@ class TestConnectionPool(utils.TestCase):
|
||||
|
||||
def test_connection_pool_maximum_connection_get_timeout(self):
|
||||
connection_pool = _TestConnectionPool(
|
||||
maxsize=1, unused_timeout=self.unused_timeout, conn_get_timeout=0
|
||||
)
|
||||
maxsize=1,
|
||||
unused_timeout=self.unused_timeout,
|
||||
conn_get_timeout=0)
|
||||
|
||||
def _acquire_connection():
|
||||
with connection_pool.acquire():
|
||||
@@ -107,7 +109,8 @@ class TestConnectionPool(utils.TestCase):
|
||||
# Make sure we've consumed the only available connection from the pool
|
||||
conn = connection_pool.get_nowait()
|
||||
|
||||
self.assertRaises(_memcache_pool.exception.QueueEmpty, _acquire_connection)
|
||||
self.assertRaises(_memcache_pool.exception.QueueEmpty,
|
||||
_acquire_connection)
|
||||
|
||||
# Put the connection back and ensure we can acquire the connection
|
||||
# after it is available.
|
||||
|
||||
@@ -18,68 +18,60 @@ from keystonemiddleware.tests.unit import utils
|
||||
|
||||
class MemcacheCryptPositiveTests(utils.BaseTestCase):
|
||||
def _setup_keys(self, strategy):
|
||||
return memcache_crypt.derive_keys("token", "secret", strategy)
|
||||
return memcache_crypt.derive_keys('token', 'secret', strategy)
|
||||
|
||||
def test_derive_keys(self):
|
||||
keys = self._setup_keys(b"strategy")
|
||||
self.assertEqual(len(keys["ENCRYPTION"]), len(keys["CACHE_KEY"]))
|
||||
self.assertEqual(len(keys["CACHE_KEY"]), len(keys["MAC"]))
|
||||
self.assertNotEqual(keys["ENCRYPTION"], keys["MAC"])
|
||||
self.assertIn("strategy", keys.keys())
|
||||
keys = self._setup_keys(b'strategy')
|
||||
self.assertEqual(len(keys['ENCRYPTION']),
|
||||
len(keys['CACHE_KEY']))
|
||||
self.assertEqual(len(keys['CACHE_KEY']),
|
||||
len(keys['MAC']))
|
||||
self.assertNotEqual(keys['ENCRYPTION'],
|
||||
keys['MAC'])
|
||||
self.assertIn('strategy', keys.keys())
|
||||
|
||||
def test_key_strategy_diff(self):
|
||||
k1 = self._setup_keys(b"MAC")
|
||||
k2 = self._setup_keys(b"ENCRYPT")
|
||||
k1 = self._setup_keys(b'MAC')
|
||||
k2 = self._setup_keys(b'ENCRYPT')
|
||||
self.assertNotEqual(k1, k2)
|
||||
|
||||
def test_sign_data(self):
|
||||
keys = self._setup_keys(b"MAC")
|
||||
sig = memcache_crypt.sign_data(keys["MAC"], b"data")
|
||||
keys = self._setup_keys(b'MAC')
|
||||
sig = memcache_crypt.sign_data(keys['MAC'], b'data')
|
||||
self.assertEqual(len(sig), memcache_crypt.DIGEST_LENGTH_B64)
|
||||
|
||||
def test_encryption(self):
|
||||
int2byte = struct.Struct(">B").pack
|
||||
keys = self._setup_keys(b"ENCRYPT")
|
||||
keys = self._setup_keys(b'ENCRYPT')
|
||||
# what you put in is what you get out
|
||||
for data in [b"data", b"1234567890123456", b"\x00\xff" * 13] + [
|
||||
int2byte(x % 256) * x for x in range(768)
|
||||
]:
|
||||
crypt = memcache_crypt.encrypt_data(keys["ENCRYPTION"], data)
|
||||
decrypt = memcache_crypt.decrypt_data(keys["ENCRYPTION"], crypt)
|
||||
for data in [b'data', b'1234567890123456', b'\x00\xFF' * 13
|
||||
] + [int2byte(x % 256) * x for x in range(768)]:
|
||||
crypt = memcache_crypt.encrypt_data(keys['ENCRYPTION'], data)
|
||||
decrypt = memcache_crypt.decrypt_data(keys['ENCRYPTION'], crypt)
|
||||
self.assertEqual(data, decrypt)
|
||||
self.assertRaises(
|
||||
memcache_crypt.DecryptError,
|
||||
memcache_crypt.decrypt_data,
|
||||
keys["ENCRYPTION"],
|
||||
crypt[:-1],
|
||||
)
|
||||
self.assertRaises(memcache_crypt.DecryptError,
|
||||
memcache_crypt.decrypt_data,
|
||||
keys['ENCRYPTION'], crypt[:-1])
|
||||
|
||||
def test_protect_wrappers(self):
|
||||
data = b"My Pretty Little Data"
|
||||
for strategy in [b"MAC", b"ENCRYPT"]:
|
||||
data = b'My Pretty Little Data'
|
||||
for strategy in [b'MAC', b'ENCRYPT']:
|
||||
keys = self._setup_keys(strategy)
|
||||
protected = memcache_crypt.protect_data(keys, data)
|
||||
self.assertNotEqual(protected, data)
|
||||
if strategy == b"ENCRYPT":
|
||||
if strategy == b'ENCRYPT':
|
||||
self.assertNotIn(data, protected)
|
||||
unprotected = memcache_crypt.unprotect_data(keys, protected)
|
||||
self.assertEqual(data, unprotected)
|
||||
self.assertRaises(
|
||||
memcache_crypt.InvalidMacError,
|
||||
memcache_crypt.unprotect_data,
|
||||
keys,
|
||||
protected[:-1],
|
||||
)
|
||||
self.assertRaises(memcache_crypt.InvalidMacError,
|
||||
memcache_crypt.unprotect_data,
|
||||
keys, protected[:-1])
|
||||
self.assertIsNone(memcache_crypt.unprotect_data(keys, None))
|
||||
|
||||
def test_no_cryptography(self):
|
||||
aes = memcache_crypt.ciphers
|
||||
memcache_crypt.ciphers = None
|
||||
self.assertRaises(
|
||||
memcache_crypt.CryptoUnavailableError,
|
||||
memcache_crypt.encrypt_data,
|
||||
"token",
|
||||
"secret",
|
||||
"data",
|
||||
)
|
||||
self.assertRaises(memcache_crypt.CryptoUnavailableError,
|
||||
memcache_crypt.encrypt_data, 'token', 'secret',
|
||||
'data')
|
||||
memcache_crypt.ciphers = aes
|
||||
@@ -21,67 +21,76 @@ from keystonemiddleware.tests.unit import utils
|
||||
|
||||
|
||||
class RequestObjectTests(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(RequestObjectTests, self).setUp()
|
||||
self.request = _request._AuthTokenRequest.blank("/")
|
||||
self.request = _request._AuthTokenRequest.blank('/')
|
||||
|
||||
def test_setting_user_token_valid(self):
|
||||
self.assertNotIn("X-Identity-Status", self.request.headers)
|
||||
self.assertNotIn('X-Identity-Status', self.request.headers)
|
||||
|
||||
self.request.user_token_valid = True
|
||||
self.assertEqual("Confirmed", self.request.headers["X-Identity-Status"])
|
||||
self.assertEqual('Confirmed',
|
||||
self.request.headers['X-Identity-Status'])
|
||||
self.assertTrue(self.request.user_token_valid)
|
||||
|
||||
self.request.user_token_valid = False
|
||||
self.assertEqual("Invalid", self.request.headers["X-Identity-Status"])
|
||||
self.assertEqual('Invalid',
|
||||
self.request.headers['X-Identity-Status'])
|
||||
self.assertFalse(self.request.user_token_valid)
|
||||
|
||||
def test_setting_service_token_valid(self):
|
||||
self.assertNotIn("X-Service-Identity-Status", self.request.headers)
|
||||
self.assertNotIn('X-Service-Identity-Status', self.request.headers)
|
||||
|
||||
self.request.service_token_valid = True
|
||||
self.assertEqual("Confirmed", self.request.headers["X-Service-Identity-Status"])
|
||||
self.assertEqual('Confirmed',
|
||||
self.request.headers['X-Service-Identity-Status'])
|
||||
self.assertTrue(self.request.service_token_valid)
|
||||
|
||||
self.request.service_token_valid = False
|
||||
self.assertEqual("Invalid", self.request.headers["X-Service-Identity-Status"])
|
||||
self.assertEqual('Invalid',
|
||||
self.request.headers['X-Service-Identity-Status'])
|
||||
self.assertFalse(self.request.service_token_valid)
|
||||
|
||||
def test_removing_headers(self):
|
||||
GOOD = ("X-Auth-Token", "unknownstring", uuid.uuid4().hex)
|
||||
GOOD = ('X-Auth-Token',
|
||||
'unknownstring',
|
||||
uuid.uuid4().hex)
|
||||
|
||||
BAD = (
|
||||
"X-Domain-Id",
|
||||
"X-Domain-Name",
|
||||
"X-Project-Id",
|
||||
"X-Project-Name",
|
||||
"X-Project-Domain-Id",
|
||||
"X-Project-Domain-Name",
|
||||
"X-User-Id",
|
||||
"X-User-Name",
|
||||
"X-User-Domain-Id",
|
||||
"X-User-Domain-Name",
|
||||
"X-Roles",
|
||||
"X-Identity-Status",
|
||||
"X-Service-Domain-Id",
|
||||
"X-Service-Domain-Name",
|
||||
"X-Service-Project-Id",
|
||||
"X-Service-Project-Name",
|
||||
"X-Service-Project-Domain-Id",
|
||||
"X-Service-Project-Domain-Name",
|
||||
"X-Service-User-Id",
|
||||
"X-Service-User-Name",
|
||||
"X-Service-User-Domain-Id",
|
||||
"X-Service-User-Domain-Name",
|
||||
"X-Service-Roles",
|
||||
"X-Service-Identity-Status",
|
||||
"X-Service-Catalog",
|
||||
"X-Role",
|
||||
"X-User",
|
||||
"X-Tenant-Id",
|
||||
"X-Tenant-Name",
|
||||
"X-Tenant",
|
||||
)
|
||||
BAD = ('X-Domain-Id',
|
||||
'X-Domain-Name',
|
||||
'X-Project-Id',
|
||||
'X-Project-Name',
|
||||
'X-Project-Domain-Id',
|
||||
'X-Project-Domain-Name',
|
||||
'X-User-Id',
|
||||
'X-User-Name',
|
||||
'X-User-Domain-Id',
|
||||
'X-User-Domain-Name',
|
||||
'X-Roles',
|
||||
'X-Identity-Status',
|
||||
|
||||
'X-Service-Domain-Id',
|
||||
'X-Service-Domain-Name',
|
||||
'X-Service-Project-Id',
|
||||
'X-Service-Project-Name',
|
||||
'X-Service-Project-Domain-Id',
|
||||
'X-Service-Project-Domain-Name',
|
||||
'X-Service-User-Id',
|
||||
'X-Service-User-Name',
|
||||
'X-Service-User-Domain-Id',
|
||||
'X-Service-User-Domain-Name',
|
||||
'X-Service-Roles',
|
||||
'X-Service-Identity-Status',
|
||||
|
||||
'X-Service-Catalog',
|
||||
|
||||
'X-Role',
|
||||
'X-User',
|
||||
'X-Tenant-Id',
|
||||
'X-Tenant-Name',
|
||||
'X-Tenant',
|
||||
)
|
||||
|
||||
header_vals = {}
|
||||
|
||||
@@ -99,38 +108,31 @@ class RequestObjectTests(utils.TestCase):
|
||||
self.assertEqual(header_vals[header], self.request.headers[header])
|
||||
|
||||
def _test_v3_headers(self, token, prefix):
|
||||
self.assertEqual(
|
||||
token.domain_id, self.request.headers["X%s-Domain-Id" % prefix]
|
||||
)
|
||||
self.assertEqual(
|
||||
token.domain_name, self.request.headers["X%s-Domain-Name" % prefix]
|
||||
)
|
||||
self.assertEqual(
|
||||
token.project_id, self.request.headers["X%s-Project-Id" % prefix]
|
||||
)
|
||||
self.assertEqual(
|
||||
token.project_name, self.request.headers["X%s-Project-Name" % prefix]
|
||||
)
|
||||
self.assertEqual(token.domain_id,
|
||||
self.request.headers['X%s-Domain-Id' % prefix])
|
||||
self.assertEqual(token.domain_name,
|
||||
self.request.headers['X%s-Domain-Name' % prefix])
|
||||
self.assertEqual(token.project_id,
|
||||
self.request.headers['X%s-Project-Id' % prefix])
|
||||
self.assertEqual(token.project_name,
|
||||
self.request.headers['X%s-Project-Name' % prefix])
|
||||
self.assertEqual(
|
||||
token.project_domain_id,
|
||||
self.request.headers["X%s-Project-Domain-Id" % prefix],
|
||||
)
|
||||
self.request.headers['X%s-Project-Domain-Id' % prefix])
|
||||
self.assertEqual(
|
||||
token.project_domain_name,
|
||||
self.request.headers["X%s-Project-Domain-Name" % prefix],
|
||||
)
|
||||
self.request.headers['X%s-Project-Domain-Name' % prefix])
|
||||
|
||||
self.assertEqual(token.user_id, self.request.headers["X%s-User-Id" % prefix])
|
||||
self.assertEqual(token.user_id,
|
||||
self.request.headers['X%s-User-Id' % prefix])
|
||||
self.assertEqual(token.user_name,
|
||||
self.request.headers['X%s-User-Name' % prefix])
|
||||
self.assertEqual(
|
||||
token.user_name, self.request.headers["X%s-User-Name" % prefix]
|
||||
)
|
||||
self.assertEqual(
|
||||
token.user_domain_id, self.request.headers["X%s-User-Domain-Id" % prefix]
|
||||
)
|
||||
token.user_domain_id,
|
||||
self.request.headers['X%s-User-Domain-Id' % prefix])
|
||||
self.assertEqual(
|
||||
token.user_domain_name,
|
||||
self.request.headers["X%s-User-Domain-Name" % prefix],
|
||||
)
|
||||
self.request.headers['X%s-User-Domain-Name' % prefix])
|
||||
|
||||
def test_project_scoped_user_headers(self):
|
||||
token = fixture.V3Token()
|
||||
@@ -140,7 +142,7 @@ class RequestObjectTests(utils.TestCase):
|
||||
auth_ref = access.create(auth_token=token_id, body=token)
|
||||
self.request.set_user_headers(auth_ref)
|
||||
|
||||
self._test_v3_headers(token, "")
|
||||
self._test_v3_headers(token, '')
|
||||
|
||||
def test_project_scoped_service_headers(self):
|
||||
token = fixture.V3Token()
|
||||
@@ -150,17 +152,17 @@ class RequestObjectTests(utils.TestCase):
|
||||
auth_ref = access.create(auth_token=token_id, body=token)
|
||||
self.request.set_service_headers(auth_ref)
|
||||
|
||||
self._test_v3_headers(token, "-Service")
|
||||
self._test_v3_headers(token, '-Service')
|
||||
|
||||
def test_auth_type(self):
|
||||
self.assertIsNone(self.request.auth_type)
|
||||
self.request.environ["AUTH_TYPE"] = "NeGoTiatE"
|
||||
self.assertEqual("negotiate", self.request.auth_type)
|
||||
self.request.environ['AUTH_TYPE'] = 'NeGoTiatE'
|
||||
self.assertEqual('negotiate', self.request.auth_type)
|
||||
|
||||
def test_user_token(self):
|
||||
token = uuid.uuid4().hex
|
||||
self.assertIsNone(self.request.user_token)
|
||||
self.request.headers["X-Auth-Token"] = token
|
||||
self.request.headers['X-Auth-Token'] = token
|
||||
self.assertEqual(token, self.request.user_token)
|
||||
|
||||
def test_storage_token(self):
|
||||
@@ -168,58 +170,57 @@ class RequestObjectTests(utils.TestCase):
|
||||
user_token = uuid.uuid4().hex
|
||||
|
||||
self.assertIsNone(self.request.user_token)
|
||||
self.request.headers["X-Storage-Token"] = storage_token
|
||||
self.request.headers['X-Storage-Token'] = storage_token
|
||||
self.assertEqual(storage_token, self.request.user_token)
|
||||
self.request.headers["X-Auth-Token"] = user_token
|
||||
self.request.headers['X-Auth-Token'] = user_token
|
||||
self.assertEqual(user_token, self.request.user_token)
|
||||
|
||||
def test_service_token(self):
|
||||
token = uuid.uuid4().hex
|
||||
self.assertIsNone(self.request.service_token)
|
||||
self.request.headers["X-Service-Token"] = token
|
||||
self.request.headers['X-Service-Token'] = token
|
||||
self.assertEqual(token, self.request.service_token)
|
||||
|
||||
def test_token_auth(self):
|
||||
plugin = object()
|
||||
|
||||
self.assertNotIn("keystone.token_auth", self.request.environ)
|
||||
self.assertNotIn('keystone.token_auth', self.request.environ)
|
||||
self.request.token_auth = plugin
|
||||
self.assertIs(plugin, self.request.environ["keystone.token_auth"])
|
||||
self.assertIs(plugin, self.request.environ['keystone.token_auth'])
|
||||
self.assertIs(plugin, self.request.token_auth)
|
||||
|
||||
def test_token_info(self):
|
||||
info = fixture.V3Token()
|
||||
|
||||
self.assertNotIn("keystone.token_info", self.request.environ)
|
||||
self.assertNotIn('keystone.token_info', self.request.environ)
|
||||
self.request.token_info = info
|
||||
self.assertIs(info, self.request.environ["keystone.token_info"])
|
||||
self.assertIs(info, self.request.environ['keystone.token_info'])
|
||||
self.assertIs(info, self.request.token_info)
|
||||
|
||||
def test_token_without_catalog(self):
|
||||
token = fixture.V3Token()
|
||||
auth_ref = access.create(body=token)
|
||||
self.request.set_service_catalog_headers(auth_ref)
|
||||
self.assertNotIn("X-Service-Catalog", self.request.headers)
|
||||
self.assertNotIn('X-Service-Catalog', self.request.headers)
|
||||
|
||||
|
||||
class CatalogConversionTests(utils.TestCase):
|
||||
PUBLIC_URL = "http://server:5000/v2.0"
|
||||
ADMIN_URL = "http://admin:35357/v2.0"
|
||||
INTERNAL_URL = "http://internal:5000/v2.0"
|
||||
|
||||
REGION_ONE = "RegionOne"
|
||||
REGION_TWO = "RegionTwo"
|
||||
REGION_THREE = "RegionThree"
|
||||
PUBLIC_URL = 'http://server:5000/v3'
|
||||
ADMIN_URL = 'http://admin:35357/v3'
|
||||
INTERNAL_URL = 'http://internal:5000/v3'
|
||||
|
||||
REGION_ONE = 'RegionOne'
|
||||
REGION_TWO = 'RegionTwo'
|
||||
REGION_THREE = 'RegionThree'
|
||||
|
||||
def test_basic_convert(self):
|
||||
token = fixture.V3Token()
|
||||
s = token.add_service(type="identity")
|
||||
s.add_standard_endpoints(
|
||||
public=self.PUBLIC_URL,
|
||||
admin=self.ADMIN_URL,
|
||||
internal=self.INTERNAL_URL,
|
||||
region=self.REGION_ONE,
|
||||
)
|
||||
s = token.add_service(type='identity')
|
||||
s.add_standard_endpoints(public=self.PUBLIC_URL,
|
||||
admin=self.ADMIN_URL,
|
||||
internal=self.INTERNAL_URL,
|
||||
region=self.REGION_ONE)
|
||||
|
||||
auth_ref = access.create(body=token)
|
||||
catalog_data = auth_ref.service_catalog.catalog
|
||||
@@ -227,23 +228,23 @@ class CatalogConversionTests(utils.TestCase):
|
||||
|
||||
self.assertEqual(1, len(catalog))
|
||||
service = catalog[0]
|
||||
self.assertEqual(1, len(service["endpoints"]))
|
||||
endpoints = service["endpoints"][0]
|
||||
self.assertEqual(1, len(service['endpoints']))
|
||||
endpoints = service['endpoints'][0]
|
||||
|
||||
self.assertEqual("identity", service["type"])
|
||||
self.assertEqual('identity', service['type'])
|
||||
self.assertEqual(4, len(endpoints))
|
||||
self.assertEqual(self.PUBLIC_URL, endpoints["publicURL"])
|
||||
self.assertEqual(self.ADMIN_URL, endpoints["adminURL"])
|
||||
self.assertEqual(self.INTERNAL_URL, endpoints["internalURL"])
|
||||
self.assertEqual(self.REGION_ONE, endpoints["region"])
|
||||
self.assertEqual(self.PUBLIC_URL, endpoints['publicURL'])
|
||||
self.assertEqual(self.ADMIN_URL, endpoints['adminURL'])
|
||||
self.assertEqual(self.INTERNAL_URL, endpoints['internalURL'])
|
||||
self.assertEqual(self.REGION_ONE, endpoints['region'])
|
||||
|
||||
def test_multi_region(self):
|
||||
token = fixture.V3Token()
|
||||
s = token.add_service(type="identity")
|
||||
s = token.add_service(type='identity')
|
||||
|
||||
s.add_endpoint("internal", self.INTERNAL_URL, region=self.REGION_ONE)
|
||||
s.add_endpoint("public", self.PUBLIC_URL, region=self.REGION_TWO)
|
||||
s.add_endpoint("admin", self.ADMIN_URL, region=self.REGION_THREE)
|
||||
s.add_endpoint('internal', self.INTERNAL_URL, region=self.REGION_ONE)
|
||||
s.add_endpoint('public', self.PUBLIC_URL, region=self.REGION_TWO)
|
||||
s.add_endpoint('admin', self.ADMIN_URL, region=self.REGION_THREE)
|
||||
|
||||
auth_ref = access.create(body=token)
|
||||
catalog_data = auth_ref.service_catalog.catalog
|
||||
@@ -253,13 +254,14 @@ class CatalogConversionTests(utils.TestCase):
|
||||
service = catalog[0]
|
||||
|
||||
# the 3 regions will come through as 3 separate endpoints
|
||||
expected = [
|
||||
{"internalURL": self.INTERNAL_URL, "region": self.REGION_ONE},
|
||||
{"publicURL": self.PUBLIC_URL, "region": self.REGION_TWO},
|
||||
{"adminURL": self.ADMIN_URL, "region": self.REGION_THREE},
|
||||
]
|
||||
expected = [{'internalURL': self.INTERNAL_URL,
|
||||
'region': self.REGION_ONE},
|
||||
{'publicURL': self.PUBLIC_URL,
|
||||
'region': self.REGION_TWO},
|
||||
{'adminURL': self.ADMIN_URL,
|
||||
'region': self.REGION_THREE}]
|
||||
|
||||
self.assertEqual("identity", service["type"])
|
||||
self.assertEqual(3, len(service["endpoints"]))
|
||||
self.assertEqual('identity', service['type'])
|
||||
self.assertEqual(3, len(service['endpoints']))
|
||||
for e in expected:
|
||||
self.assertIn(e, expected)
|
||||
@@ -19,21 +19,27 @@ from keystonemiddleware.auth_token import _base
|
||||
from keystonemiddleware.tests.unit.auth_token import base
|
||||
|
||||
# NOTE(jamielennox): just some sample values that we can use for testing
|
||||
BASE_URI = "https://keystone.example.com:1234"
|
||||
AUTH_URL = "https://keystone.auth.com:1234"
|
||||
BASE_URI = 'https://keystone.example.com:1234'
|
||||
AUTH_URL = 'https://keystone.auth.com:1234'
|
||||
|
||||
|
||||
class BaseUserPluginTests(object):
|
||||
def configure_middleware(self, auth_type, **kwargs):
|
||||
|
||||
def configure_middleware(self,
|
||||
auth_type,
|
||||
**kwargs):
|
||||
opts = loading.get_auth_plugin_conf_options(auth_type)
|
||||
self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP)
|
||||
|
||||
# Since these tests cfg.config() themselves rather than waiting for
|
||||
# auth_token to do it on __init__ we need to register the base auth
|
||||
# options (e.g., auth_plugin)
|
||||
loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP)
|
||||
loading.register_auth_conf_options(self.cfg.conf,
|
||||
group=_base.AUTHTOKEN_GROUP)
|
||||
|
||||
self.cfg.config(group=_base.AUTHTOKEN_GROUP, auth_type=auth_type, **kwargs)
|
||||
self.cfg.config(group=_base.AUTHTOKEN_GROUP,
|
||||
auth_type=auth_type,
|
||||
**kwargs)
|
||||
|
||||
def assertTokenDataEqual(self, token_id, token, token_data):
|
||||
self.assertEqual(token_id, token_data.auth_token)
|
||||
@@ -46,15 +52,15 @@ class BaseUserPluginTests(object):
|
||||
self.assertEqual(self.get_role_names(token), token_data.role_names)
|
||||
|
||||
def get_plugin(self, token_id, service_token_id=None):
|
||||
headers = {"X-Auth-Token": token_id}
|
||||
headers = {'X-Auth-Token': token_id}
|
||||
|
||||
if service_token_id:
|
||||
headers["X-Service-Token"] = service_token_id
|
||||
headers['X-Service-Token'] = service_token_id
|
||||
|
||||
m = self.create_simple_middleware()
|
||||
|
||||
resp = self.call(m, headers=headers)
|
||||
return resp.request.environ["keystone.token_auth"]
|
||||
return resp.request.environ['keystone.token_auth']
|
||||
|
||||
def test_user_information(self):
|
||||
token_id, token = self.get_token()
|
||||
@@ -75,21 +81,22 @@ class BaseUserPluginTests(object):
|
||||
|
||||
|
||||
class V3UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(V3UserPluginTests, self).setUp()
|
||||
|
||||
self.service_token_id = uuid.uuid4().hex
|
||||
self.service_token = fixture.V3Token()
|
||||
s = self.service_token.add_service("identity", name="keystone")
|
||||
s.add_standard_endpoints(public=BASE_URI, admin=BASE_URI, internal=BASE_URI)
|
||||
s = self.service_token.add_service('identity', name='keystone')
|
||||
s.add_standard_endpoints(public=BASE_URI,
|
||||
admin=BASE_URI,
|
||||
internal=BASE_URI)
|
||||
|
||||
self.configure_middleware(
|
||||
auth_type="v3password",
|
||||
auth_url="%s/v3/" % AUTH_URL,
|
||||
user_id=self.service_token.user_id,
|
||||
password=uuid.uuid4().hex,
|
||||
project_id=self.service_token.project_id,
|
||||
)
|
||||
self.configure_middleware(auth_type='v3password',
|
||||
auth_url='%s/v3/' % AUTH_URL,
|
||||
user_id=self.service_token.user_id,
|
||||
password=uuid.uuid4().hex,
|
||||
project_id=self.service_token.project_id)
|
||||
|
||||
auth_discovery = fixture.DiscoveryList(href=AUTH_URL)
|
||||
self.requests_mock.get(AUTH_URL, json=auth_discovery)
|
||||
@@ -98,13 +105,12 @@ class V3UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase):
|
||||
self.requests_mock.get(BASE_URI, json=base_discovery)
|
||||
|
||||
self.requests_mock.post(
|
||||
"%s/v3/auth/tokens" % AUTH_URL,
|
||||
headers={"X-Subject-Token": self.service_token_id},
|
||||
json=self.service_token,
|
||||
)
|
||||
'%s/v3/auth/tokens' % AUTH_URL,
|
||||
headers={'X-Subject-Token': self.service_token_id},
|
||||
json=self.service_token)
|
||||
|
||||
def get_role_names(self, token):
|
||||
return [x["name"] for x in token["token"].get("roles", [])]
|
||||
return [x['name'] for x in token['token'].get('roles', [])]
|
||||
|
||||
def get_token(self, project=True, service=False):
|
||||
token_id = uuid.uuid4().hex
|
||||
@@ -113,25 +119,23 @@ class V3UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase):
|
||||
token.set_project_scope()
|
||||
token.add_role()
|
||||
if service:
|
||||
token.add_role("service")
|
||||
token.add_role('service')
|
||||
|
||||
request_headers = {
|
||||
"X-Auth-Token": self.service_token_id,
|
||||
"X-Subject-Token": token_id,
|
||||
}
|
||||
headers = {"X-Subject-Token": token_id}
|
||||
request_headers = {'X-Auth-Token': self.service_token_id,
|
||||
'X-Subject-Token': token_id}
|
||||
headers = {'X-Subject-Token': token_id}
|
||||
|
||||
self.requests_mock.get(
|
||||
"%s/v3/auth/tokens" % BASE_URI,
|
||||
request_headers=request_headers,
|
||||
headers=headers,
|
||||
json=token,
|
||||
)
|
||||
self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI,
|
||||
request_headers=request_headers,
|
||||
headers=headers,
|
||||
json=token)
|
||||
|
||||
return token_id, token
|
||||
|
||||
def assertTokenDataEqual(self, token_id, token, token_data):
|
||||
super(V3UserPluginTests, self).assertTokenDataEqual(token_id, token, token_data)
|
||||
super(V3UserPluginTests, self).assertTokenDataEqual(token_id,
|
||||
token,
|
||||
token_data)
|
||||
|
||||
self.assertEqual(token.user_domain_id, token_data.user_domain_id)
|
||||
self.assertEqual(token.project_id, token_data.project_id)
|
||||
|
||||
@@ -30,7 +30,7 @@ import testresources
|
||||
|
||||
|
||||
TESTDIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOTDIR = os.path.normpath(os.path.join(TESTDIR, "..", "..", ".."))
|
||||
ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..', '..'))
|
||||
|
||||
|
||||
class Examples(fixtures.Fixture):
|
||||
@@ -51,51 +51,51 @@ class Examples(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(Examples, self).setUp()
|
||||
|
||||
self.KERBEROS_BIND = "USER@REALM"
|
||||
self.SERVICE_KERBEROS_BIND = "SERVICE_USER@SERVICE_REALM"
|
||||
self.KERBEROS_BIND = 'USER@REALM'
|
||||
self.SERVICE_KERBEROS_BIND = 'SERVICE_USER@SERVICE_REALM'
|
||||
|
||||
self.UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d"
|
||||
self.UUID_TOKEN_NO_SERVICE_CATALOG = "8286720fbe4941e69fa8241723bb02df"
|
||||
self.UUID_TOKEN_UNSCOPED = "731f903721c14827be7b2dc912af7776"
|
||||
self.UUID_TOKEN_BIND = "3fc54048ad64405c98225ce0897af7c5"
|
||||
self.UUID_TOKEN_UNKNOWN_BIND = "8885fdf4d42e4fb9879e6379fa1eaf48"
|
||||
self.v3_UUID_TOKEN_DEFAULT = "5603457654b346fdbb93437bfe76f2f1"
|
||||
self.v3_UUID_TOKEN_UNSCOPED = "d34835fdaec447e695a0a024d84f8d79"
|
||||
self.v3_UUID_TOKEN_DOMAIN_SCOPED = "e8a7b63aaa4449f38f0c5c05c3581792"
|
||||
self.v3_UUID_TOKEN_BIND = "2f61f73e1c854cbb9534c487f9bd63c2"
|
||||
self.v3_UUID_TOKEN_UNKNOWN_BIND = "7ed9781b62cd4880b8d8c6788ab1d1e2"
|
||||
self.v3_SYSTEM_SCOPED_TOKEN = "9ca6e88364b6418a88ffc02e6a24afd8"
|
||||
self.UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df'
|
||||
self.UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776'
|
||||
self.UUID_TOKEN_BIND = '3fc54048ad64405c98225ce0897af7c5'
|
||||
self.UUID_TOKEN_UNKNOWN_BIND = '8885fdf4d42e4fb9879e6379fa1eaf48'
|
||||
self.v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1'
|
||||
self.v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79'
|
||||
self.v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792'
|
||||
self.v3_UUID_TOKEN_BIND = '2f61f73e1c854cbb9534c487f9bd63c2'
|
||||
self.v3_UUID_TOKEN_UNKNOWN_BIND = '7ed9781b62cd4880b8d8c6788ab1d1e2'
|
||||
self.v3_SYSTEM_SCOPED_TOKEN = '9ca6e88364b6418a88ffc02e6a24afd8'
|
||||
|
||||
self.UUID_SERVICE_TOKEN_DEFAULT = "fe4c0710ec2f492748596c1b53ab124"
|
||||
self.UUID_SERVICE_TOKEN_BIND = "5e43439613d34a13a7e03b2762bd08ab"
|
||||
self.v3_UUID_SERVICE_TOKEN_DEFAULT = "g431071bbc2f492748596c1b53cb229"
|
||||
self.v3_UUID_SERVICE_TOKEN_BIND = "be705e4426d0449a89e35ae21c380a05"
|
||||
self.UUID_SERVICE_TOKEN_DEFAULT = 'fe4c0710ec2f492748596c1b53ab124'
|
||||
self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab'
|
||||
self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229'
|
||||
self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
|
||||
self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex
|
||||
|
||||
self.v3_APP_CRED_TOKEN = "6f506fa9641448bbaecbd12dd30678a9"
|
||||
self.v3_APP_CRED_ACCESS_RULES = "c417747898c44629b08791f2579e40a5"
|
||||
self.v3_APP_CRED_EMPTY_ACCESS_RULES = "c75905c307f04fdd9979126582d7aae"
|
||||
self.v3_APP_CRED_MATCHING_RULES = "ad49decc7106489d95ca9ed874b6cb66"
|
||||
self.v3_APP_CRED_TOKEN = '6f506fa9641448bbaecbd12dd30678a9'
|
||||
self.v3_APP_CRED_ACCESS_RULES = 'c417747898c44629b08791f2579e40a5'
|
||||
self.v3_APP_CRED_EMPTY_ACCESS_RULES = 'c75905c307f04fdd9979126582d7aae'
|
||||
self.v3_APP_CRED_MATCHING_RULES = 'ad49decc7106489d95ca9ed874b6cb66'
|
||||
|
||||
self.v3_OAUTH2_CREDENTIAL = uuid.uuid4().hex
|
||||
self.V3_OAUTH2_MTLS_CERTIFICATE = self._create_pem_certificate(
|
||||
self._create_dn(
|
||||
country_name="jp",
|
||||
state_or_province_name="kanagawa",
|
||||
locality_name="kawasaki",
|
||||
organization_name="fujitsu",
|
||||
organizational_unit_name="test",
|
||||
common_name="root",
|
||||
country_name='jp',
|
||||
state_or_province_name='kanagawa',
|
||||
locality_name='kawasaki',
|
||||
organization_name='fujitsu',
|
||||
organizational_unit_name='test',
|
||||
common_name='root'
|
||||
)
|
||||
)
|
||||
self.V3_OAUTH2_MTLS_CERTIFICATE_DIFF = self._create_pem_certificate(
|
||||
self._create_dn(
|
||||
country_name="jp",
|
||||
state_or_province_name="kanagawa",
|
||||
locality_name="kawasaki",
|
||||
organization_name="fujitsu",
|
||||
organizational_unit_name="test",
|
||||
common_name="diff",
|
||||
country_name='jp',
|
||||
state_or_province_name='kanagawa',
|
||||
locality_name='kawasaki',
|
||||
organization_name='fujitsu',
|
||||
organizational_unit_name='test',
|
||||
common_name='diff'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -103,286 +103,188 @@ class Examples(fixtures.Fixture):
|
||||
self.TOKEN_RESPONSES = {}
|
||||
|
||||
# basic values
|
||||
PROJECT_ID = "tenant_id1"
|
||||
PROJECT_NAME = "tenant_name1"
|
||||
USER_ID = "user_id1"
|
||||
USER_NAME = "user_name1"
|
||||
DOMAIN_ID = "domain_id1"
|
||||
DOMAIN_NAME = "domain_name1"
|
||||
ROLE_NAME1 = "role1"
|
||||
ROLE_NAME2 = "role2"
|
||||
PROJECT_ID = 'tenant_id1'
|
||||
PROJECT_NAME = 'tenant_name1'
|
||||
USER_ID = 'user_id1'
|
||||
USER_NAME = 'user_name1'
|
||||
DOMAIN_ID = 'domain_id1'
|
||||
DOMAIN_NAME = 'domain_name1'
|
||||
ROLE_NAME1 = 'role1'
|
||||
ROLE_NAME2 = 'role2'
|
||||
|
||||
SERVICE_PROJECT_ID = "service_project_id1"
|
||||
SERVICE_PROJECT_NAME = "service_project_name1"
|
||||
SERVICE_USER_ID = "service_user_id1"
|
||||
SERVICE_USER_NAME = "service_user_name1"
|
||||
SERVICE_DOMAIN_ID = "service_domain_id1"
|
||||
SERVICE_DOMAIN_NAME = "service_domain_name1"
|
||||
SERVICE_ROLE_NAME1 = "service"
|
||||
SERVICE_ROLE_NAME2 = "service_role2"
|
||||
SERVICE_PROJECT_ID = 'service_project_id1'
|
||||
SERVICE_PROJECT_NAME = 'service_project_name1'
|
||||
SERVICE_USER_ID = 'service_user_id1'
|
||||
SERVICE_USER_NAME = 'service_user_name1'
|
||||
SERVICE_DOMAIN_ID = 'service_domain_id1'
|
||||
SERVICE_DOMAIN_NAME = 'service_domain_name1'
|
||||
SERVICE_ROLE_NAME1 = 'service'
|
||||
SERVICE_ROLE_NAME2 = 'service_role2'
|
||||
|
||||
APP_CRED_ID = "app_cred_id1"
|
||||
APP_CRED_ID = 'app_cred_id1'
|
||||
|
||||
self.SERVICE_TYPE = "identity"
|
||||
self.UNVERSIONED_SERVICE_URL = "https://keystone.example.com:1234/"
|
||||
self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + "v2.0"
|
||||
|
||||
# Generated V2 Tokens
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_TOKEN_DEFAULT,
|
||||
tenant_id=PROJECT_ID,
|
||||
tenant_name=PROJECT_NAME,
|
||||
user_id=USER_ID,
|
||||
user_name=USER_NAME,
|
||||
)
|
||||
token.add_role(name=ROLE_NAME1)
|
||||
token.add_role(name=ROLE_NAME2)
|
||||
svc = token.add_service(self.SERVICE_TYPE)
|
||||
svc.add_endpoint(public=self.SERVICE_URL)
|
||||
self.TOKEN_RESPONSES[self.UUID_TOKEN_DEFAULT] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_TOKEN_UNSCOPED, user_id=USER_ID, user_name=USER_NAME
|
||||
)
|
||||
self.TOKEN_RESPONSES[self.UUID_TOKEN_UNSCOPED] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id="valid-token",
|
||||
tenant_id=PROJECT_ID,
|
||||
tenant_name=PROJECT_NAME,
|
||||
user_id=USER_ID,
|
||||
user_name=USER_NAME,
|
||||
)
|
||||
token.add_role(ROLE_NAME1)
|
||||
token.add_role(ROLE_NAME2)
|
||||
self.TOKEN_RESPONSES[self.UUID_TOKEN_NO_SERVICE_CATALOG] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_TOKEN_BIND,
|
||||
tenant_id=PROJECT_ID,
|
||||
tenant_name=PROJECT_NAME,
|
||||
user_id=USER_ID,
|
||||
user_name=USER_NAME,
|
||||
)
|
||||
token.add_role(ROLE_NAME1)
|
||||
token.add_role(ROLE_NAME2)
|
||||
token["access"]["token"]["bind"] = {"kerberos": self.KERBEROS_BIND}
|
||||
self.TOKEN_RESPONSES[self.UUID_TOKEN_BIND] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_SERVICE_TOKEN_BIND,
|
||||
tenant_id=SERVICE_PROJECT_ID,
|
||||
tenant_name=SERVICE_PROJECT_NAME,
|
||||
user_id=SERVICE_USER_ID,
|
||||
user_name=SERVICE_USER_NAME,
|
||||
)
|
||||
token.add_role(SERVICE_ROLE_NAME1)
|
||||
token.add_role(SERVICE_ROLE_NAME2)
|
||||
token["access"]["token"]["bind"] = {"kerberos": self.SERVICE_KERBEROS_BIND}
|
||||
self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_BIND] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_TOKEN_UNKNOWN_BIND,
|
||||
tenant_id=PROJECT_ID,
|
||||
tenant_name=PROJECT_NAME,
|
||||
user_id=USER_ID,
|
||||
user_name=USER_NAME,
|
||||
)
|
||||
token.add_role(ROLE_NAME1)
|
||||
token.add_role(ROLE_NAME2)
|
||||
token["access"]["token"]["bind"] = {"FOO": "BAR"}
|
||||
self.TOKEN_RESPONSES[self.UUID_TOKEN_UNKNOWN_BIND] = token
|
||||
|
||||
token = fixture.V2Token(
|
||||
token_id=self.UUID_SERVICE_TOKEN_DEFAULT,
|
||||
tenant_id=SERVICE_PROJECT_ID,
|
||||
tenant_name=SERVICE_PROJECT_NAME,
|
||||
user_id=SERVICE_USER_ID,
|
||||
user_name=SERVICE_USER_NAME,
|
||||
)
|
||||
token.add_role(name=SERVICE_ROLE_NAME1)
|
||||
token.add_role(name=SERVICE_ROLE_NAME2)
|
||||
svc = token.add_service(self.SERVICE_TYPE)
|
||||
svc.add_endpoint(public=self.SERVICE_URL)
|
||||
self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_DEFAULT] = token
|
||||
self.SERVICE_TYPE = 'identity'
|
||||
self.UNVERSIONED_SERVICE_URL = 'https://keystone.example.com:1234/'
|
||||
self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + 'v3'
|
||||
|
||||
# Generated V3 Tokens
|
||||
|
||||
token = fixture.V3Token(
|
||||
user_id=USER_ID,
|
||||
user_name=USER_NAME,
|
||||
user_domain_id=DOMAIN_ID,
|
||||
user_domain_name=DOMAIN_NAME,
|
||||
project_id=PROJECT_ID,
|
||||
project_name=PROJECT_NAME,
|
||||
| ||||