Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-keystonemiddleware for
openSUSE:Factory checked in at 2026-03-10 17:56:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-keystonemiddleware (Old)
and /work/SRC/openSUSE:Factory/.python-keystonemiddleware.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-keystonemiddleware"
Tue Mar 10 17:56:22 2026 rev:22 rq:1337889 version:12.0.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-keystonemiddleware/python-keystonemiddleware.changes
2026-01-19 18:42:36.393233683 +0100
+++
/work/SRC/openSUSE:Factory/.python-keystonemiddleware.new.8177/python-keystonemiddleware.changes
2026-03-10 17:59:35.303091450 +0100
@@ -1,0 +2,20 @@
+Tue Mar 10 07:55:13 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 12.0.0:
+ * Remove ec2\_token and s3\_token middleware
+ * Ignore B105 error
+ * Fix privilege escalation via spoofed identity headers
+ * Drop workaround for Python < 2.7.4
+ * Add cryptography package as an optional dependency
+ * Revert "Wipe context before returning request response"
+ * s3token: Stop loading deprecated options
+ * reno: Update master for unmaintained/2024.1
+ * Wipe context before returning request response
+ * Drop flake8-docstrings
+ * Remove unused bandit target
+ * Update master for stable/2025.2
+- drop
+ 0001-Fix-privilege-escalation-via-spoofed-identity-header.patch
+ (upstream)
+
+-------------------------------------------------------------------
Old:
----
0001-Fix-privilege-escalation-via-spoofed-identity-header.patch
keystonemiddleware-10.12.0.tar.gz
New:
----
keystonemiddleware-12.0.0.tar.gz
----------(Old B)----------
Old:- drop
0001-Fix-privilege-escalation-via-spoofed-identity-header.patch
(upstream)
----------(Old E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-keystonemiddleware.spec ++++++
--- /var/tmp/diff_new_pack.H9KYNF/_old 2026-03-10 17:59:36.315133059 +0100
+++ /var/tmp/diff_new_pack.H9KYNF/_new 2026-03-10 17:59:36.319133223 +0100
@@ -17,15 +17,13 @@
Name: python-keystonemiddleware
-Version: 10.12.0
+Version: 12.0.0
Release: 0
Summary: Middleware for OpenStack Identity
License: Apache-2.0
Group: Development/Languages/Python
URL: https://docs.openstack.org/keystonemiddleware
Source0:
https://files.pythonhosted.org/packages/source/k/keystonemiddleware/keystonemiddleware-%{version}.tar.gz
-# PATCH-FIX-UPSTREAM
-Patch1: 0001-Fix-privilege-escalation-via-spoofed-identity-header.patch
BuildRequires: %{python_module WebOb >= 1.7.1}
BuildRequires: %{python_module WebTest}
BuildRequires: %{python_module cryptography}
++++++ keystonemiddleware-10.12.0.tar.gz -> keystonemiddleware-12.0.0.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/AUTHORS
new/keystonemiddleware-12.0.0/AUTHORS
--- old/keystonemiddleware-10.12.0/AUTHORS 2025-08-21 11:34:47.000000000
+0200
+++ new/keystonemiddleware-12.0.0/AUTHORS 2026-02-17 15:49:49.000000000
+0100
@@ -1,4 +1,5 @@
Abhishek Sharma <[email protected]>
+Adam Oswick <[email protected]>
Adam Young <[email protected]>
Alan Pevec <[email protected]>
Alexander Makarov <[email protected]>
@@ -12,6 +13,7 @@
Andrey Pavlov <[email protected]>
Anh Tran <[email protected]>
Anthony Young <[email protected]>
+Arnaud Morin <[email protected]>
Artem Vasilyev <[email protected]>
Arun Kant <[email protected]>
Ayumu Ueha <[email protected]>
@@ -70,6 +72,7 @@
Gage Hugo <[email protected]>
Ghanshyam Mann <[email protected]>
Gordon Chung <[email protected]>
+Grzegorz Grasza <[email protected]>
Guang Yee <[email protected]>
Guang Yee <[email protected]>
Guang Yee <[email protected]>
@@ -101,6 +104,7 @@
Kevin Benton <[email protected]>
Kevin L. Mitchell <[email protected]>
Kieran Spear <[email protected]>
+Koya Watanabe <[email protected]>
Kristi Nikolla <[email protected]>
Kui Shi <[email protected]>
Kun Huang <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/ChangeLog
new/keystonemiddleware-12.0.0/ChangeLog
--- old/keystonemiddleware-10.12.0/ChangeLog 2025-08-21 11:34:47.000000000
+0200
+++ new/keystonemiddleware-12.0.0/ChangeLog 2026-02-17 15:49:49.000000000
+0100
@@ -1,6 +1,26 @@
CHANGES
=======
+12.0.0
+------
+
+* Remove ec2\_token and s3\_token middleware
+* Ignore B105 error
+
+11.0.0
+------
+
+* Fix privilege escalation via spoofed identity headers
+* Drop workaround for Python < 2.7.4
+* Add cryptography package as an optional dependency
+* Revert "Wipe context before returning request response"
+* s3token: Stop loading deprecated options
+* reno: Update master for unmaintained/2024.1
+* Wipe context before returning request response
+* Drop flake8-docstrings
+* Remove unused bandit target
+* Update master for stable/2025.2
+
10.12.0
-------
@@ -63,6 +83,7 @@
* reno: Update master for unmaintained/yoga
* tox: Drop envdir
+* Replace CRLF by LF
* Update python classifier in setup.cfg
* Remove unnecessary setup\_hook
* Python 3.12: do not use utcnow()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/PKG-INFO
new/keystonemiddleware-12.0.0/PKG-INFO
--- old/keystonemiddleware-10.12.0/PKG-INFO 2025-08-21 11:34:47.926037800
+0200
+++ new/keystonemiddleware-12.0.0/PKG-INFO 2026-02-17 15:49:50.080432700
+0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: keystonemiddleware
-Version: 10.12.0
+Version: 12.0.0
Summary: Middleware for OpenStack Identity
Home-page: https://docs.openstack.org/keystonemiddleware/latest/
Author: OpenStack
@@ -35,9 +35,10 @@
Requires-Dist: WebOb>=1.7.1
Provides-Extra: audit-notifications
Requires-Dist: oslo.messaging>=5.29.0; extra == "audit-notifications"
+Provides-Extra: memcache-encryption
+Requires-Dist: cryptography>=2.7; extra == "memcache-encryption"
Provides-Extra: test
Requires-Dist: hacking~=6.1.0; extra == "test"
-Requires-Dist: flake8-docstrings~=1.7.0; extra == "test"
Requires-Dist: coverage>=4.0; extra == "test"
Requires-Dist: cryptography>=3.0; extra == "test"
Requires-Dist: fixtures>=3.0.0; extra == "test"
@@ -53,6 +54,16 @@
Requires-Dist: oslo.messaging>=5.29.0; extra == "test"
Requires-Dist: PyJWT>=2.4.0; extra == "test"
Requires-Dist: bandit>=1.1.0; extra == "test"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: provides-extra
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
========================
Team and repository tags
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/doc/source/installation.rst
new/keystonemiddleware-12.0.0/doc/source/installation.rst
--- old/keystonemiddleware-10.12.0/doc/source/installation.rst 2025-08-21
11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/doc/source/installation.rst 2026-02-17
15:48:39.000000000 +0100
@@ -23,3 +23,7 @@
To install support for audit notifications::
$ pip install keystonemiddleware[audit_notifications]
+
+To install support for memcache encryption::
+
+ $ pip install keystonemiddleware[memcache_encryption]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/ec2_token.py
new/keystonemiddleware-12.0.0/keystonemiddleware/ec2_token.py
--- old/keystonemiddleware-10.12.0/keystonemiddleware/ec2_token.py
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware/ec2_token.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,213 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Starting point for routing EC2 requests."""
-
-import hashlib
-
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_serialization import jsonutils
-import requests
-import webob.dec
-
-from keystonemiddleware.i18n import _
-
-
-keystone_ec2_opts = [
- cfg.StrOpt('url',
- default='http://localhost:5000/v3/ec2tokens',
- help='URL to get token from ec2 request.'),
- cfg.StrOpt('keyfile',
- help='Required if EC2 server requires client certificate.'),
- cfg.StrOpt('certfile',
- help='Client certificate key filename. Required if EC2 server '
- 'requires client certificate.'),
- cfg.StrOpt('cafile',
- help='A PEM encoded certificate authority to use when '
- 'verifying HTTPS connections. Defaults to the system '
- 'CAs.'),
- cfg.BoolOpt('insecure', default=False,
- help='Disable SSL certificate verification.'),
- cfg.IntOpt('timeout', default=60,
- help='Timeout to obtain token.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(keystone_ec2_opts, group='keystone_ec2_token')
-
-
-PROTOCOL_NAME = 'EC2 Token Authentication'
-
-
-class EC2Token(object):
- """Authenticate an EC2 request with keystone and convert to token."""
-
- def __init__(self, application, conf):
- super(EC2Token, self).__init__()
- self._application = application
- self._logger = logging.getLogger(conf.get('log_name', __name__))
- self._logger.debug('Starting the %s component', PROTOCOL_NAME)
-
- def _ec2_error_response(self, code, message):
- """Helper to construct an EC2 compatible error message."""
- self._logger.debug('EC2 error response: %(code)s: %(message)s',
- {'code': code, 'message': message})
- resp = webob.Response()
- resp.status = 400
- resp.headers['Content-Type'] = 'text/xml'
- error_msg = str('<?xml version="1.0"?>\n'
- '<Response><Errors><Error><Code>%s</Code>'
- '<Message>%s</Message></Error></Errors></Response>' %
- (code, message))
- error_msg = error_msg.encode()
- resp.body = error_msg
- return resp
-
- def _get_signature(self, req):
- """Extract the signature from the request.
-
- This can be a get/post variable or for version 4 also in a header
- called 'Authorization'.
- - params['Signature'] == version 0,1,2,3
- - params['X-Amz-Signature'] == version 4
- - header 'Authorization' == version 4
- """
- sig = req.params.get('Signature') or req.params.get('X-Amz-Signature')
- if sig is None and 'Authorization' in req.headers:
- auth_str = req.headers['Authorization']
- sig = auth_str.partition("Signature=")[2].split(',')[0]
-
- return sig
-
- def _get_access(self, req):
- """Extract the access key identifier.
-
- For version 0/1/2/3 this is passed as the AccessKeyId parameter, for
- version 4 it is either an X-Amz-Credential parameter or a Credential=
- field in the 'Authorization' header string.
- """
- access = req.params.get('AWSAccessKeyId')
- if access is None:
- cred_param = req.params.get('X-Amz-Credential')
- if cred_param:
- access = cred_param.split("/")[0]
-
- if access is None and 'Authorization' in req.headers:
- auth_str = req.headers['Authorization']
- cred_str = auth_str.partition("Credential=")[2].split(',')[0]
- access = cred_str.split("/")[0]
-
- return access
-
- @webob.dec.wsgify()
- def __call__(self, req):
- # NOTE(alevine): We need to calculate the hash here because
- # subsequent access to request modifies the req.body so the hash
- # calculation will yield invalid results.
- body_hash = hashlib.sha256(req.body).hexdigest()
-
- signature = self._get_signature(req)
- if not signature:
- msg = _("Signature not provided")
- return self._ec2_error_response("AuthFailure", msg)
- access = self._get_access(req)
- if not access:
- msg = _("Access key not provided")
- return self._ec2_error_response("AuthFailure", msg)
-
- if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers:
- auth_params = {}
- else:
- # Make a copy of args for authentication and signature verification
- auth_params = dict(req.params)
- # Not part of authentication args
- auth_params.pop('Signature', None)
-
- headers = req.headers
- # NOTE(andrey-mp): jsonutils dumps it as list of keys without
- # conversion instead real dict
- headers = {k: headers[k] for k in headers}
- cred_dict = {
- 'access': access,
- 'signature': signature,
- 'host': req.host,
- 'verb': req.method,
- 'path': req.path,
- 'params': auth_params,
- 'headers': headers,
- 'body_hash': body_hash
- }
- if "ec2" in CONF.keystone_ec2_token.url:
- creds = {'ec2Credentials': cred_dict}
- else:
- creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}}
- creds_json = jsonutils.dumps(creds)
- headers = {'Content-Type': 'application/json'}
-
- verify = not CONF.keystone_ec2_token.insecure
- if verify and CONF.keystone_ec2_token.cafile:
- verify = CONF.keystone_ec2_token.cafile
-
- cert = None
- if (CONF.keystone_ec2_token.certfile and
- CONF.keystone_ec2_token.keyfile):
- cert = (CONF.keystone_ec2_certfile,
- CONF.keystone_ec2_token.keyfile)
- elif CONF.keystone_ec2_token.certfile:
- cert = CONF.keystone_ec2_token.certfile
-
- response = requests.post(CONF.keystone_ec2_token.url,
- data=creds_json, headers=headers,
- verify=verify, cert=cert,
- timeout=CONF.keystone_ec2_token.timeout)
-
- # NOTE(vish): We could save a call to keystone by
- # having keystone return token, tenant,
- # user, and roles from this call.
-
- status_code = response.status_code
- if status_code != 200:
- msg = _('Error response from keystone: %s') % response.reason
- self._logger.debug(msg)
- return self._ec2_error_response("AuthFailure", msg)
- try:
- token_id = response.headers['x-subject-token']
- except (AttributeError, KeyError):
- msg = _("Failure parsing response from keystone")
- self._logger.exception(msg)
- return self._ec2_error_response("AuthFailure", msg)
-
- # Authenticated!
- req.headers['X-Auth-Token'] = token_id
- return self._application
-
-
-def filter_factory(global_conf, **local_conf):
- """Return a WSGI filter app for use with paste.deploy."""
- conf = global_conf.copy()
- conf.update(local_conf)
-
- def auth_filter(app):
- return EC2Token(app, conf)
- return auth_filter
-
-
-def app_factory(global_conf, **local_conf):
- conf = global_conf.copy()
- conf.update(local_conf)
- return EC2Token(None, conf)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/echo/service.py
new/keystonemiddleware-12.0.0/keystonemiddleware/echo/service.py
--- old/keystonemiddleware-10.12.0/keystonemiddleware/echo/service.py
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware/echo/service.py
2026-02-17 15:48:39.000000000 +0100
@@ -43,7 +43,7 @@
def __init__(self):
# hardcode any non-default configuration here
- conf = {'auth_protocol': 'http', 'admin_token': 'ADMIN'}
+ 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)...')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/external_oauth2_token.py
new/keystonemiddleware-12.0.0/keystonemiddleware/external_oauth2_token.py
--- old/keystonemiddleware-10.12.0/keystonemiddleware/external_oauth2_token.py
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware/external_oauth2_token.py
2026-02-17 15:48:39.000000000 +0100
@@ -33,6 +33,7 @@
from keystonemiddleware._common import config
from keystonemiddleware.auth_token import _cache
+from keystonemiddleware.auth_token import _request
from keystonemiddleware.exceptions import ConfigurationError
from keystonemiddleware.exceptions import KeystoneMiddlewareException
from keystonemiddleware.i18n import _
@@ -246,7 +247,7 @@
the auth method 'client_secret_basic'.
"""
req_data = {'token': access_token,
- 'token_type_hint': 'access_token'}
+ 'token_type_hint': 'access_token'} # nosec: B105
auth = requests.auth.HTTPBasicAuth(self.client_id,
self.client_secret)
http_response = self.session.request(
@@ -279,7 +280,7 @@
'client_id': self.client_id,
'client_secret': self.client_secret,
'token': access_token,
- 'token_type_hint': 'access_token'
+ 'token_type_hint': 'access_token' # nosec: B105
}
http_response = self.session.request(
self.introspect_endpoint,
@@ -301,7 +302,7 @@
req_data = {
'client_id': self.client_id,
'token': access_token,
- 'token_type_hint': 'access_token'
+ 'token_type_hint': 'access_token' # nosec: B105
}
http_response = self.session.request(
self.introspect_endpoint,
@@ -382,7 +383,7 @@
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': client_assertion,
'token': access_token,
- 'token_type_hint': 'access_token'
+ 'token_type_hint': 'access_token' # nosec: B105
}
http_response = self.session.request(
self.introspect_endpoint,
@@ -442,7 +443,7 @@
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': client_assertion,
'token': access_token,
- 'token_type_hint': 'access_token'
+ 'token_type_hint': 'access_token' # nosec: B105
}
http_response = self.session.request(
self.introspect_endpoint,
@@ -534,7 +535,7 @@
**cache_kwargs)
return _cache.TokenCache(self._log, **cache_kwargs)
- @webob.dec.wsgify()
+ @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)
def __call__(self, req):
"""Handle incoming request."""
self.process_request(req)
@@ -545,8 +546,10 @@
"""Process request.
:param request: Incoming request
- :type request: _request.AuthTokenRequest
+ :type request: _request._AuthTokenRequest
"""
+ request.remove_auth_headers()
+
access_token = None
if (request.authorization and
request.authorization.authtype == 'Bearer'):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/s3_token.py
new/keystonemiddleware-12.0.0/keystonemiddleware/s3_token.py
--- old/keystonemiddleware-10.12.0/keystonemiddleware/s3_token.py
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware/s3_token.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,238 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# Copyright 2011,2012 Akira YOSHIYAMA <[email protected]>
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This source code is based ./auth_token.py and ./ec2_token.py.
-# See them for their copyright.
-
-"""
-S3 Token Middleware.
-
-This WSGI component:
-
-* Gets a request from the swift3 middleware with an S3 Authorization
- access key.
-* Validates s3 token in Keystone.
-* Transforms the account name to AUTH_%(tenant_name).
-
-"""
-
-import webob
-
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_serialization import jsonutils
-from oslo_utils import strutils
-import requests
-
-s3_opts = [
- cfg.IntOpt('timeout', default=60,
- help='Timeout to obtain token.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(s3_opts, group='s3_token')
-
-PROTOCOL_NAME = 'S3 Token Authentication'
-
-
-class ServiceError(Exception):
- pass
-
-
-class S3Token(object):
- """Middleware that handles S3 authentication."""
-
- def __init__(self, app, conf):
- """Common initialization code."""
- self._app = app
- self._logger = logging.getLogger(conf.get('log_name', __name__))
- self._logger.debug('Starting the %s component', PROTOCOL_NAME)
- self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_')
- # where to find the auth service (we use this to validate tokens)
-
- self._request_uri = conf.get('www_authenticate_uri')
- auth_uri = conf.get('auth_uri')
- if not self._request_uri and auth_uri:
- self._logger.warning(
- "Use of the auth_uri option was deprecated "
- "in the Queens release in favor of www_authenticate_uri. This "
- "option will be removed in the S release.")
- self._request_uri = auth_uri
- if not self._request_uri:
- self._logger.warning(
- "Use of the auth_host, auth_port, and auth_protocol "
- "configuration options was deprecated in the Newton release "
- "in favor of www_authenticate_uri. These options will be "
- "removed in the S release.")
- auth_host = conf.get('auth_host')
- auth_port = int(conf.get('auth_port', 35357))
- auth_protocol = conf.get('auth_protocol', 'https')
-
- self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host,
- auth_port)
-
- # SSL
- insecure = strutils.bool_from_string(conf.get('insecure', False))
- cert_file = conf.get('certfile')
- key_file = conf.get('keyfile')
-
- if insecure:
- self._verify = False
- elif cert_file and key_file:
- self._verify = (cert_file, key_file)
- elif cert_file:
- self._verify = cert_file
- else:
- self._verify = None
-
- def _deny_request(self, code):
- error_table = {
- 'AccessDenied': (401, 'Access denied'),
- 'InvalidURI': (400, 'Could not parse the specified URI'),
- }
- resp = webob.Response(content_type='text/xml')
- resp.status = error_table[code][0]
- error_msg = ('<?xml version="1.0" encoding="UTF-8"?>\r\n'
- '<Error>\r\n <Code>%s</Code>\r\n '
- '<Message>%s</Message>\r\n</Error>\r\n' %
- (code, error_table[code][1]))
- error_msg = error_msg.encode()
- resp.body = error_msg
- return resp
-
- def _json_request(self, creds_json):
- headers = {'Content-Type': 'application/json'}
- try:
- response = requests.post('%s/v3/s3tokens' % self._request_uri,
- headers=headers, data=creds_json,
- verify=self._verify,
- timeout=CONF.s3_token.timeout)
- except requests.exceptions.RequestException as e:
- self._logger.info('HTTP connection exception: %s', e)
- resp = self._deny_request('InvalidURI')
- raise ServiceError(resp)
-
- if response.status_code < 200 or response.status_code >= 300:
- self._logger.debug('Keystone reply error: status=%s reason=%s',
- response.status_code, response.reason)
- resp = self._deny_request('AccessDenied')
- raise ServiceError(resp)
-
- return response
-
- def __call__(self, environ, start_response):
- """Handle incoming request. authenticate and send downstream."""
- req = webob.Request(environ)
- self._logger.debug('Calling S3Token middleware.')
-
- try:
- parts = strutils.split_path(req.path, 1, 4, True)
- version, account, container, obj = parts
- except ValueError:
- msg = 'Not a path query, skipping.'
- self._logger.debug(msg)
- return self._app(environ, start_response)
-
- # Read request signature and access id.
- if 'Authorization' not in req.headers:
- msg = 'No Authorization header. skipping.'
- self._logger.debug(msg)
- return self._app(environ, start_response)
-
- token = req.headers.get('X-Auth-Token',
- req.headers.get('X-Storage-Token'))
- if not token:
- msg = 'You did not specify an auth or a storage token. skipping.'
- self._logger.debug(msg)
- return self._app(environ, start_response)
-
- auth_header = req.headers['Authorization']
- try:
- access, signature = auth_header.split(' ')[-1].rsplit(':', 1)
- except ValueError:
- msg = 'You have an invalid Authorization header: %s'
- self._logger.debug(msg, auth_header)
- return self._deny_request('InvalidURI')(environ, start_response)
-
- # NOTE(chmou): This is to handle the special case with nova
- # when we have the option s3_affix_tenant. We will force it to
- # connect to another account than the one
- # authenticated. Before people start getting worried about
- # security, I should point that we are connecting with
- # username/token specified by the user but instead of
- # connecting to its own account we will force it to go to an
- # another account. In a normal scenario if that user don't
- # have the reseller right it will just fail but since the
- # reseller account can connect to every account it is allowed
- # by the swift_auth middleware.
- force_tenant = None
- if ':' in access:
- access, force_tenant = access.split(':')
-
- # Authenticate request.
- creds = {'credentials': {'access': access,
- 'token': token,
- 'signature': signature}}
- creds_json = jsonutils.dumps(creds)
- self._logger.debug('Connecting to Keystone sending this JSON: %s',
- creds_json)
- # NOTE(vish): We could save a call to keystone by having
- # keystone return token, tenant, user, and roles
- # from this call.
- #
- # NOTE(chmou): We still have the same problem we would need to
- # change token_auth to detect if we already
- # identified and not doing a second query and just
- # pass it through to swiftauth in this case.
- try:
- resp = self._json_request(creds_json)
- except ServiceError as e:
- resp = e.args[0]
- msg = 'Received error, exiting middleware with error: %s'
- self._logger.debug(msg, resp.status_code)
- return resp(environ, start_response)
-
- self._logger.debug('Keystone Reply: Status: %d, Output: %s',
- resp.status_code, resp.content)
-
- try:
- identity_info = resp.json()
- token_id = str(identity_info['access']['token']['id'])
- tenant = identity_info['access']['token']['tenant']
- except (ValueError, KeyError):
- error = 'Error on keystone reply: %d %s'
- self._logger.debug(error, resp.status_code, resp.content)
- return self._deny_request('InvalidURI')(environ, start_response)
-
- req.headers['X-Auth-Token'] = token_id
- tenant_to_connect = force_tenant or tenant['id']
- self._logger.debug('Connecting with tenant: %s', tenant_to_connect)
- new_tenant_name = '%s%s' % (self._reseller_prefix, tenant_to_connect)
- environ['PATH_INFO'] = environ['PATH_INFO'].replace(account,
- new_tenant_name)
- return self._app(environ, start_response)
-
-
-def filter_factory(global_conf, **local_conf):
- """Return a WSGI filter app for use with paste.deploy."""
- conf = global_conf.copy()
- conf.update(local_conf)
-
- def auth_filter(app):
- return S3Token(app, conf)
- return auth_filter
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py
---
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py
2025-08-21 11:33:29.000000000 +0200
+++
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,180 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from unittest import mock
-
-from oslo_serialization import jsonutils
-import requests
-import webob
-
-from keystonemiddleware import ec2_token
-from keystonemiddleware.tests.unit import utils
-
-
-TOKEN_ID = 'fake-token-id'
-EMPTY_RESPONSE = {}
-
-
-class FakeResponse(object):
- reason = "Test Reason"
- headers = {'x-subject-token': TOKEN_ID}
-
- def __init__(self, json, status_code=400):
- self._json = json
- self.status_code = status_code
-
- def json(self):
- return self._json
-
-
-class FakeApp(object):
- """This represents a WSGI app protected by the auth_token middleware."""
-
- def __call__(self, env, start_response):
- resp = webob.Response()
- resp.environ = env
- return resp(env, start_response)
-
-
-class EC2TokenMiddlewareTestBase(utils.TestCase):
-
- TEST_PROTOCOL = 'https'
- TEST_HOST = 'fakehost'
- TEST_PORT = 35357
- TEST_URL = '%s://%s:%d/v3/ec2tokens' % (TEST_PROTOCOL,
- TEST_HOST,
- TEST_PORT)
-
- def setUp(self):
- super(EC2TokenMiddlewareTestBase, self).setUp()
- self.middleware = ec2_token.EC2Token(FakeApp(), {})
-
- def _validate_ec2_error(self, response, http_status, ec2_code):
- self.assertEqual(http_status, response.status_code,
- 'Expected HTTP status %s' % http_status)
- error_msg = '<Code>%s</Code>' % ec2_code
- error_msg = error_msg.encode()
- self.assertIn(error_msg, response.body)
-
-
-class EC2TokenMiddlewareTestGood(EC2TokenMiddlewareTestBase):
- @mock.patch.object(
- requests, 'post',
- return_value=FakeResponse(EMPTY_RESPONSE, status_code=200))
- def test_protocol_old_versions(self, mock_request):
- req = webob.Request.blank('/test')
- req.GET['Signature'] = 'test-signature'
- req.GET['AWSAccessKeyId'] = 'test-key-id'
- req.body = b'Action=ListUsers&Version=2010-05-08'
- resp = req.get_response(self.middleware)
- self.assertEqual(200, resp.status_code)
- self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token'])
-
- mock_request.assert_called_with(
- 'http://localhost:5000/v3/ec2tokens',
- data=mock.ANY, headers={'Content-Type': 'application/json'},
- verify=True, cert=None, timeout=mock.ANY)
-
- data = jsonutils.loads(mock_request.call_args[1]['data'])
- expected_data = {
- 'ec2Credentials': {
- 'access': 'test-key-id',
- 'headers': {'Host': 'localhost:80', 'Content-Length': '35'},
- 'host': 'localhost:80',
- 'verb': 'GET',
- 'params': {'AWSAccessKeyId': 'test-key-id'},
- 'signature': 'test-signature',
- 'path': '/test',
- 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01'
- 'bf2c23245fa365ef83fe8f1f955085e2'}}
- self.assertDictEqual(expected_data, data)
-
- @mock.patch.object(
- requests, 'post',
- return_value=FakeResponse(EMPTY_RESPONSE, status_code=200))
- def test_protocol_v4(self, mock_request):
- req = webob.Request.blank('/test')
- auth_str = (
- 'AWS4-HMAC-SHA256'
- ' Credential=test-key-id/20110909/us-east-1/iam/aws4_request,'
- ' SignedHeaders=content-type;host;x-amz-date,'
- ' Signature=test-signature')
- req.headers['Authorization'] = auth_str
- req.body = b'Action=ListUsers&Version=2010-05-08'
- resp = req.get_response(self.middleware)
- self.assertEqual(200, resp.status_code)
- self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token'])
-
- mock_request.assert_called_with(
- 'http://localhost:5000/v3/ec2tokens',
- data=mock.ANY, headers={'Content-Type': 'application/json'},
- verify=True, cert=None, timeout=mock.ANY)
-
- data = jsonutils.loads(mock_request.call_args[1]['data'])
- expected_data = {
- 'ec2Credentials': {
- 'access': 'test-key-id',
- 'headers': {'Host': 'localhost:80',
- 'Content-Length': '35',
- 'Authorization': auth_str},
- 'host': 'localhost:80',
- 'verb': 'GET',
- 'params': {},
- 'signature': 'test-signature',
- 'path': '/test',
- 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01'
- 'bf2c23245fa365ef83fe8f1f955085e2'}}
- self.assertDictEqual(expected_data, data)
-
-
-class EC2TokenMiddlewareTestBad(EC2TokenMiddlewareTestBase):
-
- def test_no_signature(self):
- req = webob.Request.blank('/test')
- resp = req.get_response(self.middleware)
- self._validate_ec2_error(resp, 400, 'AuthFailure')
-
- def test_no_key_id(self):
- req = webob.Request.blank('/test')
- req.GET['Signature'] = 'test-signature'
- resp = req.get_response(self.middleware)
- self._validate_ec2_error(resp, 400, 'AuthFailure')
-
- @mock.patch.object(
- requests, 'post',
- return_value=FakeResponse(EMPTY_RESPONSE))
- def test_communication_failure(self, mock_request):
- req = webob.Request.blank('/test')
- req.GET['Signature'] = 'test-signature'
- req.GET['AWSAccessKeyId'] = 'test-key-id'
- resp = req.get_response(self.middleware)
- self._validate_ec2_error(resp, 400, 'AuthFailure')
- mock_request.assert_called_with(
- 'http://localhost:5000/v3/ec2tokens',
- data=mock.ANY, headers=mock.ANY,
- verify=mock.ANY, cert=mock.ANY, timeout=mock.ANY)
-
- @mock.patch.object(
- requests, 'post',
- return_value=FakeResponse(EMPTY_RESPONSE))
- def test_no_result_data(self, mock_request):
- req = webob.Request.blank('/test')
- req.GET['Signature'] = 'test-signature'
- req.GET['AWSAccessKeyId'] = 'test-key-id'
- resp = req.get_response(self.middleware)
- self._validate_ec2_error(resp, 400, 'AuthFailure')
- mock_request.assert_called_with(
- 'http://localhost:5000/v3/ec2tokens',
- data=mock.ANY, headers=mock.ANY,
- verify=mock.ANY, cert=mock.ANY, timeout=mock.ANY)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_entry_points.py
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_entry_points.py
---
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_entry_points.py
2025-08-21 11:33:29.000000000 +0200
+++
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_entry_points.py
2026-02-17 15:48:39.000000000 +0100
@@ -22,8 +22,6 @@
expected_factory_names = [
'audit',
'auth_token',
- 'ec2_token',
- 's3_token',
]
em = stevedore.ExtensionManager('paste.filter_factory')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py
---
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py
2025-08-21 11:33:29.000000000 +0200
+++
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py
2026-02-17 15:48:39.000000000 +0100
@@ -1823,6 +1823,82 @@
self.assertEqual(resp.headers.get('WWW-Authenticate'),
'Authorization OAuth 2.0 uri="%s"' % self._audience)
+ def test_spoofed_headers_are_sanitized(self):
+ """Test that spoofed identity headers are removed and replaced.
+
+ This test verifies the fix for a privilege escalation vulnerability
+ where an attacker could send spoofed identity headers that would not
+ be cleared by the middleware, allowing unauthorized access.
+ """
+ conf = copy.deepcopy(self._test_conf)
+ self.set_middleware(conf=conf)
+
+ # Use non-admin roles in the token metadata
+ non_admin_roles = 'member,reader'
+ non_admin_metadata = copy.deepcopy(self._default_metadata)
+ non_admin_metadata['roles'] = non_admin_roles
+
+ def mock_resp(request, context):
+ return self._introspect_response(
+ request, context,
+ auth_method=self._auth_method,
+ introspect_client_id=self._test_client_id,
+ introspect_client_secret=self._test_client_secret,
+ access_token=self._token,
+ active=True,
+ metadata=non_admin_metadata
+ )
+
+ self.requests_mock.post(self._introspect_endpoint,
+ json=mock_resp)
+ self.requests_mock.get(self._auth_url,
+ json=VERSION_LIST_v3,
+ status_code=300)
+
+ # Attempt to spoof multiple identity headers
+ spoofed_headers = get_authorization_header(self._token)
+ spoofed_headers.update({
+ 'X-Identity-Status': 'Confirmed',
+ 'X-Is-Admin-Project': 'true',
+ 'X-User-Id': 'spoofed_admin_user_id',
+ 'X-User-Name': 'spoofed_admin',
+ 'X-Roles': 'admin,superuser',
+ 'X-Project-Id': 'spoofed_project_id',
+ 'X-User-Domain-Id': 'spoofed_domain_id',
+ 'X-User-Domain-Name': 'spoofed_domain',
+ })
+
+ resp = self.call_middleware(
+ headers=spoofed_headers,
+ expected_status=200,
+ method='GET', path='/vnfpkgm/v1/vnf_packages',
+ environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))}
+ )
+ self.assertEqual(FakeApp.SUCCESS, resp.body)
+
+ # Verify spoofed headers were replaced with actual token values
+ env = resp.request.environ
+
+ # X-Is-Admin-Project should not be present (not the spoofed 'true')
+ # because the token has non-admin roles and the middleware only sets
+ # this header when is_admin is true
+ self.assertNotIn('HTTP_X_IS_ADMIN_PROJECT', env)
+
+ # User info should match the token, not the spoofed values
+ self.assertEqual(self._user_id, env['HTTP_X_USER_ID'])
+ self.assertEqual(self._user_name, env['HTTP_X_USER_NAME'])
+ self.assertEqual(self._user_domain_id, env['HTTP_X_USER_DOMAIN_ID'])
+ self.assertEqual(
+ self._user_domain_name,
+ env['HTTP_X_USER_DOMAIN_NAME']
+ )
+
+ # Roles should be from the token, not spoofed
+ self.assertEqual(non_admin_roles, env['HTTP_X_ROLES'])
+
+ # Project info should match the token
+ self.assertEqual(self._project_id, env['HTTP_X_PROJECT_ID'])
+
class ExternalAuth2ProtocolTest(BaseExternalOauth2TokenMiddlewareTest):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py
---
old/keystonemiddleware-10.12.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py
2025-08-21 11:33:29.000000000 +0200
+++
new/keystonemiddleware-12.0.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,249 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from unittest import mock
-import urllib.parse
-
-import fixtures
-from oslo_serialization import jsonutils
-import requests
-from requests_mock.contrib import fixture as rm_fixture
-from testtools import matchers
-import webob
-
-from keystonemiddleware import s3_token
-from keystonemiddleware.tests.unit import utils
-
-
-GOOD_RESPONSE = {'access': {'token': {'id': 'TOKEN_ID',
- 'tenant': {'id': 'TENANT_ID'}}}}
-
-
-class FakeApp(object):
- """This represents a WSGI app protected by the auth_token middleware."""
-
- def __call__(self, env, start_response):
- resp = webob.Response()
- resp.environ = env
- return resp(env, start_response)
-
-
-class S3TokenMiddlewareTestBase(utils.TestCase):
-
- TEST_WWW_AUTHENTICATE_URI = 'https://fakehost/identity'
- TEST_URL = '%s/v3/s3tokens' % (TEST_WWW_AUTHENTICATE_URI, )
-
- def setUp(self):
- super(S3TokenMiddlewareTestBase, self).setUp()
-
- self.conf = {
- 'www_authenticate_uri': self.TEST_WWW_AUTHENTICATE_URI,
- }
-
- self.requests_mock = self.useFixture(rm_fixture.Fixture())
-
- def start_fake_response(self, status, headers):
- self.response_status = int(status.split(' ', 1)[0])
- self.response_headers = dict(headers)
-
-
-class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase):
-
- def setUp(self):
- super(S3TokenMiddlewareTestGood, self).setUp()
- self.middleware = s3_token.S3Token(FakeApp(), self.conf)
-
- self.requests_mock.post(self.TEST_URL,
- status_code=201,
- json=GOOD_RESPONSE)
-
- # Ignore the request and pass to the next middleware in the
- # pipeline if no path has been specified.
- def test_no_path_request(self):
- req = webob.Request.blank('/')
- self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
-
- # Ignore the request and pass to the next middleware in the
- # pipeline if no Authorization header has been specified
- def test_without_authorization(self):
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
-
- def test_without_auth_storage_token(self):
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'badboy'
- self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
-
- def test_authorized(self):
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- req.get_response(self.middleware)
- self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID'))
- self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID')
-
- def test_authorized_http(self):
- protocol = 'http'
- host = 'fakehost'
- port = 35357
- self.requests_mock.post(
- '%s://%s:%s/v3/s3tokens' % (protocol, host, port),
- status_code=201, json=GOOD_RESPONSE)
-
- self.middleware = (
- s3_token.filter_factory({'auth_protocol': protocol,
- 'auth_host': host,
- 'auth_port': port})(FakeApp()))
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- req.get_response(self.middleware)
- self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID'))
- self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID')
-
- def test_authorization_nova_toconnect(self):
- req = webob.Request.blank('/v1/AUTH_swiftint/c/o')
- req.headers['Authorization'] = 'access:FORCED_TENANT_ID:signature'
- req.headers['X-Storage-Token'] = 'token'
- req.get_response(self.middleware)
- path = req.environ['PATH_INFO']
- self.assertTrue(path.startswith('/v1/AUTH_FORCED_TENANT_ID'))
-
- @mock.patch.object(requests, 'post')
- def test_insecure(self, MOCK_REQUEST):
- self.middleware = (
- s3_token.filter_factory({'insecure': 'True'})(FakeApp()))
-
- text_return_value = jsonutils.dumps(GOOD_RESPONSE).encode()
- MOCK_REQUEST.return_value = utils.TestResponse({
- 'status_code': 201,
- 'text': text_return_value})
-
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- req.get_response(self.middleware)
-
- self.assertTrue(MOCK_REQUEST.called)
- mock_args, mock_kwargs = MOCK_REQUEST.call_args
- self.assertIs(mock_kwargs['verify'], False)
-
- def test_insecure_option(self):
- # insecure is passed as a string.
-
- # Some non-secure values.
- true_values = ['true', 'True', '1', 'yes']
- for val in true_values:
- config = {'insecure': val, 'certfile': 'false_ind'}
- middleware = s3_token.filter_factory(config)(FakeApp())
- self.assertIs(False, middleware._verify)
-
- # Some "secure" values, including unexpected value.
- false_values = ['false', 'False', '0', 'no', 'someweirdvalue']
- for val in false_values:
- config = {'insecure': val, 'certfile': 'false_ind'}
- middleware = s3_token.filter_factory(config)(FakeApp())
- self.assertEqual('false_ind', middleware._verify)
-
- # Default is secure.
- config = {'certfile': 'false_ind'}
- middleware = s3_token.filter_factory(config)(FakeApp())
- self.assertIs('false_ind', middleware._verify)
-
- def test_unicode_path(self):
- url = u'/v1/AUTH_cfa/c/euro\u20ac'.encode('utf8')
- req = webob.Request.blank(urllib.parse.quote(url))
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- req.get_response(self.middleware)
-
-
-class S3TokenMiddlewareTestBad(S3TokenMiddlewareTestBase):
- def setUp(self):
- super(S3TokenMiddlewareTestBad, self).setUp()
- self.middleware = s3_token.S3Token(FakeApp(), self.conf)
-
- def test_unauthorized_token(self):
- ret = {"error":
- {"message": "EC2 access key not found.",
- "code": 401,
- "title": "Unauthorized"}}
- self.requests_mock.post(self.TEST_URL, status_code=403, json=ret)
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- resp = req.get_response(self.middleware)
- s3_denied_req = self.middleware._deny_request('AccessDenied')
- self.assertEqual(resp.body, s3_denied_req.body)
- self.assertEqual(resp.status_int, s3_denied_req.status_int)
-
- def test_bogus_authorization(self):
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'badboy'
- req.headers['X-Storage-Token'] = 'token'
- resp = req.get_response(self.middleware)
- self.assertEqual(resp.status_int, 400)
- s3_invalid_req = self.middleware._deny_request('InvalidURI')
- self.assertEqual(resp.body, s3_invalid_req.body)
- self.assertEqual(resp.status_int, s3_invalid_req.status_int)
-
- def test_fail_to_connect_to_keystone(self):
- with mock.patch.object(self.middleware, '_json_request') as o:
- s3_invalid_req = self.middleware._deny_request('InvalidURI')
- o.side_effect = s3_token.ServiceError(s3_invalid_req)
-
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- resp = req.get_response(self.middleware)
- self.assertEqual(resp.body, s3_invalid_req.body)
- self.assertEqual(resp.status_int, s3_invalid_req.status_int)
-
- def test_bad_reply(self):
- self.requests_mock.post(self.TEST_URL,
- status_code=201,
- text="<badreply>")
-
- req = webob.Request.blank('/v1/AUTH_cfa/c/o')
- req.headers['Authorization'] = 'access:signature'
- req.headers['X-Storage-Token'] = 'token'
- resp = req.get_response(self.middleware)
- s3_invalid_req = self.middleware._deny_request('InvalidURI')
- self.assertEqual(resp.body, s3_invalid_req.body)
- self.assertEqual(resp.status_int, s3_invalid_req.status_int)
-
-
-class S3TokenMiddlewareTestDeprecatedOptions(S3TokenMiddlewareTestBase):
- def setUp(self):
- super(S3TokenMiddlewareTestDeprecatedOptions, self).setUp()
- self.conf = {
- 'auth_uri': self.TEST_WWW_AUTHENTICATE_URI,
- }
- self.logger = self.useFixture(fixtures.FakeLogger())
- self.middleware = s3_token.S3Token(FakeApp(), self.conf)
-
- self.requests_mock.post(self.TEST_URL,
- status_code=201,
- json=GOOD_RESPONSE)
-
- def test_logs_warning(self):
- req = webob.Request.blank('/')
- self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
- log = "Use of the auth_uri option was deprecated in the Queens " \
- "release in favor of www_authenticate_uri."
- self.assertThat(self.logger.output, matchers.Contains(log))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/PKG-INFO
new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/PKG-INFO
--- old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/PKG-INFO
2025-08-21 11:34:47.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/PKG-INFO
2026-02-17 15:49:50.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: keystonemiddleware
-Version: 10.12.0
+Version: 12.0.0
Summary: Middleware for OpenStack Identity
Home-page: https://docs.openstack.org/keystonemiddleware/latest/
Author: OpenStack
@@ -35,9 +35,10 @@
Requires-Dist: WebOb>=1.7.1
Provides-Extra: audit-notifications
Requires-Dist: oslo.messaging>=5.29.0; extra == "audit-notifications"
+Provides-Extra: memcache-encryption
+Requires-Dist: cryptography>=2.7; extra == "memcache-encryption"
Provides-Extra: test
Requires-Dist: hacking~=6.1.0; extra == "test"
-Requires-Dist: flake8-docstrings~=1.7.0; extra == "test"
Requires-Dist: coverage>=4.0; extra == "test"
Requires-Dist: cryptography>=3.0; extra == "test"
Requires-Dist: fixtures>=3.0.0; extra == "test"
@@ -53,6 +54,16 @@
Requires-Dist: oslo.messaging>=5.29.0; extra == "test"
Requires-Dist: PyJWT>=2.4.0; extra == "test"
Requires-Dist: bandit>=1.1.0; extra == "test"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: provides-extra
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
========================
Team and repository tags
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/SOURCES.txt
new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/SOURCES.txt
--- old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/SOURCES.txt
2025-08-21 11:34:47.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/SOURCES.txt
2026-02-17 15:49:50.000000000 +0100
@@ -28,7 +28,6 @@
doc/source/images/graphs_authComp.svg
doc/source/images/graphs_authCompDelegate.svg
keystonemiddleware/__init__.py
-keystonemiddleware/ec2_token.py
keystonemiddleware/exceptions.py
keystonemiddleware/external_oauth2_token.py
keystonemiddleware/fixture.py
@@ -36,7 +35,6 @@
keystonemiddleware/oauth2_mtls_token.py
keystonemiddleware/oauth2_token.py
keystonemiddleware/opts.py
-keystonemiddleware/s3_token.py
keystonemiddleware.egg-info/PKG-INFO
keystonemiddleware.egg-info/SOURCES.txt
keystonemiddleware.egg-info/dependency_links.txt
@@ -69,14 +67,12 @@
keystonemiddleware/tests/unit/__init__.py
keystonemiddleware/tests/unit/client_fixtures.py
keystonemiddleware/tests/unit/test_access_rules.py
-keystonemiddleware/tests/unit/test_ec2_token_middleware.py
keystonemiddleware/tests/unit/test_entry_points.py
keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py
keystonemiddleware/tests/unit/test_fixtures.py
keystonemiddleware/tests/unit/test_oauth2_mtls_token_middleware.py
keystonemiddleware/tests/unit/test_oauth2_token_middleware.py
keystonemiddleware/tests/unit/test_opts.py
-keystonemiddleware/tests/unit/test_s3_token_middleware.py
keystonemiddleware/tests/unit/utils.py
keystonemiddleware/tests/unit/audit/__init__.py
keystonemiddleware/tests/unit/audit/base.py
@@ -135,11 +131,13 @@
releasenotes/notes/fix-cache-data-corrupted-issue-d1bd546625690581.yaml
releasenotes/notes/interface-option-ed551d2a3162668d.yaml
releasenotes/notes/ksm_4.1.0-3cd78446d8e63616.yaml
+releasenotes/notes/remove-ec2-s3-token-middleware-e0b9b63428224600.yaml
releasenotes/notes/remove-py38-438c67d4d2da02a3.yaml
releasenotes/notes/remove-py39-ffac30d683a53099.yaml
releasenotes/notes/remove_kwargs_to_fetch_token-20e3451ed192ab6a.yaml
releasenotes/notes/removed-as-of-ussuri-4e1ea485ba8801c9.yaml
releasenotes/notes/rename-auth-uri-d223d883f5898aee.yaml
+releasenotes/notes/s3token-remove-deprecated-opts-a58a7715b275a89c.yaml
releasenotes/notes/s3token_auth_uri-490c1287d90b9df7.yaml
releasenotes/notes/tls-support-via-oslo-cache-51d744dd8a3f6ba0.yaml
releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml
@@ -148,6 +146,7 @@
releasenotes/source/2024.1.rst
releasenotes/source/2024.2.rst
releasenotes/source/2025.1.rst
+releasenotes/source/2025.2.rst
releasenotes/source/conf.py
releasenotes/source/index.rst
releasenotes/source/mitaka.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/entry_points.txt
new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/entry_points.txt
--- old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/entry_points.txt
2025-08-21 11:34:47.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/entry_points.txt
2026-02-17 15:49:50.000000000 +0100
@@ -5,8 +5,6 @@
[paste.filter_factory]
audit = keystonemiddleware.audit:filter_factory
auth_token = keystonemiddleware.auth_token:filter_factory
-ec2_token = keystonemiddleware.ec2_token:filter_factory
external_oauth2_token = keystonemiddleware.external_oauth2_token:filter_factory
oauth2_mtls_token = keystonemiddleware.oauth2_mtls_token:filter_factory
oauth2_token = keystonemiddleware.oauth2_token:filter_factory
-s3_token = keystonemiddleware.s3_token:filter_factory
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/pbr.json
new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/pbr.json
--- old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/pbr.json
2025-08-21 11:34:47.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/pbr.json
2026-02-17 15:49:50.000000000 +0100
@@ -1 +1 @@
-{"git_version": "c893b62", "is_release": true}
\ No newline at end of file
+{"git_version": "fe744ab", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/requires.txt
new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/requires.txt
--- old/keystonemiddleware-10.12.0/keystonemiddleware.egg-info/requires.txt
2025-08-21 11:34:47.000000000 +0200
+++ new/keystonemiddleware-12.0.0/keystonemiddleware.egg-info/requires.txt
2026-02-17 15:49:50.000000000 +0100
@@ -16,9 +16,11 @@
[audit_notifications]
oslo.messaging>=5.29.0
+[memcache_encryption]
+cryptography>=2.7
+
[test]
hacking~=6.1.0
-flake8-docstrings~=1.7.0
coverage>=4.0
cryptography>=3.0
fixtures>=3.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml
new/keystonemiddleware-12.0.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml
---
old/keystonemiddleware-10.12.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml
2025-08-21 11:33:29.000000000 +0200
+++
new/keystonemiddleware-12.0.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml
2026-02-17 15:48:39.000000000 +0100
@@ -1,8 +1,8 @@
----
-features:
- - |
- [`blueprint enhance-oauth2-interoperability
<https://blueprints.launchpad.net/keystone/+spec/enhance-oauth2-interoperability>`_]
- The external_oauth2_token filter has been added for accepting or denying
- incoming requests containing OAuth 2.0 access tokens that are obtained
- from an external authorization server by users through their OAuth 2.0
- credentials.
+---
+features:
+ - |
+ [`blueprint enhance-oauth2-interoperability
<https://blueprints.launchpad.net/keystone/+spec/enhance-oauth2-interoperability>`_]
+ The external_oauth2_token filter has been added for accepting or denying
+ incoming requests containing OAuth 2.0 access tokens that are obtained
+ from an external authorization server by users through their OAuth 2.0
+ credentials.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/notes/remove-ec2-s3-token-middleware-e0b9b63428224600.yaml
new/keystonemiddleware-12.0.0/releasenotes/notes/remove-ec2-s3-token-middleware-e0b9b63428224600.yaml
---
old/keystonemiddleware-10.12.0/releasenotes/notes/remove-ec2-s3-token-middleware-e0b9b63428224600.yaml
1970-01-01 01:00:00.000000000 +0100
+++
new/keystonemiddleware-12.0.0/releasenotes/notes/remove-ec2-s3-token-middleware-e0b9b63428224600.yaml
2026-02-17 15:48:39.000000000 +0100
@@ -0,0 +1,9 @@
+---
+upgrade:
+ - |
+ The ``ec2_token`` and ``s3_token`` middleware have been removed.
+ These middlewares were designed for use with Nova's EC2 API (removed in
+ Mitaka) and Swift's swift3 middleware (superseded by
swift.common.middleware.s3api).
+ Neither middleware has active consumers in current OpenStack deployments.
+ For EC2 API compatibility, use the standalone ec2-api project. For S3 API
+ compatibility with Swift, use Swift's built-in s3api and s3token
middleware.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/notes/s3token-remove-deprecated-opts-a58a7715b275a89c.yaml
new/keystonemiddleware-12.0.0/releasenotes/notes/s3token-remove-deprecated-opts-a58a7715b275a89c.yaml
---
old/keystonemiddleware-10.12.0/releasenotes/notes/s3token-remove-deprecated-opts-a58a7715b275a89c.yaml
1970-01-01 01:00:00.000000000 +0100
+++
new/keystonemiddleware-12.0.0/releasenotes/notes/s3token-remove-deprecated-opts-a58a7715b275a89c.yaml
2026-02-17 15:48:39.000000000 +0100
@@ -0,0 +1,10 @@
+---
+upgrade:
+ - |
+ The s3token middleware now requires the ``www_authenticate_uri`` option and
+ no longer loads the following deprecated options.
+
+ - ``auth_uri``
+ - ``auth_host``
+ - ``auth_port``
+ - ``auth_protocol``
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/source/2024.1.rst
new/keystonemiddleware-12.0.0/releasenotes/source/2024.1.rst
--- old/keystonemiddleware-10.12.0/releasenotes/source/2024.1.rst
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/releasenotes/source/2024.1.rst
2026-02-17 15:48:39.000000000 +0100
@@ -3,4 +3,4 @@
===========================
.. release-notes::
- :branch: stable/2024.1
+ :branch: unmaintained/2024.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/source/2025.2.rst
new/keystonemiddleware-12.0.0/releasenotes/source/2025.2.rst
--- old/keystonemiddleware-10.12.0/releasenotes/source/2025.2.rst
1970-01-01 01:00:00.000000000 +0100
+++ new/keystonemiddleware-12.0.0/releasenotes/source/2025.2.rst
2026-02-17 15:48:39.000000000 +0100
@@ -0,0 +1,6 @@
+===========================
+2025.2 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2025.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/keystonemiddleware-10.12.0/releasenotes/source/index.rst
new/keystonemiddleware-12.0.0/releasenotes/source/index.rst
--- old/keystonemiddleware-10.12.0/releasenotes/source/index.rst
2025-08-21 11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/releasenotes/source/index.rst 2026-02-17
15:48:39.000000000 +0100
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ 2025.2
2025.1
2024.2
2024.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/setup.cfg
new/keystonemiddleware-12.0.0/setup.cfg
--- old/keystonemiddleware-10.12.0/setup.cfg 2025-08-21 11:34:47.930037700
+0200
+++ new/keystonemiddleware-12.0.0/setup.cfg 2026-02-17 15:49:50.080432700
+0100
@@ -28,6 +28,8 @@
[extras]
audit_notifications =
oslo.messaging>=5.29.0 # Apache-2.0
+memcache_encryption =
+ cryptography>=2.7 # BSD/Apache-2.0
[entry_points]
oslo.config.opts =
@@ -36,8 +38,6 @@
paste.filter_factory =
auth_token = keystonemiddleware.auth_token:filter_factory
audit = keystonemiddleware.audit:filter_factory
- ec2_token = keystonemiddleware.ec2_token:filter_factory
- s3_token = keystonemiddleware.s3_token:filter_factory
oauth2_token = keystonemiddleware.oauth2_token:filter_factory
oauth2_mtls_token = keystonemiddleware.oauth2_mtls_token:filter_factory
external_oauth2_token =
keystonemiddleware.external_oauth2_token:filter_factory
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/setup.py
new/keystonemiddleware-12.0.0/setup.py
--- old/keystonemiddleware-10.12.0/setup.py 2025-08-21 11:33:29.000000000
+0200
+++ new/keystonemiddleware-12.0.0/setup.py 2026-02-17 15:48:39.000000000
+0100
@@ -13,17 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
-# In python < 2.7.4, a lazy loading of package `pbr` will break
-# setuptools if some other modules registered functions in `atexit`.
-# solution from: http://bugs.python.org/issue15881#msg170215
-try:
- import multiprocessing # noqa
-except ImportError:
- pass
-
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/test-requirements.txt
new/keystonemiddleware-12.0.0/test-requirements.txt
--- old/keystonemiddleware-10.12.0/test-requirements.txt 2025-08-21
11:33:29.000000000 +0200
+++ new/keystonemiddleware-12.0.0/test-requirements.txt 2026-02-17
15:48:39.000000000 +0100
@@ -1,5 +1,4 @@
hacking~=6.1.0 # Apache-2.0
-flake8-docstrings~=1.7.0 # MIT
coverage>=4.0 # Apache-2.0
cryptography>=3.0 # BSD/Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/keystonemiddleware-10.12.0/tox.ini
new/keystonemiddleware-12.0.0/tox.ini
--- old/keystonemiddleware-10.12.0/tox.ini 2025-08-21 11:33:29.000000000
+0200
+++ new/keystonemiddleware-12.0.0/tox.ini 2026-02-17 15:48:39.000000000
+0100
@@ -20,11 +20,6 @@
flake8
bandit -r keystonemiddleware -x tests -n5
-[testenv:bandit]
-# NOTE(browne): This is required for the integration test job of the bandit
-# project. Please do not remove.
-commands = bandit -r keystonemiddleware -x tests -n5
-
[testenv:venv]
commands = {posargs}