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:
Stephen Finucane committed 2026-09-18 18:43:02 +01:00
1 parent 18c23a61d9
commit ffd622be16
49 files changed
+4790 -5699

No files matched your search

-25
View File
@@ -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
View File
@@ -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
View File
@@ -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
+20 -23
View File
@@ -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
+53 -63
View File
@@ -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
+82 -99
View File
@@ -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
+11 -10
View File
@@ -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)
+189 -224
View File
@@ -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
+1 -1
View File
@@ -10,4 +10,4 @@
# License for the specific language governing permissions and limitations
# under the License.
AUTHTOKEN_GROUP = "keystone_authtoken"
AUTHTOKEN_GROUP = 'keystone_authtoken'
+39 -66
View File
@@ -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
+38 -53
View File
@@ -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'])
+171 -217
View File
@@ -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))]
+46 -43
View File
@@ -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:
+8 -10
View File
@@ -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
+6 -5
View File
@@ -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
+22 -42
View File
@@ -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)
+1 -1
View File
@@ -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
+37 -41
View File
@@ -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):
+23 -26
View File
@@ -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)
+3 -1
View File
@@ -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
+14 -15
View File
@@ -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)
+206 -293
View File
@@ -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,