oslo.middleware-3.34.0/0000775000175100017510000000000013227207404014716 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/HACKING.rst0000666000175100017510000000023013227207123016507 0ustar zuulzuul00000000000000oslo.middleware Style Commandments ================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ oslo.middleware-3.34.0/test-requirements.txt0000666000175100017510000000111013227207123021150 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. fixtures>=3.0.0 # Apache-2.0/BSD hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 mock>=2.0.0 # BSD openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 sphinx>=1.6.2 # BSD testtools>=2.2.0 # MIT coverage!=4.4,>=4.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 oslo.middleware-3.34.0/CONTRIBUTING.rst0000666000175100017510000000104413227207123017356 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/oslo.middleware oslo.middleware-3.34.0/ChangeLog0000664000175100017510000003314213227207403016472 0ustar zuulzuul00000000000000CHANGES ======= 3.34.0 ------ * Avoid tox\_install.sh for constraints support * Updated from global requirements * add bandit to pep8 job 3.33.0 ------ * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata 3.32.1 ------ 3.32.0 ------ * Updated from global requirements * Updated from global requirements * Remove Class HTTPProxyToWSGIMiddleware 3.31.0 ------ * Imported Translations from Zanata * Invalid parsing of Forwarded header fixed * Remove method set\_latent * Updated from global requirements * Updated from global requirements * Fix exception in json response under py35 * Update reno for stable/pike * Updated from global requirements * doc: Specify openstackdocs theme 3.30.0 ------ * Update URLs in documents according to document migration 3.29.2 ------ * update the docs url in the readme 3.29.1 ------ * switch from oslosphinx to openstackdocstheme * Replace deprecated inspect function in Python 3 * import admin guide content from openstack-manuals * rearrange content to fit the new standard layout * Remove deprecated oslo.subdomain from CORS opts 3.29.0 ------ * Updated from global requirements * Remove pbr warnerrors in favor of sphinx check 3.28.0 ------ * Updated from global requirements * Updated from global requirements * Remove usage of parameter enforce\_type * Updated from global requirements 3.27.0 ------ * Set global\_request\_id if passed in as X-OpenStack-Request-ID * Allow compatibility headers for request\_id * Updated from global requirements * Updated from global requirements * Updated from global requirements 3.26.0 ------ * Updated from global requirements * Optimize the link address * Fix test\_request\_too\_large\_no\_content\_length failure 3.25.0 ------ * Remove log translations * Updated from global requirements 3.24.0 ------ * Updated from global requirements * [Fix gate]Update test requirement * Updated from global requirements * pbr.version.VersionInfo needs package name (oslo.xyz and not oslo\_xyz) * Update reno for stable/ocata 3.23.1 ------ * Filter token data out of catch\_errors middleware 3.23.0 ------ * remove superfluous random stuff * Updated from global requirements * Remove references to Python 3.4 * Add new middleware to send API data to statsd * Add Constraints support * Replace six.iteritems() with .items() * Replaces uuid.uuid4 with uuidutils.generate\_uuid() 3.22.0 ------ * Transform healthcheck from filter to an application * Filter X-Auth-Token in catch\_errors 3.21.0 ------ * Suggest to use egg based filter\_factory rather than fragile full path * Document how to use detailed mode in healthcheck * Remove self-referential link in docs * Add Python 3.5 classifier and venv * Enable release notes translation * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Allow healthcheck to be configured by oslo.config * Updated from global requirements * Replace 'assertEqual(None, ...)' with 'assertIsNone(...)' * TrivialFix: Remove default=None when set value in Config 3.20.0 ------ * Limit ssl deprecation warning to external importers * Changed the home-page link * Updated from global requirements * Updated from global requirements * Updated from global requirements * make sure we handle the forwarded for headers 3.19.0 ------ * Show more healthcheck examples * Remove pot files * Fix inline docstring to use default path (not /status) * Updated from global requirements 3.18.0 ------ * Updated from global requirements * Fixed typo in SSL 3.17.0 ------ * Updated from global requirements * Replace deprecated LOG.warn with LOG.warning 3.16.0 ------ * Updated from global requirements * Fix parameters of assertEqual are misplaced * Updated from global requirements * Fix unit tests on Windows 3.15.0 ------ * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Updated from global requirements 3.14.0 ------ * Updated from global requirements 3.13.0 ------ * Fix spelling of config option help * Expose sample config opts for http-proxy-to-wsgi 3.12.0 ------ * Updated from global requirements * Updated from global requirements * Do not add a default content type when replying 3.11.0 ------ * cors: remove unused import * Updated from global requirements * Add a simple \_\_main\_\_ to easily show healthcheck output * Deprecate using String as valid value for allowed\_origin * Deprecate multiple config block parsing * Deprecated set\_latent 3.10.0 ------ * Removed simple headers from documentation examples * Added PATCH method to default config * Added set\_defaults method to oslo\_middleware.cors * Set default allow methods to those defined in RFC 2616 * Removed default headers from CORS\_OPTS 3.9.0 ----- * Imported Translations from Zanata * Updated from global requirements * Remove direct dependency on babel * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * cors: prevent WebOb setting a default Content-Type * CORS Middleware now honors upstream Vary header * Disable http\_proxy\_to\_wsgi middleware by default * CORS tests now use a transient configuration * Updated config documentation for cors\_middleware * Updated from global requirements * Updated from global requirements * Revert "work around doc build error" 3.7.0 ----- * work around doc build error * Clean up removed hacking rule from [flake8] ignore lists 3.6.0 ----- * Update translation setup * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Remove ordereddict from requirements.txt * Updated from global requirements 3.5.0 ----- * Remove old references to namespace packages * test: pass enforce\_type=True when using CONF.set\_override * Updated from global requirements * Change LOG.warn to LOG.warning * Fix the url in the CONTRIBUTING.rst * Python 3 deprecated the logger.warn method in favor of warning * assertIsNone(val) instead of assertEqual(None,val) 3.4.0 ----- * Updated from global requirements 3.3.0 ----- * [fix-compat] More definitions for oslo.middleware * Updated from global requirements 3.2.0 ----- * Re-Add oslo.middleware namespace for backward compat * Removes MANIFEST.in as it is not needed explicitely by PBR 3.1.0 ----- * Support Unicode request\_id on Python 3 * Drop python 2.6 support 3.0.0 ----- * Move cors allowed\_origin check into add\_origin * Backward compat for allowed\_origin * Updated from global requirements * Updated from global requirements * remove bad entry point test logic * Remove python 2.6 classifier * Updated from global requirements * Switched StrOpt to ListOpt in CORS allowed\_origins * Enable latent CORS configuration via pastedeploy * Remove python 2.6 and cleanup tox.ini * Add oslo\_config program support to paste middleware * Define entry points for filter factories for Paste Deployment 2.11.0 ------ * Remove oslo namespace package * add missing shortcut for HTTPProxyToWSGI middleware * Revert "Add 'X-Auth-Token' default header to CORS middleware" * Updated from global requirements * Imported Translations from Zanata * Add a disabled by ports -> files healthcheck plugin * Add 'X-Auth-Token' default header to CORS middleware * Updated from global requirements 2.10.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements 2.9.0 ----- * Fix coverage configuration and execution * No need for Oslo Incubator Sync * Allow health check results to provide there own details * ssl: deprecated in favor of http\_proxy\_to\_wsgi * Introduce HTTP Proxy to WSGI middleware * Imported Translations from Zanata * Fix string formatting * Fix typos in docstring * Include changelog/history in docs * Add shields.io version/downloads links/badges into README.rst * Change ignore-errors to ignore\_errors * Fix the home-page in setup.cfg with Oslo wiki page * Add plugin doco generated with stevedore.sphinxext * Avoid any body when request is a HEAD request * Further improve the healthcheck output * Allow the healthcheck middleware to provide more detailed responses * Updated from global requirements * Imported Translations from Zanata * cors: fix wildcard in actual request * cors: fix filter with oslo\_config\_project is set 2.8.0 ----- * Split option discovery function by middleware * Updated from global requirements * Updated from global requirements 2.7.0 ----- * Fix remarks missed on #209817 2.6.1 ----- * Added new test for CORS Simple Headers * Restore backward compat of paste factory 2.6.0 ----- * Do not dump HealthcheckResult class source * Updated from global requirements * Renamed latent\_configuration to \_latent\_configuration * Added latent properties to CORS middleware * Declare some methods static * Allow to get option from paste-deploy * Remove usage of oslo.config global * Imported Translations from Transifex * Updated from global requirements 2.5.0 ----- * Imported Translations from Transifex * Updated from global requirements * Imported Translations from Transifex * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added verbose debug logging to CORS 2.4.0 ----- * Fix mocking for 1.1.0 * Updated from global requirements * Imported Translations from Transifex * Updated from global requirements * Support PasteDeploy * Add tox target to find missing requirements 2.3.0 ----- * Drop use of 'oslo' namespace package 2.2.0 ----- * CORS Middleware defers to server response * Updated from global requirements * Drop use of 'oslo' namespace package * Use correct oslo\_middleware.base methods in CORS middleware * Added request to parse\_response * Updated from global requirements * Updated from global requirements 2.1.0 ----- * Revert "Remove oslo namespace package" * Add middleware to support ssl termination proxies 2.0.0 ----- 1.3.0 ----- * Added CORS wildcard handling * Drop use of 'oslo' namespace package * Remove oslo namespace package * Updated from global requirements * Advertise support for Python3.4 / Remove support for Python 3.3 * Remove run\_cross\_tests.sh 1.2.0 ----- * Imported Translations from Transifex * Update CORS tests to use config fixture's load\_raw\_values * Updated from global requirements * Update response body when healthcheck is available 1.1.0 ----- * Uncap library requirements for liberty * Add CORS Middleware for Oslo * Update to latest hacking * Updated from global requirements 1.0.0 ----- * Updated from global requirements 0.5.0 ----- * Update the list of packages in setup.cfg * Updated from global requirements * Adds missing files 0.4.0 ----- * Fixes the healthcheck factory method and docs * add shortcut to healthcheck middleware * Updated from global requirements * Move i18n module to a private name * Update Oslo imports to remove namespace package * Add healthcheck middleware * Updated from global requirements * Fix bug tracker link in readme 0.3.0 ----- * Move files out of the namespace package * Don't use default value in LimitingReader * switch to oslo.context * Workflow documentation is now in infra-manual 0.2.0 ----- * Updated from global requirements * Updated from global requirements * Flesh out the README * Imported Translations from Transifex * Updated from global requirements * Add pbr to installation requirements * Updated from global requirements * Updated from global requirements * Remove extraneous vim editor configuration comments * Imported Translations from Transifex * Support building wheels (PEP-427) * Fix coverage testing * Expose sizelimit option to config generator * Imported Translations from Transifex * Imported Translations from Transifex * Updated from global requirements * Imported Translations from Transifex * Remove oslo-incubator fixture 0.1.0 ----- * Updated from global requirements * Setup for translation * Work toward Python 3.4 support and testing * warn against sorting requirements * Update docs for first release * expose middleware through oslo.middleware * add doc generation support * drop middleware suffix in class names * fix pep8 issues * Fixed syntax of the HACKING.rst file * Enable hacking check H305 * Cleaning up index.rst file * replace with oslo.i18n * remove stray tests * initial oslo.middleware checkin * exported from oslo-incubator by graduate.sh * pep8: fixed multiple violations * Use moxstubout and mockpatch from oslotest * Improve help strings * Use oslotest instead of common test module * sizelimit: port to Python 3 * Don't store the request ID value in middleware as class variable * Python 3: enable tests/unit/middleware/test\_request\_id.py * Update oslo log messages with translation domains * Small edits on help strings * Middleware to catch all error in WSGI pipeline * Use hacking import\_exceptions for gettextutils.\_ * Middlelware to ensure request ID * middleware.sizelimit: stop importing wsgi * Use six.iteritems to make dict work on Python2/3 * Trivial: Make vertical white space after license header consistent * Remove vim header * Remove uuidutils imports in oslo modules * Revert "Removes generate\_uuid from uuidutils" * Removes generate\_uuid from uuidutils * Replace using tests.utils part1 * Bump hacking to 0.7.0 * middleware: code cleanup * Enable H302 hacking check * Enable hacking H404 test * Move wsgi module to deprecated package * python3: python3 binary/text data compatbility * Use stubout in test\_correlation\_id * Add Middleware for correlation\_id * Add middleware to limit size of HTTP requests * Initial skeleton project oslo.middleware-3.34.0/requirements.txt0000666000175100017510000000107313227207123020203 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause) oslo.config>=5.1.0 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 WebOb>=1.7.1 # MIT debtcollector>=1.2.0 # Apache-2.0 statsd>=3.2.1 # MIT oslo.middleware-3.34.0/LICENSE0000666000175100017510000002363613227207123015735 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. oslo.middleware-3.34.0/AUTHORS0000664000175100017510000000616513227207403015775 0ustar zuulzuul00000000000000Abhijeet Malawade Adam Kijak Aditi Raveesh Akihiro Motoki Akihiro Motoki Andreas Jaeger Andreas Jaeger Ann Kamyshnikova Brant Knudson Cedric Brandily Chang Bo Guo ChangBo Guo(gcb) Chaozhe.Chen Chris Buccella Christian Berendt Chuck Short Craige McWhirter Cyril Roelandt Dan Prince Davanum Srinivas (dims) Davanum Srinivas Davanum Srinivas Dina Belova Dong Ma Doug Hellmann Doug Hellmann Eric Brown Flavio Percoco Gabriel Adrian Samfira Gordon Chung Guang Yee Haifeng.Yan Henry Gessau Ihar Hrachyshka Jamie Lennox Javeme Jay Pipes Jeremy Stanley JiaJunsu Joe Gordon Joshua Harlow Joshua Harlow Joshua Harlow Juan Antonio Osorio Robles Julian Edwards Julien Danjou Kamil Rykowski Kirill Bespalov Maciej Szankin Mark McLoughlin Matt Fischer Mehdi Abaakouk Mehdi Abaakouk Michael Krotscheck Monty Taylor Noorul Islam K M OpenStack Release Bot Ronald Bradford Samuel de Medeiros Queiroz Sean Dague Sergey Kraynev Thomas Goirand Thomas Goirand Timur Sufiev Tom Cocozzello Tony Breeds Victor Sergeyev Victor Stinner Vu Cong Tuan Yujun Zhang Zhihai Song Zhongyue Luo Zuul chenghuiyu gengchc2 gord chung gordon chung howardlee lioplhp melissaml ricolin sonu.kumar venkatamahesh yan.haifeng zhangguoqing oslo.middleware-3.34.0/PKG-INFO0000664000175100017510000000345413227207404016021 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.middleware Version: 3.34.0 Summary: Oslo Middleware library Home-page: https://docs.openstack.org/oslo.middleware/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: =================================== oslo.middleware =================================== .. image:: https://img.shields.io/pypi/v/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Downloads Oslo middleware library includes components that can be injected into wsgi pipelines to intercept request/response flows. The base class can be enhanced with functionality like add/delete/modification of http headers and support for limiting size/connection etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.middleware/latest * Source: https://git.openstack.org/cgit/openstack/oslo.middleware * Bugs: https://bugs.launchpad.net/oslo.middleware Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.middleware-3.34.0/tox.ini0000666000175100017510000000270013227207123016230 0ustar zuulzuul00000000000000[tox] minversion = 2.0 envlist = py35,py27,pypy,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 # Run security linter bandit -r oslo_middleware -x tests -n5 [testenv:venv] commands = {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=oslo_middleware --testr-args='{posargs}' [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py [hacking] import_exceptions = oslo_middleware._i18n [testenv:pip-missing-reqs] # do not install test-requirements as that will pollute the virtualenv for # determining missing packages # this also means that pip-missing-reqs must be installed separately, outside # of the requirements.txt files deps = pip_missing_reqs commands = pip-missing-reqs -d --ignore-module=oslo_middleware* --ignore-module=pkg_resources --ignore-file=oslo_middleware/tests/* oslo_middleware [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html oslo.middleware-3.34.0/oslo/0000775000175100017510000000000013227207404015672 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo/__init__.py0000666000175100017510000000116513227207123020006 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __import__('pkg_resources').declare_namespace(__name__) oslo.middleware-3.34.0/oslo/middleware/0000775000175100017510000000000013227207404020007 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo/middleware/__init__.py0000666000175100017510000000362613227207123022127 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys import warnings def deprecated(): new_name = __name__.replace('.', '_') warnings.warn( ('The oslo namespace package is deprecated. Please use %s instead.' % new_name), DeprecationWarning, stacklevel=3, ) # NOTE(dims): We cannot remove the deprecation or redirects below # until Liberty-EOL deprecated() from oslo_middleware import base from oslo_middleware import catch_errors from oslo_middleware import correlation_id from oslo_middleware import debug from oslo_middleware import request_id from oslo_middleware import sizelimit sys.modules['oslo.middleware.base'] = base sys.modules['oslo.middleware.catch_errors'] = catch_errors sys.modules['oslo.middleware.correlation_id'] = correlation_id sys.modules['oslo.middleware.debug'] = debug sys.modules['oslo.middleware.request_id'] = request_id sys.modules['oslo.middleware.sizelimit'] = sizelimit from oslo_middleware.catch_errors import CatchErrors from oslo_middleware.correlation_id import CorrelationId from oslo_middleware.cors import CORS from oslo_middleware.debug import Debug from oslo_middleware.healthcheck import Healthcheck from oslo_middleware.http_proxy_to_wsgi import HTTPProxyToWSGI from oslo_middleware.request_id import RequestId from oslo_middleware.sizelimit import RequestBodySizeLimiter from oslo_middleware.ssl import SSLMiddlewareoslo.middleware-3.34.0/.mailmap0000666000175100017510000000013013227207123016331 0ustar zuulzuul00000000000000# Format is: # # oslo.middleware-3.34.0/releasenotes/0000775000175100017510000000000013227207404017407 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/0000775000175100017510000000000013227207404020707 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/conf.py0000666000175100017510000002167513227207123022221 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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 file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # 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.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'reno.sphinxext', 'openstackdocstheme' ] # openstackdocstheme options repository_name = 'openstack/oslo.middleware' bug_project = 'oslo.middleware' bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.middleware Release Notes' copyright = u'2016, oslo.middleware Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'oslo.middlewareReleaseNotesDoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'oslo.middlewareReleaseNotes.tex', u'oslo.middleware Release Notes Documentation', u'oslo.middleware Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oslo.middlewareReleaseNotes', u'oslo.middleware Release Notes Documentation', [u'oslo.middleware Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'oslo.middlewareReleaseNotes', u'oslo.middleware Release Notes Documentation', u'oslo.middleware Developers', 'oslo.middlewareReleaseNotes', 'The library includes components that can be injected into wsgi pipelines' ' to intercept request/response flows.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] oslo.middleware-3.34.0/releasenotes/source/locale/0000775000175100017510000000000013227207404022146 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/locale/en_GB/0000775000175100017510000000000013227207404023120 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013227207404024705 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000666000175100017510000000425613227207123027745 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.middleware Release Notes 3.32.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-09-25 17:24+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-07 09:28+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "3.20.0" msgstr "3.20.0" msgid "3.27.0" msgstr "3.27.0" msgid "New Features" msgstr "New Features" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Switch to reno for managing release notes." msgstr "Switch to Reno for managing release notes." msgid "" "This adds a new ``compat_headers`` class attribute to the ``RequestId`` " "middleware. That allows projects like Nova that have API contracts on " "alternative request-id headers to adopt the oslo ``RequestId`` middleware " "but still retain their API contract." msgstr "" "This adds a new ``compat_headers`` class attribute to the ``RequestId`` " "middleware. That allows projects like Nova that have API contracts on " "alternative request-id headers to adopt the oslo ``RequestId`` middleware " "but still retain their API contract." msgid "" "This adds support for ``global_request_id`` to the ``RequestId`` middleware. " "An inbound header of ``X-OpenStack-Request-ID`` is accepted as long as it is " "of the format ``req-$uuid``, and made available to oslo.context. This will " "allow for cross project request id tracking." msgstr "" "This adds support for ``global_request_id`` to the ``RequestId`` middleware. " "An inbound header of ``X-OpenStack-Request-ID`` is accepted as long as it is " "of the format ``req-$uuid``, and made available to oslo.context. This will " "allow for cross project request id tracking." msgid "Unreleased Release Notes" msgstr "Unreleased Release Notes" msgid "oslo.middleware Release Notes" msgstr "oslo.middleware Release Notes" oslo.middleware-3.34.0/releasenotes/source/locale/fr/0000775000175100017510000000000013227207404022555 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175100017510000000000013227207404024342 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000666000175100017510000000155213227207123027376 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.middleware Release Notes 3.20.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-11-02 02:56+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:02+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "Other Notes" msgstr "Autres notes" msgid "Switch to reno for managing release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" msgid "Unreleased Release Notes" msgstr "Note de release pour les changements non déployées" msgid "oslo.middleware Release Notes" msgstr "Note de release pour oslo.middleware" oslo.middleware-3.34.0/releasenotes/source/_static/0000775000175100017510000000000013227207404022335 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013227207123024606 0ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/unreleased.rst0000666000175100017510000000014413227207123023567 0ustar zuulzuul00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: oslo.middleware-3.34.0/releasenotes/source/index.rst0000666000175100017510000000023513227207123022550 0ustar zuulzuul00000000000000============================= oslo.middleware Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased pike ocata oslo.middleware-3.34.0/releasenotes/source/ocata.rst0000666000175100017510000000023013227207123022523 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata oslo.middleware-3.34.0/releasenotes/source/_templates/0000775000175100017510000000000013227207404023044 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013227207123025315 0ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/source/pike.rst0000666000175100017510000000021713227207123022371 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike oslo.middleware-3.34.0/releasenotes/notes/0000775000175100017510000000000013227207404020537 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/releasenotes/notes/compat_headers-55a635b8ec01b6f1.yaml0000666000175100017510000000044113227207123026610 0ustar zuulzuul00000000000000--- features: - | This adds a new ``compat_headers`` class attribute to the ``RequestId`` middleware. That allows projects like Nova that have API contracts on alternative request-id headers to adopt the oslo ``RequestId`` middleware but still retain their API contract. oslo.middleware-3.34.0/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml0000666000175100017510000000007113227207123025420 0ustar zuulzuul00000000000000--- other: - Switch to reno for managing release notes.oslo.middleware-3.34.0/releasenotes/notes/global_request_id-a8ec7260fbd76444.yaml0000666000175100017510000000047213227207123027334 0ustar zuulzuul00000000000000--- features: - | This adds support for ``global_request_id`` to the ``RequestId`` middleware. An inbound header of ``X-OpenStack-Request-ID`` is accepted as long as it is of the format ``req-$uuid``, and made available to oslo.context. This will allow for cross project request id tracking. oslo.middleware-3.34.0/.coveragerc0000666000175100017510000000017113227207123017036 0ustar zuulzuul00000000000000[run] branch = True source = oslo_middleware omit = oslo_middleware/tests/* [report] ignore_errors = True precision = 2 oslo.middleware-3.34.0/oslo_middleware/0000775000175100017510000000000013227207404020067 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/debug.py0000666000175100017510000000333513227207123021533 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Debug middleware""" from __future__ import print_function import sys import webob.dec from oslo_middleware import base class Debug(base.ConfigurableMiddleware): """Helper class that returns debug information. Can be inserted into any WSGI application chain to get information about the request and response. """ @webob.dec.wsgify def __call__(self, req): print(("*" * 40) + " REQUEST ENVIRON") for key, value in req.environ.items(): print(key, "=", value) print() resp = req.get_response(self.application) print(("*" * 40) + " RESPONSE HEADERS") for (key, value) in resp.headers.items(): print(key, "=", value) print() resp.app_iter = self.print_generator(resp.app_iter) return resp @staticmethod def print_generator(app_iter): """Prints the contents of a wrapper string iterator when iterated.""" print(("*" * 40) + " BODY") for part in app_iter: sys.stdout.write(part) sys.stdout.flush() yield part print() oslo.middleware-3.34.0/oslo_middleware/request_id.py0000666000175100017510000000442713227207123022614 0ustar zuulzuul00000000000000# Copyright (c) 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from oslo_context import context import webob.dec from oslo_middleware import base ENV_REQUEST_ID = 'openstack.request_id' GLOBAL_REQ_ID = 'openstack.global_request_id' HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id' INBOUND_HEADER = 'X-Openstack-Request-Id' ID_FORMAT = (r'^req-[a-f0-9]{8}-[a-f0-9]{4}-' r'[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$') class RequestId(base.ConfigurableMiddleware): """Middleware that ensures request ID. It ensures to assign request ID for each API request and set it to request environment. The request ID is also added to API response. """ # if compat_headers is set, we also return the request_id in those # headers as well. This allows projects like Nova to adopt # oslo.middleware without impacting existing users. compat_headers = [] def set_global_req_id(self, req): gr_id = req.headers.get(INBOUND_HEADER, "") if re.match(ID_FORMAT, gr_id): req.environ[GLOBAL_REQ_ID] = gr_id # TODO(sdague): it would be nice to warn if we dropped a bogus # request_id, but the infrastructure for doing that isn't yet # setup at this stage. @webob.dec.wsgify def __call__(self, req): self.set_global_req_id(req) req_id = context.generate_request_id() req.environ[ENV_REQUEST_ID] = req_id response = req.get_response(self.application) return_headers = [HTTP_RESP_HEADER_REQUEST_ID] return_headers.extend(self.compat_headers) for header in return_headers: if header not in response.headers: response.headers.add(header, req_id) return response oslo.middleware-3.34.0/oslo_middleware/tests/0000775000175100017510000000000013227207404021231 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/tests/test_catch_errors.py0000666000175100017510000000533113227207123025322 0ustar zuulzuul00000000000000# Copyright (c) 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import mock from oslotest import base as test_base import webob.dec import webob.exc from oslo_middleware import catch_errors class CatchErrorsTest(test_base.BaseTestCase): def _test_has_request_id(self, application, expected_code=None): app = catch_errors.CatchErrors(application) req = webob.Request.blank('/test') req.environ['HTTP_X_AUTH_TOKEN'] = 'hello=world' res = req.get_response(app) self.assertEqual(expected_code, res.status_int) def test_success_response(self): @webob.dec.wsgify def application(req): return 'Hello, World!!!' self._test_has_request_id(application, webob.exc.HTTPOk.code) def test_internal_server_error(self): @webob.dec.wsgify def application(req): raise Exception() with mock.patch.object(catch_errors.LOG, 'exception') as log_exc: self._test_has_request_id(application, webob.exc.HTTPInternalServerError.code) self.assertEqual(1, log_exc.call_count) req_log = log_exc.call_args[0][1] self.assertIn('X-Auth-Token: *****', str(req_log)) def test_filter_tokens_from_log(self): logger = self.useFixture(fixtures.FakeLogger(nuke_handlers=False)) @webob.dec.wsgify def application(req): raise Exception() app = catch_errors.CatchErrors(application) req = webob.Request.blank('/test', text=u'test data', method='POST', headers={'X-Auth-Token': 'secret1', 'X-Service-Token': 'secret2', 'X-Other-Token': 'secret3'}) res = req.get_response(app) self.assertEqual(500, res.status_int) output = logger.output self.assertIn('X-Auth-Token: *****', output) self.assertIn('X-Service-Token: *****', output) self.assertIn('X-Other-Token: *****', output) self.assertIn('test data', output) oslo.middleware-3.34.0/oslo_middleware/tests/test_healthcheck.py0000666000175100017510000002067513227207123025117 0ustar zuulzuul00000000000000# Copyright (c) 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import threading import time import mock from oslo_config import fixture as config from oslo_serialization import jsonutils from oslotest import base as test_base import requests import webob.dec import webob.exc from oslo_middleware import healthcheck from oslo_middleware.healthcheck import __main__ class HealthcheckMainTests(test_base.BaseTestCase): def test_startup_response(self): server = __main__.create_server(0) th = threading.Thread(target=server.serve_forever) th.start() self.addCleanup(server.shutdown) while True: try: # Connecting on 0.0.0.0 is not allowed on windows # The operating system will return WSAEADDRNOTAVAIL which # in turn will throw a requests.ConnectionError r = requests.get("http://127.0.0.1:%s" % ( server.server_address[1])) except requests.ConnectionError: # Server hasn't started up yet, try again in a few. time.sleep(1) else: self.assertEqual(200, r.status_code) break class HealthcheckTests(test_base.BaseTestCase): def setUp(self): super(HealthcheckTests, self).setUp() self.useFixture(config.Config()) @staticmethod @webob.dec.wsgify def application(req): return 'Hello, World!!!' def _do_test_request(self, conf={}, path='/healthcheck', accept='text/plain', method='GET', server_port=80): self.app = healthcheck.Healthcheck(self.application, conf) req = webob.Request.blank(path, accept=accept, method=method) req.server_port = server_port res = req.get_response(self.app) return res def _do_test(self, conf={}, path='/healthcheck', expected_code=webob.exc.HTTPOk.code, expected_body=b'', accept='text/plain', method='GET', server_port=80): res = self._do_test_request(conf=conf, path=path, accept=accept, method=method, server_port=server_port) self.assertEqual(expected_code, res.status_int) self.assertEqual(expected_body, res.body) def test_default_path_match(self): self._do_test() def test_default_path_not_match(self): self._do_test(path='/toto', expected_body=b'Hello, World!!!') def test_configured_path_match(self): conf = {'path': '/hidden_healthcheck'} self._do_test(conf, path='/hidden_healthcheck') def test_configured_path_not_match(self): conf = {'path': '/hidden_healthcheck'} self._do_test(conf, path='/toto', expected_body=b'Hello, World!!!') @mock.patch('oslo_middleware.healthcheck.disable_by_file.LOG') def test_disablefile_unconfigured(self, fake_log): fake_warn = fake_log.warning conf = {'backends': 'disable_by_file'} self._do_test(conf, expected_body=b'OK') self.assertIn('disable_by_file', self.app._backends.names()) fake_warn.assert_called_once_with( 'DisableByFile healthcheck middleware ' 'enabled without disable_by_file_path ' 'set' ) def test_disablefile_enabled(self): conf = {'backends': 'disable_by_file', 'disable_by_file_path': '/foobar'} self._do_test(conf, expected_body=b'OK') self.assertIn('disable_by_file', self.app._backends.names()) def test_disablefile_enabled_head(self): conf = {'backends': 'disable_by_file', 'disable_by_file_path': '/foobar'} self._do_test(conf, expected_body=b'', method='HEAD', expected_code=webob.exc.HTTPNoContent.code) def test_disablefile_enabled_html_detailed(self): conf = {'backends': 'disable_by_file', 'disable_by_file_path': '/foobar', 'detailed': True} res = self._do_test_request(conf, accept="text/html") self.assertIn(b'Result of 1 checks:', res.body) self.assertIn(b'OK', res.body) self.assertEqual(webob.exc.HTTPOk.code, res.status_int) def test_disablefile_disabled(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_file', 'disable_by_file_path': filename} self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'DISABLED BY FILE') self.assertIn('disable_by_file', self.app._backends.names()) def test_disablefile_disabled_head(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_file', 'disable_by_file_path': filename} self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'', method='HEAD') self.assertIn('disable_by_file', self.app._backends.names()) def test_disablefile_disabled_html_detailed(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_file', 'disable_by_file_path': filename, 'detailed': True} res = self._do_test_request(conf, accept="text/html") self.assertIn(b'DISABLED BY FILE', res.body) self.assertEqual(webob.exc.HTTPServiceUnavailable.code, res.status_int) def test_two_backends(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_file,disable_by_file', 'disable_by_file_path': filename} self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'DISABLED BY FILE\nDISABLED BY FILE') self.assertIn('disable_by_file', self.app._backends.names()) def test_disable_by_port_file(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_files_ports', 'disable_by_file_paths': "80:%s" % filename} self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'DISABLED BY FILE') self.assertIn('disable_by_files_ports', self.app._backends.names()) def test_no_disable_by_port_file(self): filename = self.create_tempfiles([('test', 'foobar')])[0] conf = {'backends': 'disable_by_files_ports', 'disable_by_file_paths': "8000:%s" % filename} self._do_test(conf, expected_code=webob.exc.HTTPOk.code, expected_body=b'OK') self.assertIn('disable_by_files_ports', self.app._backends.names()) def test_disable_by_port_many_files(self): filename = self.create_tempfiles([('test', 'foobar')])[0] filename2 = self.create_tempfiles([('test2', 'foobar2')])[0] conf = {'backends': 'disable_by_files_ports', 'disable_by_file_paths': "80:%s,81:%s" % (filename, filename2)} self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'DISABLED BY FILE') self._do_test(conf, expected_code=webob.exc.HTTPServiceUnavailable.code, expected_body=b'DISABLED BY FILE', server_port=81) self.assertIn('disable_by_files_ports', self.app._backends.names()) def test_json_response(self): expected_body = jsonutils.dumps({'detailed': False, 'reasons': []}, indent=4, sort_keys=True).encode('utf-8') self._do_test(expected_body=expected_body, accept='application/json') oslo.middleware-3.34.0/oslo_middleware/tests/test_entry_points.py0000666000175100017510000000257613227207123025411 0ustar zuulzuul00000000000000# 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 oslotest import base import stevedore from testtools import matchers class TestPasteDeploymentEntryPoints(base.BaseTestCase): def test_entry_points(self): factory_classes = { 'catch_errors': 'CatchErrors', 'correlation_id': 'CorrelationId', 'cors': 'CORS', 'debug': 'Debug', 'healthcheck': 'Healthcheck', 'http_proxy_to_wsgi': 'HTTPProxyToWSGI', 'request_id': 'RequestId', 'sizelimit': 'RequestBodySizeLimiter', 'ssl': 'SSLMiddleware', } em = stevedore.ExtensionManager('paste.filter_factory') # Ensure all the factories are defined by their names factory_names = [extension.name for extension in em] self.assertThat(factory_names, matchers.ContainsAll(factory_classes)) oslo.middleware-3.34.0/oslo_middleware/tests/test_ssl.py0000666000175100017510000000410713227207123023445 0ustar zuulzuul00000000000000# Copyright (c) 2015 Thales Services SAS # 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. from oslo_config import fixture as config from oslotest import base import webob from oslo_middleware import ssl class SSLMiddlewareTest(base.BaseTestCase): def setUp(self): super(SSLMiddlewareTest, self).setUp() self.useFixture(config.Config()) def _test_scheme(self, expected, headers, secure_proxy_ssl_header=None): middleware = ssl.SSLMiddleware(None) if secure_proxy_ssl_header: middleware.oslo_conf.set_override( 'secure_proxy_ssl_header', secure_proxy_ssl_header, group='oslo_middleware') request = webob.Request.blank('http://example.com/', headers=headers) # Ensure ssl middleware does not stop pipeline execution self.assertIsNone(middleware.process_request(request)) self.assertEqual(expected, request.scheme) def test_without_forwarded_protocol(self): self._test_scheme('http', {}) def test_with_forwarded_protocol(self): headers = {'X-Forwarded-Proto': 'https'} self._test_scheme('https', headers) def test_with_custom_header(self): headers = {'X-Forwarded-Proto': 'https'} self._test_scheme('http', headers, secure_proxy_ssl_header='X-My-Header') def test_with_custom_header_and_forwarded_protocol(self): headers = {'X-My-Header': 'https'} self._test_scheme('https', headers, secure_proxy_ssl_header='X-My-Header') oslo.middleware-3.34.0/oslo_middleware/tests/test_correlation_id.py0000666000175100017510000000326113227207123025641 0ustar zuulzuul00000000000000# Copyright (c) 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import mock from oslotest import base as test_base from oslotest import moxstubout from oslo_middleware import correlation_id class CorrelationIdTest(test_base.BaseTestCase): def setUp(self): super(CorrelationIdTest, self).setUp() self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs def test_process_request(self): app = mock.Mock() req = mock.Mock() req.headers = {} mock_uuid4 = mock.Mock() mock_uuid4.return_value = "fake_uuid" self.stubs.Set(uuid, 'uuid4', mock_uuid4) middleware = correlation_id.CorrelationId(app) middleware(req) self.assertEqual("fake_uuid", req.headers.get("X_CORRELATION_ID")) def test_process_request_should_not_regenerate_correlation_id(self): app = mock.Mock() req = mock.Mock() req.headers = {"X_CORRELATION_ID": "correlation_id"} middleware = correlation_id.CorrelationId(app) middleware(req) self.assertEqual("correlation_id", req.headers.get("X_CORRELATION_ID")) oslo.middleware-3.34.0/oslo_middleware/tests/test_base.py0000666000175100017510000000627513227207123023566 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import webob from oslo_middleware.base import ConfigurableMiddleware from oslo_middleware.base import Middleware from oslotest.base import BaseTestCase @webob.dec.wsgify def application(req): return 'Hello, World!!!' class TestBase(BaseTestCase): """Test the base middleware class.""" def test_extend_with_request(self): """Assert that a newer middleware behaves as appropriate. This tests makes sure that the request is passed to the middleware's implementation. """ # Bootstrap the application self.application = RequestBase(application) # Send a request through. request = webob.Request({}, method='GET') request.get_response(self.application) self.assertTrue(self.application.called_with_request) def test_extend_without_request(self): """Assert that an older middleware behaves as appropriate. This tests makes sure that the request method is NOT passed to the middleware's implementation, and that there are no other expected errors. """ # Bootstrap the application self.application = NoRequestBase(application) # Send a request through. request = webob.Request({}, method='GET') request.get_response(self.application) self.assertTrue(self.application.called_without_request) def test_no_content_type_added(self): class TestMiddleware(Middleware): @staticmethod def process_request(req): return "foobar" m = TestMiddleware(None) request = webob.Request({}, method='GET') response = request.get_response(m) self.assertNotIn('Content-Type', response.headers) def test_paste_deploy_legacy(self): app = LegacyMiddlewareTest.factory( {'global': True}, local=True)(application) self.assertEqual({}, app.conf) def test_paste_deploy_configurable(self): app = ConfigurableMiddlewareTest.factory( {'global': True}, local=True)(application) self.assertEqual({'global': True, 'local': True}, app.conf) class NoRequestBase(Middleware): """Test middleware, implements old model.""" def process_response(self, response): self.called_without_request = True return response class RequestBase(Middleware): """Test middleware, implements new model.""" def process_response(self, response, request): self.called_with_request = True return response class ConfigurableMiddlewareTest(ConfigurableMiddleware): pass class LegacyMiddlewareTest(Middleware): pass oslo.middleware-3.34.0/oslo_middleware/tests/__init__.py0000666000175100017510000000000013227207123023330 0ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/tests/test_cors.py0000666000175100017510000015423613227207123023623 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import fixture from oslotest import base as test_base import webob import webob.dec import webob.exc as exc from oslo_middleware import cors @webob.dec.wsgify def test_application(req): if req.path_info == '/server_cors': # Mirror back the origin in the request. response = webob.Response(status=200) response.headers['Access-Control-Allow-Origin'] = \ req.headers['Origin'] response.headers['X-Server-Generated-Response'] = '1' return response if req.path_info == '/server_cors_vary': # Mirror back the origin in the request. response = webob.Response(status=200) response.headers['Vary'] = 'Custom-Vary' return response if req.path_info == '/server_no_cors': # Send a response with no CORS headers. response = webob.Response(status=200) return response if req.method == 'OPTIONS': raise exc.HTTPNotFound() return 'Hello World' class CORSTestBase(test_base.BaseTestCase): """Base class for all CORS tests. Sets up applications and helper methods. """ def setUp(self): """Setup the tests.""" super(CORSTestBase, self).setUp() # Set up the config fixture. self.config_fixture = self.useFixture(fixture.Config()) self.config = self.config_fixture.conf def assertCORSResponse(self, response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, vary='Origin', has_content_type=False): """Test helper for CORS response headers. Assert all the headers in a given response. By default, we assume the response is empty. """ # Assert response status. self.assertEqual(status, response.status) # Assert the Access-Control-Allow-Origin header. self.assertHeader(response, 'Access-Control-Allow-Origin', allow_origin) # Assert the Access-Control-Max-Age header. self.assertHeader(response, 'Access-Control-Max-Age', max_age) # Assert the Access-Control-Allow-Methods header. self.assertHeader(response, 'Access-Control-Allow-Methods', allow_methods) # Assert the Access-Control-Allow-Headers header. self.assertHeader(response, 'Access-Control-Allow-Headers', allow_headers) # Assert the Access-Control-Allow-Credentials header. self.assertHeader(response, 'Access-Control-Allow-Credentials', allow_credentials) # Assert the Access-Control-Expose-Headers header. self.assertHeader(response, 'Access-Control-Expose-Headers', expose_headers) # Assert no Content-Type added. if not has_content_type: self.assertHeader(response, 'Content-Type') # If we're expecting an origin response, also assert that the # Vary: Origin header is set, since this implementation of the CORS # specification permits multiple origin domains. if allow_origin: self.assertHeader(response, 'Vary', vary) def assertHeader(self, response, header, value=None): if value: self.assertIn(header, response.headers) self.assertEqual(str(value), response.headers[header]) else: self.assertNotIn(header, response.headers) class CORSTestDefaultOverrides(CORSTestBase): def setUp(self): super(CORSTestDefaultOverrides, self).setUp() fixture = self.config_fixture # Line length accommodation fixture.load_raw_values(group='cors', allowed_origin='http://valid.example.com') fixture.load_raw_values(group='cors.override_creds', allowed_origin='http://creds.example.com', allow_credentials='True') fixture.load_raw_values(group='cors.override_headers', allowed_origin='http://headers.example.com', expose_headers='X-Header-1,X-Header-2', allow_headers='X-Header-1,X-Header-2') self.override_opts = { 'expose_headers': ['X-Header-1'], 'allow_headers': ['X-Header-2'], 'allow_methods': ['GET', 'DELETE'], 'allow_credentials': False, 'max_age': 10 } def test_config_defaults(self): """Assert that using set_defaults overrides the appropriate values.""" cors.set_defaults(**self.override_opts) for opt in cors.CORS_OPTS: if opt.dest in self.override_opts: self.assertEqual(self.override_opts[opt.dest], opt.default) def test_invalid_default_option(self): """Assert that using set_defaults only permits valid options.""" self.assertRaises(AttributeError, cors.set_defaults, allowed_origin='test') def test_cascading_override(self): """Assert that using set_defaults overrides cors.* config values.""" # set defaults cors.set_defaults(**self.override_opts) # Now that the config is set up, create our application. self.application = cors.CORS(test_application, self.config) # Check the global configuration for expected values: gc = self.config.cors self.assertEqual(['http://valid.example.com'], gc.allowed_origin) self.assertEqual(self.override_opts['allow_credentials'], gc.allow_credentials) self.assertEqual(self.override_opts['expose_headers'], gc.expose_headers) self.assertEqual(10, gc.max_age) self.assertEqual(self.override_opts['allow_methods'], gc.allow_methods) self.assertEqual(self.override_opts['allow_headers'], gc.allow_headers) # Check the child configuration for expected values: cc = self.config['cors.override_creds'] self.assertEqual(['http://creds.example.com'], cc.allowed_origin) self.assertTrue(cc.allow_credentials) self.assertEqual(self.override_opts['expose_headers'], cc.expose_headers) self.assertEqual(10, cc.max_age) self.assertEqual(self.override_opts['allow_methods'], cc.allow_methods) self.assertEqual(self.override_opts['allow_headers'], cc.allow_headers) # Check the other child configuration for expected values: ec = self.config['cors.override_headers'] self.assertEqual(['http://headers.example.com'], ec.allowed_origin) self.assertEqual(self.override_opts['allow_credentials'], ec.allow_credentials) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers) self.assertEqual(10, ec.max_age) self.assertEqual(self.override_opts['allow_methods'], ec.allow_methods) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers) class CORSTestFilterFactory(CORSTestBase): """Test the CORS filter_factory method.""" def test_filter_factory(self): self.config([]) # Test a valid filter. filter = cors.filter_factory(None, allowed_origin='http://valid.example.com', allow_credentials='False', max_age='', expose_headers='', allow_methods='GET', allow_headers='') application = filter(test_application) self.assertIn('http://valid.example.com', application.allowed_origins) config = application.allowed_origins['http://valid.example.com'] self.assertEqual(False, config['allow_credentials']) self.assertIsNone(config['max_age']) self.assertEqual([], config['expose_headers']) self.assertEqual(['GET'], config['allow_methods']) self.assertEqual([], config['allow_headers']) def test_filter_factory_multiorigin(self): self.config([]) # Test a valid filter. filter = cors.filter_factory(None, allowed_origin='http://valid.example.com,' 'http://other.example.com') application = filter(test_application) self.assertIn('http://valid.example.com', application.allowed_origins) self.assertIn('http://other.example.com', application.allowed_origins) def test_no_origin_fail(self): '''Assert that a filter factory with no allowed_origin fails.''' self.assertRaises(TypeError, cors.filter_factory, global_conf=None, # allowed_origin=None, # Expected value. allow_credentials='False', max_age='', expose_headers='', allow_methods='GET', allow_headers='') def test_no_origin_but_oslo_config_project(self): '''Assert that a filter factory with oslo_config_project succeed.''' cors.filter_factory(global_conf=None, oslo_config_project='foobar') def test_cor_config_sections_with_defaults(self): '''Assert cors.* config sections with default values work.''' # Set up the config fixture. self.config_fixture.load_raw_values(group='cors.subdomain') # Now that the config is set up, create our application. self.application = cors.CORS(test_application, self.config) class CORSRegularRequestTest(CORSTestBase): """CORS Specification Section 6.1 http://www.w3.org/TR/cors/#resource-requests """ # List of HTTP methods (other than OPTIONS) to test with. methods = ['POST', 'PUT', 'DELETE', 'GET', 'TRACE', 'HEAD'] def setUp(self): """Setup the tests.""" super(CORSRegularRequestTest, self).setUp() fixture = self.config_fixture # Line length accommodation fixture.load_raw_values(group='cors', allowed_origin='http://valid.example.com', allow_credentials='False', max_age='', expose_headers='', allow_methods='GET', allow_headers='') fixture.load_raw_values(group='cors.credentials', allowed_origin='http://creds.example.com', allow_credentials='True') fixture.load_raw_values(group='cors.exposed-headers', allowed_origin='http://headers.example.com', expose_headers='X-Header-1,X-Header-2', allow_headers='X-Header-1,X-Header-2') fixture.load_raw_values(group='cors.cached', allowed_origin='http://cached.example.com', max_age='3600') fixture.load_raw_values(group='cors.get-only', allowed_origin='http://get.example.com', allow_methods='GET') fixture.load_raw_values(group='cors.all-methods', allowed_origin='http://all.example.com', allow_methods='GET,PUT,POST,DELETE,HEAD') fixture.load_raw_values(group='cors.duplicate', allowed_origin='http://domain1.example.com,' 'http://domain2.example.com') # Now that the config is set up, create our application. self.application = cors.CORS(test_application, self.config) def test_config_overrides(self): """Assert that the configuration options are properly registered.""" # Confirm global configuration gc = self.config.cors self.assertEqual(['http://valid.example.com'], gc.allowed_origin) self.assertEqual(False, gc.allow_credentials) self.assertEqual([], gc.expose_headers) self.assertIsNone(gc.max_age) self.assertEqual(['GET'], gc.allow_methods) self.assertEqual([], gc.allow_headers) # Confirm credentials overrides. cc = self.config['cors.credentials'] self.assertEqual(['http://creds.example.com'], cc.allowed_origin) self.assertEqual(True, cc.allow_credentials) self.assertEqual(gc.expose_headers, cc.expose_headers) self.assertEqual(gc.max_age, cc.max_age) self.assertEqual(gc.allow_methods, cc.allow_methods) self.assertEqual(gc.allow_headers, cc.allow_headers) # Confirm exposed-headers overrides. ec = self.config['cors.exposed-headers'] self.assertEqual(['http://headers.example.com'], ec.allowed_origin) self.assertEqual(gc.allow_credentials, ec.allow_credentials) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers) self.assertEqual(gc.max_age, ec.max_age) self.assertEqual(gc.allow_methods, ec.allow_methods) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers) # Confirm cached overrides. chc = self.config['cors.cached'] self.assertEqual(['http://cached.example.com'], chc.allowed_origin) self.assertEqual(gc.allow_credentials, chc.allow_credentials) self.assertEqual(gc.expose_headers, chc.expose_headers) self.assertEqual(3600, chc.max_age) self.assertEqual(gc.allow_methods, chc.allow_methods) self.assertEqual(gc.allow_headers, chc.allow_headers) # Confirm get-only overrides. goc = self.config['cors.get-only'] self.assertEqual(['http://get.example.com'], goc.allowed_origin) self.assertEqual(gc.allow_credentials, goc.allow_credentials) self.assertEqual(gc.expose_headers, goc.expose_headers) self.assertEqual(gc.max_age, goc.max_age) self.assertEqual(['GET'], goc.allow_methods) self.assertEqual(gc.allow_headers, goc.allow_headers) # Confirm all-methods overrides. ac = self.config['cors.all-methods'] self.assertEqual(['http://all.example.com'], ac.allowed_origin) self.assertEqual(gc.allow_credentials, ac.allow_credentials) self.assertEqual(gc.expose_headers, ac.expose_headers) self.assertEqual(gc.max_age, ac.max_age) self.assertEqual(['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], ac.allow_methods) self.assertEqual(gc.allow_headers, ac.allow_headers) # Confirm duplicate domains. ac = self.config['cors.duplicate'] self.assertEqual(['http://domain1.example.com', 'http://domain2.example.com'], ac.allowed_origin) self.assertEqual(gc.allow_credentials, ac.allow_credentials) self.assertEqual(gc.expose_headers, ac.expose_headers) self.assertEqual(gc.max_age, ac.max_age) self.assertEqual(gc.allow_methods, ac.allow_methods) self.assertEqual(gc.allow_headers, ac.allow_headers) def test_no_origin_header(self): """CORS Specification Section 6.1.1 If the Origin header is not present terminate this set of steps. The request is outside the scope of this specification. """ for method in self.methods: request = webob.Request.blank('/') response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) def test_origin_headers(self): """CORS Specification Section 6.1.2 If the value of the Origin header is not a case-sensitive match for any of the values in list of origins, do not set any additional headers and terminate this set of steps. """ # Test valid origin header. for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://valid.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://valid.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) # Test origin header not present in configuration. for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://invalid.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) # Test valid, but case-mismatched origin header. for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://VALID.EXAMPLE.COM' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) # Test valid header from list of duplicates. for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://domain2.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://domain2.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) def test_supports_credentials(self): """CORS Specification Section 6.1.3 If the resource supports credentials add a single Access-Control-Allow-Origin header, with the value of the Origin header as value, and add a single Access-Control-Allow-Credentials header with the case-sensitive string "true" as value. Otherwise, add a single Access-Control-Allow-Origin header, with either the value of the Origin header or the string "*" as value. NOTE: We never use the "*" as origin. """ # Test valid origin header without credentials. for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://valid.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://valid.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) # Test valid origin header with credentials for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://creds.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://creds.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials="true", expose_headers=None, has_content_type=True) def test_expose_headers(self): """CORS Specification Section 6.1.4 If the list of exposed headers is not empty add one or more Access-Control-Expose-Headers headers, with as values the header field names given in the list of exposed headers. """ for method in self.methods: request = webob.Request.blank('/') request.method = method request.headers['Origin'] = 'http://headers.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://headers.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers='X-Header-1,X-Header-2', has_content_type=True) def test_application_options_response(self): """Assert that an application provided OPTIONS response is honored. If the underlying application, via middleware or other, provides a CORS response, its response should be honored. """ test_origin = 'http://creds.example.com' request = webob.Request.blank('/server_cors') request.method = "GET" request.headers['Origin'] = test_origin request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) # If the regular CORS handling catches this request, it should set # the allow credentials header. This makes sure that it doesn't. self.assertNotIn('Access-Control-Allow-Credentials', response.headers) self.assertEqual(response.headers['Access-Control-Allow-Origin'], test_origin) self.assertEqual(response.headers['X-Server-Generated-Response'], '1') def test_application_vary_respected(self): """Assert that an application's provided Vary header is persisted. If the underlying application, via middleware or other, provides a Vary header, its response should be honored. """ request = webob.Request.blank('/server_cors_vary') request.method = "GET" request.headers['Origin'] = 'http://valid.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://valid.example.com', max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None, vary='Custom-Vary,Origin', has_content_type=True) class CORSPreflightRequestTest(CORSTestBase): """CORS Specification Section 6.2 http://www.w3.org/TR/cors/#resource-preflight-requests """ def setUp(self): super(CORSPreflightRequestTest, self).setUp() fixture = self.config_fixture # Line length accommodation fixture.load_raw_values(group='cors', allowed_origin='http://valid.example.com', allow_credentials='False', max_age='', expose_headers='', allow_methods='GET', allow_headers='') fixture.load_raw_values(group='cors.credentials', allowed_origin='http://creds.example.com', allow_credentials='True') fixture.load_raw_values(group='cors.exposed-headers', allowed_origin='http://headers.example.com', expose_headers='X-Header-1,X-Header-2', allow_headers='X-Header-1,X-Header-2') fixture.load_raw_values(group='cors.cached', allowed_origin='http://cached.example.com', max_age='3600') fixture.load_raw_values(group='cors.get-only', allowed_origin='http://get.example.com', allow_methods='GET') fixture.load_raw_values(group='cors.all-methods', allowed_origin='http://all.example.com', allow_methods='GET,PUT,POST,DELETE,HEAD') # Now that the config is set up, create our application. self.application = cors.CORS(test_application, self.config) def test_config_overrides(self): """Assert that the configuration options are properly registered.""" # Confirm global configuration gc = self.config.cors self.assertEqual(gc.allowed_origin, ['http://valid.example.com']) self.assertEqual(gc.allow_credentials, False) self.assertEqual(gc.expose_headers, []) self.assertIsNone(gc.max_age) self.assertEqual(gc.allow_methods, ['GET']) self.assertEqual(gc.allow_headers, []) # Confirm credentials overrides. cc = self.config['cors.credentials'] self.assertEqual(['http://creds.example.com'], cc.allowed_origin) self.assertEqual(True, cc.allow_credentials) self.assertEqual(gc.expose_headers, cc.expose_headers) self.assertEqual(gc.max_age, cc.max_age) self.assertEqual(gc.allow_methods, cc.allow_methods) self.assertEqual(gc.allow_headers, cc.allow_headers) # Confirm exposed-headers overrides. ec = self.config['cors.exposed-headers'] self.assertEqual(['http://headers.example.com'], ec.allowed_origin) self.assertEqual(gc.allow_credentials, ec.allow_credentials) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers) self.assertEqual(gc.max_age, ec.max_age) self.assertEqual(gc.allow_methods, ec.allow_methods) self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers) # Confirm cached overrides. chc = self.config['cors.cached'] self.assertEqual(['http://cached.example.com'], chc.allowed_origin) self.assertEqual(gc.allow_credentials, chc.allow_credentials) self.assertEqual(gc.expose_headers, chc.expose_headers) self.assertEqual(3600, chc.max_age) self.assertEqual(gc.allow_methods, chc.allow_methods) self.assertEqual(gc.allow_headers, chc.allow_headers) # Confirm get-only overrides. goc = self.config['cors.get-only'] self.assertEqual(['http://get.example.com'], goc.allowed_origin) self.assertEqual(gc.allow_credentials, goc.allow_credentials) self.assertEqual(gc.expose_headers, goc.expose_headers) self.assertEqual(gc.max_age, goc.max_age) self.assertEqual(['GET'], goc.allow_methods) self.assertEqual(gc.allow_headers, goc.allow_headers) # Confirm all-methods overrides. ac = self.config['cors.all-methods'] self.assertEqual(['http://all.example.com'], ac.allowed_origin) self.assertEqual(gc.allow_credentials, ac.allow_credentials) self.assertEqual(gc.expose_headers, ac.expose_headers) self.assertEqual(gc.max_age, ac.max_age) self.assertEqual(ac.allow_methods, ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']) self.assertEqual(gc.allow_headers, ac.allow_headers) def test_no_origin_header(self): """CORS Specification Section 6.2.1 If the Origin header is not present terminate this set of steps. The request is outside the scope of this specification. """ request = webob.Request.blank('/') request.method = "OPTIONS" response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_case_sensitive_origin(self): """CORS Specification Section 6.2.2 If the value of the Origin header is not a case-sensitive match for any of the values in list of origins do not set any additional headers and terminate this set of steps. """ # Test valid domain request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://valid.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://valid.example.com', max_age=None, allow_methods='GET', allow_headers='', allow_credentials=None, expose_headers=None) # Test invalid domain request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://invalid.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) # Test case-sensitive mismatch domain request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://VALID.EXAMPLE.COM' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_simple_header_response(self): """CORS Specification Section 3 A header is said to be a simple header if the header field name is an ASCII case-insensitive match for Accept, Accept-Language, or Content-Language or if it is an ASCII case-insensitive match for Content-Type and the header field value media type (excluding parameters) is an ASCII case-insensitive match for application/x-www-form-urlencoded, multipart/form-data, or text/plain. NOTE: We are not testing the media type cases. """ simple_headers = ','.join([ 'accept', 'accept-language', 'content-language', 'content-type' ]) request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://valid.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = simple_headers response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://valid.example.com', max_age=None, allow_methods='GET', allow_headers=simple_headers, allow_credentials=None, expose_headers=None) def test_no_request_method(self): """CORS Specification Section 6.2.3 If there is no Access-Control-Request-Method header or if parsing failed, do not set any additional headers and terminate this set of steps. The request is outside the scope of this specification. """ # Test valid domain, valid method. request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://get.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://get.example.com', max_age=None, allow_methods='GET', allow_headers=None, allow_credentials=None, expose_headers=None) # Test valid domain, invalid HTTP method. request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://valid.example.com' request.headers['Access-Control-Request-Method'] = 'TEAPOT' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) # Test valid domain, no HTTP method. request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://valid.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_invalid_method(self): """CORS Specification Section 6.2.3 If method is not a case-sensitive match for any of the values in list of methods do not set any additional headers and terminate this set of steps. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://get.example.com' request.headers['Access-Control-Request-Method'] = 'get' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_no_parse_request_headers(self): """CORS Specification Section 6.2.4 If there are no Access-Control-Request-Headers headers let header field-names be the empty list. If parsing failed do not set any additional headers and terminate this set of steps. The request is outside the scope of this specification. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://headers.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = 'value with spaces' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_no_request_headers(self): """CORS Specification Section 6.2.4 If there are no Access-Control-Request-Headers headers let header field-names be the empty list. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://headers.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = '' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://headers.example.com', max_age=None, allow_methods='GET', allow_headers=None, allow_credentials=None, expose_headers=None) def test_request_headers(self): """CORS Specification Section 6.2.4 Let header field-names be the values as result of parsing the Access-Control-Request-Headers headers. If there are no Access-Control-Request-Headers headers let header field-names be the empty list. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://headers.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = 'X-Header-1,' \ 'X-Header-2' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://headers.example.com', max_age=None, allow_methods='GET', allow_headers='X-Header-1,X-Header-2', allow_credentials=None, expose_headers=None) def test_request_headers_not_permitted(self): """CORS Specification Section 6.2.4, 6.2.6 If there are no Access-Control-Request-Headers headers let header field-names be the empty list. If any of the header field-names is not a ASCII case-insensitive match for any of the values in list of headers do not set any additional headers and terminate this set of steps. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://headers.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = 'X-Not-Exposed,' \ 'X-Never-Exposed' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_credentials(self): """CORS Specification Section 6.2.7 If the resource supports credentials add a single Access-Control-Allow-Origin header, with the value of the Origin header as value, and add a single Access-Control-Allow-Credentials header with the case-sensitive string "true" as value. Otherwise, add a single Access-Control-Allow-Origin header, with either the value of the Origin header or the string "*" as value. NOTE: We never use the "*" as origin. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://creds.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://creds.example.com', max_age=None, allow_methods='GET', allow_headers=None, allow_credentials="true", expose_headers=None) def test_optional_max_age(self): """CORS Specification Section 6.2.8 Optionally add a single Access-Control-Max-Age header with as value the amount of seconds the user agent is allowed to cache the result of the request. """ request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://cached.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://cached.example.com', max_age=3600, allow_methods='GET', allow_headers=None, allow_credentials=None, expose_headers=None) def test_allow_methods(self): """CORS Specification Section 6.2.9 Add one or more Access-Control-Allow-Methods headers consisting of (a subset of) the list of methods. Since the list of methods can be unbounded, simply returning the method indicated by Access-Control-Request-Method (if supported) can be enough. """ for method in ['GET', 'PUT', 'POST', 'DELETE']: request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://all.example.com' request.headers['Access-Control-Request-Method'] = method response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://all.example.com', max_age=None, allow_methods=method, allow_headers=None, allow_credentials=None, expose_headers=None) for method in ['PUT', 'POST', 'DELETE']: request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://get.example.com' request.headers['Access-Control-Request-Method'] = method response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin=None, max_age=None, allow_methods=None, allow_headers=None, allow_credentials=None, expose_headers=None) def test_allow_headers(self): """CORS Specification Section 6.2.10 Add one or more Access-Control-Allow-Headers headers consisting of (a subset of) the list of headers. If each of the header field-names is a simple header and none is Content-Type, this step may be skipped. If a header field name is a simple header and is not Content-Type, it is not required to be listed. Content-Type is to be listed as only a subset of its values makes it qualify as simple header. """ requested_headers = 'Content-Type,X-Header-1,Cache-Control,Expires,' \ 'Last-Modified,Pragma' request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://headers.example.com' request.headers['Access-Control-Request-Method'] = 'GET' request.headers['Access-Control-Request-Headers'] = requested_headers response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://headers.example.com', max_age=None, allow_methods='GET', allow_headers=requested_headers, allow_credentials=None, expose_headers=None) def test_application_options_response(self): """Assert that an application provided OPTIONS response is honored. If the underlying application, via middleware or other, provides a CORS response, its response should be honored. """ test_origin = 'http://creds.example.com' request = webob.Request.blank('/server_cors') request.method = "OPTIONS" request.headers['Origin'] = test_origin request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) # If the regular CORS handling catches this request, it should set # the allow credentials header. This makes sure that it doesn't. self.assertNotIn('Access-Control-Allow-Credentials', response.headers) self.assertEqual(test_origin, response.headers['Access-Control-Allow-Origin']) self.assertEqual('1', response.headers['X-Server-Generated-Response']) # If the application returns an OPTIONS response without CORS # headers, assert that we apply headers. request = webob.Request.blank('/server_no_cors') request.method = "OPTIONS" request.headers['Origin'] = 'http://get.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://get.example.com', max_age=None, allow_methods='GET', allow_headers=None, allow_credentials=None, expose_headers=None, has_content_type=True) class CORSTestWildcard(CORSTestBase): """Test the CORS wildcard specification.""" def setUp(self): super(CORSTestWildcard, self).setUp() fixture = self.config_fixture # Line length accommodation fixture.load_raw_values(group='cors', allowed_origin='http://default.example.com', allow_credentials='True', max_age='', expose_headers='', allow_methods='GET,PUT,POST,DELETE,HEAD', allow_headers='') fixture.load_raw_values(group='cors.wildcard', allowed_origin='*', allow_methods='GET') # Now that the config is set up, create our application. self.application = cors.CORS(test_application, self.config) def test_config_overrides(self): """Assert that the configuration options are properly registered.""" # Confirm global configuration gc = self.config.cors self.assertEqual(['http://default.example.com'], gc.allowed_origin) self.assertEqual(True, gc.allow_credentials) self.assertEqual([], gc.expose_headers) self.assertIsNone(gc.max_age) self.assertEqual(['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], gc.allow_methods) self.assertEqual([], gc.allow_headers) # Confirm all-methods overrides. ac = self.config['cors.wildcard'] self.assertEqual(['*'], ac.allowed_origin) self.assertEqual(True, gc.allow_credentials) self.assertEqual(gc.expose_headers, ac.expose_headers) self.assertEqual(gc.max_age, ac.max_age) self.assertEqual(['GET'], ac.allow_methods) self.assertEqual(gc.allow_headers, ac.allow_headers) def test_wildcard_domain(self): """CORS Specification, Wildcards If the configuration file specifies CORS settings for the wildcard '*' domain, it should return those for all origin domains except for the overrides. """ # Test valid domain request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://default.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://default.example.com', max_age=None, allow_methods='GET', allow_headers='', allow_credentials='true', expose_headers=None) # Test valid domain request = webob.Request.blank('/') request.method = "GET" request.headers['Origin'] = 'http://default.example.com' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='http://default.example.com', max_age=None, allow_headers='', allow_credentials='true', expose_headers=None, has_content_type=True) # Test invalid domain request = webob.Request.blank('/') request.method = "OPTIONS" request.headers['Origin'] = 'http://invalid.example.com' request.headers['Access-Control-Request-Method'] = 'GET' response = request.get_response(self.application) self.assertCORSResponse(response, status='200 OK', allow_origin='*', max_age=None, allow_methods='GET', allow_headers='', allow_credentials='true', expose_headers=None, has_content_type=True) oslo.middleware-3.34.0/oslo_middleware/tests/test_sizelimit.py0000666000175100017510000000673313227207123024664 0ustar zuulzuul00000000000000# Copyright (c) 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import fixture as config from oslotest import base as test_base import six import webob from oslo_middleware import sizelimit class TestLimitingReader(test_base.BaseTestCase): def test_limiting_reader(self): BYTES = 1024 bytes_read = 0 data = six.StringIO("*" * BYTES) for chunk in sizelimit.LimitingReader(data, BYTES): bytes_read += len(chunk) self.assertEqual(BYTES, bytes_read) bytes_read = 0 data = six.StringIO("*" * BYTES) reader = sizelimit.LimitingReader(data, BYTES) byte = reader.read(1) while len(byte) != 0: bytes_read += 1 byte = reader.read(1) self.assertEqual(BYTES, bytes_read) def test_read_default_value(self): BYTES = 1024 data_str = "*" * BYTES data = six.StringIO(data_str) reader = sizelimit.LimitingReader(data, BYTES) res = reader.read() self.assertEqual(data_str, res) def test_limiting_reader_fails(self): BYTES = 1024 def _consume_all_iter(): bytes_read = 0 data = six.StringIO("*" * BYTES) for chunk in sizelimit.LimitingReader(data, BYTES - 1): bytes_read += len(chunk) self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, _consume_all_iter) def _consume_all_read(): bytes_read = 0 data = six.StringIO("*" * BYTES) reader = sizelimit.LimitingReader(data, BYTES - 1) byte = reader.read(1) while len(byte) != 0: bytes_read += 1 byte = reader.read(1) self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, _consume_all_read) class TestRequestBodySizeLimiter(test_base.BaseTestCase): def setUp(self): super(TestRequestBodySizeLimiter, self).setUp() self.useFixture(config.Config()) @webob.dec.wsgify() def fake_app(req): return webob.Response(req.body) self.middleware = sizelimit.RequestBodySizeLimiter(fake_app) self.MAX_REQUEST_BODY_SIZE = ( self.middleware.oslo_conf.oslo_middleware.max_request_body_size) self.request = webob.Request.blank('/', method='POST') def test_content_length_acceptable(self): self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE self.request.body = b"0" * self.MAX_REQUEST_BODY_SIZE response = self.request.get_response(self.middleware) self.assertEqual(200, response.status_int) def test_content_length_too_large(self): self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE + 1 self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1) response = self.request.get_response(self.middleware) self.assertEqual(413, response.status_int) oslo.middleware-3.34.0/oslo_middleware/tests/test_request_id.py0000666000175100017510000000765713227207123025025 0ustar zuulzuul00000000000000# Copyright (c) 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from oslotest import base as test_base from testtools import matchers import webob import webob.dec from oslo_middleware import request_id class AltHeader(request_id.RequestId): compat_headers = ["x-compute-req-id", "x-silly-id"] class RequestIdTest(test_base.BaseTestCase): def test_generate_request_id(self): @webob.dec.wsgify def application(req): return req.environ[request_id.ENV_REQUEST_ID] app = request_id.RequestId(application) req = webob.Request.blank('/test') res = req.get_response(app) res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID) if isinstance(res_req_id, bytes): res_req_id = res_req_id.decode('utf-8') self.assertThat(res_req_id, matchers.StartsWith('req-')) # request-id in request environ is returned as response body self.assertEqual(res.body.decode('utf-8'), res_req_id) def test_compat_headers(self): """Test that compat headers are set Compat headers might exist on a super class to support previous API contracts. This ensures that you can set that to a list of headers and those values are the same as the request_id. """ @webob.dec.wsgify def application(req): return req.environ[request_id.ENV_REQUEST_ID] app = AltHeader(application) req = webob.Request.blank('/test') res = req.get_response(app) res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID) self.assertEqual(res.headers.get("x-compute-req-id"), res_req_id) self.assertEqual(res.headers.get("x-silly-id"), res_req_id) def test_global_request_id_set(self): """Test that global request_id is set.""" @webob.dec.wsgify def application(req): return req.environ[request_id.GLOBAL_REQ_ID] global_req = "req-%s" % uuid.uuid4() app = request_id.RequestId(application) req = webob.Request.blank( '/test', headers={"X-OpenStack-Request-ID": global_req}) res = req.get_response(app) res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID) if isinstance(res_req_id, bytes): res_req_id = res_req_id.decode('utf-8') # global-request-id in request environ is returned as response body self.assertEqual(res.body.decode('utf-8'), global_req) self.assertNotEqual(res.body.decode('utf-8'), res_req_id) def test_global_request_id_drop(self): """Test that bad format ids are dropped. This ensures that badly formatted ids are dropped entirely. """ @webob.dec.wsgify def application(req): return req.environ.get(request_id.GLOBAL_REQ_ID) global_req = "req-%s-bad" % uuid.uuid4() app = request_id.RequestId(application) req = webob.Request.blank( '/test', headers={"X-OpenStack-Request-ID": global_req}) res = req.get_response(app) res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID) if isinstance(res_req_id, bytes): res_req_id = res_req_id.decode('utf-8') # global-request-id in request environ is returned as response body self.assertEqual(res.body.decode('utf-8'), '') oslo.middleware-3.34.0/oslo_middleware/tests/test_http_proxy_to_wsgi.py0000666000175100017510000001561713227207123026627 0ustar zuulzuul00000000000000# Copyright (c) 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from wsgiref import util from oslotest import base as test_base import webob from oslo_middleware import http_proxy_to_wsgi class TestHTTPProxyToWSGI(test_base.BaseTestCase): def setUp(self): super(TestHTTPProxyToWSGI, self).setUp() @webob.dec.wsgify() def fake_app(req): return util.application_uri(req.environ) self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) self.middleware.oslo_conf.set_override('enable_proxy_headers_parsing', True, group='oslo_middleware') self.request = webob.Request.blank('/foo/bar', method='POST') def test_backward_compat(self): @webob.dec.wsgify() def fake_app(req): return util.application_uri(req.environ) self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/", response.body) def test_no_headers(self): response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/", response.body) def test_url_translate_ssl(self): self.request.headers['X-Forwarded-Proto'] = "https" response = self.request.get_response(self.middleware) self.assertEqual(b"https://localhost:80/", response.body) def test_url_translate_ssl_port(self): self.request.headers['X-Forwarded-Proto'] = "https" self.request.headers['X-Forwarded-Host'] = "example.com:123" response = self.request.get_response(self.middleware) self.assertEqual(b"https://example.com:123/", response.body) def test_url_translate_host_ipv6(self): self.request.headers['X-Forwarded-Proto'] = "https" self.request.headers['X-Forwarded-Host'] = "[f00:b4d::1]:123" response = self.request.get_response(self.middleware) self.assertEqual(b"https://[f00:b4d::1]:123/", response.body) def test_url_translate_base(self): self.request.headers['X-Forwarded-Prefix'] = "/bla" response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/bla", response.body) def test_url_translate_port_and_base_and_proto_and_host(self): self.request.headers['X-Forwarded-Proto'] = "https" self.request.headers['X-Forwarded-Prefix'] = "/bla" self.request.headers['X-Forwarded-Host'] = "example.com:8043" response = self.request.get_response(self.middleware) self.assertEqual(b"https://example.com:8043/bla", response.body) def test_rfc7239_invalid(self): self.request.headers['Forwarded'] = ( "iam=anattacker;metoo, I will crash you!!P;m,xx") response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/", response.body) def test_rfc7239_proto(self): self.request.headers['Forwarded'] = ( "for=foobar;proto=https, for=foobaz;proto=http") response = self.request.get_response(self.middleware) self.assertEqual(b"https://localhost:80/", response.body) def test__parse_rfc7239_header(self): expected_result = [{'for': 'foobar', 'proto': 'https'}, {'for': 'foobaz', 'proto': 'http'}] result = self.middleware._parse_rfc7239_header( "for=foobar;proto=https, for=foobaz;proto=http") self.assertEqual(expected_result, result) result = self.middleware._parse_rfc7239_header( "for=foobar; proto=https, for=foobaz; proto=http") self.assertEqual(expected_result, result) def test_rfc7239_proto_host(self): self.request.headers['Forwarded'] = ( "for=foobar;proto=https;host=example.com, for=foobaz;proto=http") response = self.request.get_response(self.middleware) self.assertEqual(b"https://example.com/", response.body) def test_rfc7239_proto_host_base(self): self.request.headers['Forwarded'] = ( "for=foobar;proto=https;host=example.com:8043, for=foobaz") self.request.headers['X-Forwarded-Prefix'] = "/bla" response = self.request.get_response(self.middleware) self.assertEqual(b"https://example.com:8043/bla", response.body) def test_forwarded_for_headers(self): @webob.dec.wsgify() def fake_app(req): return req.environ['REMOTE_ADDR'] self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) forwarded_for_addr = '1.2.3.4' forwarded_addr = '8.8.8.8' # If both X-Forwarded-For and Fowarded headers are present, it should # use the Forwarded header and ignore the X-Forwarded-For header. self.request.headers['Forwarded'] = ( "for=%s;proto=https;host=example.com:8043" % (forwarded_addr)) self.request.headers['X-Forwarded-For'] = forwarded_for_addr response = self.request.get_response(self.middleware) self.assertEqual(forwarded_addr.encode(), response.body) # Now if only X-Forwarded-For header is present, it should be used. del self.request.headers['Forwarded'] response = self.request.get_response(self.middleware) self.assertEqual(forwarded_for_addr.encode(), response.body) class TestHTTPProxyToWSGIDisabled(test_base.BaseTestCase): def setUp(self): super(TestHTTPProxyToWSGIDisabled, self).setUp() @webob.dec.wsgify() def fake_app(req): return util.application_uri(req.environ) self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) self.middleware.oslo_conf.set_override('enable_proxy_headers_parsing', False, group='oslo_middleware') self.request = webob.Request.blank('/foo/bar', method='POST') def test_no_headers(self): response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/", response.body) def test_url_translate_ssl_has_no_effect(self): self.request.headers['X-Forwarded-Proto'] = "https" self.request.headers['X-Forwarded-Host'] = "example.com:123" response = self.request.get_response(self.middleware) self.assertEqual(b"http://localhost:80/", response.body) oslo.middleware-3.34.0/oslo_middleware/tests/test_stats.py0000666000175100017510000001150013227207123023775 0ustar zuulzuul00000000000000# Copyright (c) 2016 Cisco Systems # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import mock from oslotest import base as test_base import statsd import webob.dec import webob.exc from oslo_middleware import stats class TestStaticMethods(test_base.BaseTestCase): def test_removes_uuid(self): # Generate a long-format UUID (standard form). id = str(uuid.uuid4()) path = "foo.{uuid}.bar".format(uuid=id) stat = stats.StatsMiddleware.strip_uuid(path) self.assertEqual("foo.bar", stat) def test_removes_short_uuid(self): id = uuid.uuid4().hex path = "foo.{uuid}.bar".format(uuid=id) stat = stats.StatsMiddleware.strip_short_uuid(path) self.assertEqual("foo.bar", stat) def test_strips_dots_from_version(self): path = "/v1.2/foo.bar/bar.foo" stat = stats.StatsMiddleware.strip_dot_from_version(path) self.assertEqual("/v12/foo.bar/bar.foo", stat) class TestStatsMiddleware(test_base.BaseTestCase): def setUp(self): super(TestStatsMiddleware, self).setUp() self.patch(statsd, 'StatsClient', mock.MagicMock()) def make_stats_middleware(self, stat_name=None, stats_host=None, remove_uuid=False, remove_short_uuid=False): if stat_name is None: stat_name = uuid.uuid4().hex if stats_host is None: stats_host = uuid.uuid4().hex conf = dict( name=stat_name, stats_host=stats_host, remove_uuid=remove_uuid, remove_short_uuid=remove_short_uuid, ) @webob.dec.wsgify def fake_application(req): return 'Hello, World' return stats.StatsMiddleware(fake_application, conf) def perform_request(self, app, path, method): req = webob.Request.blank(path, method=method) return req.get_response(app) def test_sends_counter_to_statsd(self): app = self.make_stats_middleware() path = '/test/foo/bar' self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}.{path}".format( name=app.stat_name, method='GET', path=path.lstrip('/').replace('/', '.')) app.statsd.timer.assert_called_once_with(expected_stat) def test_strips_uuid_if_configured(self): app = self.make_stats_middleware(remove_uuid=True) random_uuid = str(uuid.uuid4()) path = '/foo/{uuid}/bar'.format(uuid=random_uuid) self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}.foo.bar".format( name=app.stat_name, method='GET') app.statsd.timer.assert_called_once_with(expected_stat) def test_strips_short_uuid_if_configured(self): app = self.make_stats_middleware(remove_short_uuid=True) random_uuid = uuid.uuid4().hex path = '/foo/{uuid}/bar'.format(uuid=random_uuid) self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}.foo.bar".format( name=app.stat_name, method='GET') app.statsd.timer.assert_called_once_with(expected_stat) def test_strips_both_uuid_types_if_configured(self): app = self.make_stats_middleware( remove_uuid=True, remove_short_uuid=True) random_short_uuid = uuid.uuid4().hex random_uuid = str(uuid.uuid4()) path = '/foo/{uuid}/bar/{short_uuid}'.format( uuid=random_uuid, short_uuid=random_short_uuid) self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}.foo.bar".format( name=app.stat_name, method='GET') app.statsd.timer.assert_called_once_with(expected_stat) def test_always_mutates_version_id(self): app = self.make_stats_middleware() path = '/v2.1/foo/bar' self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}.v21.foo.bar".format( name=app.stat_name, method='GET') app.statsd.timer.assert_called_once_with(expected_stat) def test_empty_path_has_sane_stat_name(self): app = self.make_stats_middleware() path = '/' self.perform_request(app, path, 'GET') expected_stat = "{name}.{method}".format( name=app.stat_name, method='GET') app.statsd.timer.assert_called_once_with(expected_stat) oslo.middleware-3.34.0/oslo_middleware/tests/test_opts.py0000666000175100017510000000166313227207123023635 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_middleware import opts from oslotest.base import BaseTestCase class TestOptionDiscovery(BaseTestCase): def test_all(self): opts.list_opts() def test_sizelimit(self): opts.list_opts_sizelimit() def test_cors(self): opts.list_opts_cors() def test_ssl(self): opts.list_opts_ssl() oslo.middleware-3.34.0/oslo_middleware/sizelimit.py0000666000175100017510000000626713227207123022465 0ustar zuulzuul00000000000000# Copyright (c) 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Request Body limiting middleware. """ from oslo_config import cfg import webob.dec import webob.exc from oslo_middleware._i18n import _ from oslo_middleware import base _oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size', group='DEFAULT'), cfg.DeprecatedOpt('max_request_body_size', group='DEFAULT')] _opts = [ # default request size is 112k cfg.IntOpt('max_request_body_size', default=114688, help='The maximum body size for each ' ' request, in bytes.', deprecated_opts=_oldopts) ] class LimitingReader(object): """Reader to limit the size of an incoming request.""" def __init__(self, data, limit): """Initiates LimitingReader object. :param data: Underlying data object :param limit: maximum number of bytes the reader should allow """ self.data = data self.limit = limit self.bytes_read = 0 def __iter__(self): for chunk in self.data: self.bytes_read += len(chunk) if self.bytes_read > self.limit: msg = _("Request is too large.") raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) else: yield chunk def read(self, i=None): # NOTE(jamielennox): We can't simply provide the default to the read() # call as the expected default differs between mod_wsgi and eventlet if i is None: result = self.data.read() else: result = self.data.read(i) self.bytes_read += len(result) if self.bytes_read > self.limit: msg = _("Request is too large.") raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) return result class RequestBodySizeLimiter(base.ConfigurableMiddleware): """Limit the size of incoming requests.""" def __init__(self, application, conf=None): super(RequestBodySizeLimiter, self).__init__(application, conf) self.oslo_conf.register_opts(_opts, group='oslo_middleware') @webob.dec.wsgify def __call__(self, req): max_size = self._conf_get('max_request_body_size') if (req.content_length is not None and req.content_length > max_size): msg = _("Request is too large.") raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) if req.content_length is None: limiter = LimitingReader(req.body_file, max_size) req.body_file = limiter return self.application oslo.middleware-3.34.0/oslo_middleware/http_proxy_to_wsgi.py0000666000175100017510000000635413227207123024424 0ustar zuulzuul00000000000000# -*- encoding: utf-8 -*- # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. See the License for the specific language governing permissions and # limitations under the License. from oslo_config import cfg from oslo_middleware import base OPTS = [ cfg.BoolOpt('enable_proxy_headers_parsing', default=False, help="Whether the application is behind a proxy or not. " "This determines if the middleware should parse the " "headers or not.") ] class HTTPProxyToWSGI(base.ConfigurableMiddleware): """HTTP proxy to WSGI termination middleware. This middleware overloads WSGI environment variables with the one provided by the remote HTTP reverse proxy. """ def __init__(self, application, *args, **kwargs): super(HTTPProxyToWSGI, self).__init__(application, *args, **kwargs) self.oslo_conf.register_opts(OPTS, group='oslo_middleware') @staticmethod def _parse_rfc7239_header(header): """Parses RFC7239 Forward headers. e.g. for=192.0.2.60;proto=http, for=192.0.2.60;by=203.0.113.43 """ result = [] for proxy in header.split(","): entry = {} for d in proxy.split(";"): key, _, value = d.partition("=") entry[key.lower().strip()] = value.strip() result.append(entry) return result def process_request(self, req): if not self._conf_get('enable_proxy_headers_parsing'): return fwd_hdr = req.environ.get("HTTP_FORWARDED") if fwd_hdr: proxies = self._parse_rfc7239_header(fwd_hdr) # Let's use the value from the first proxy if proxies: proxy = proxies[0] forwarded_proto = proxy.get("proto") if forwarded_proto: req.environ['wsgi.url_scheme'] = forwarded_proto forwarded_host = proxy.get("host") if forwarded_host: req.environ['HTTP_HOST'] = forwarded_host forwarded_for = proxy.get("for") if forwarded_for: req.environ['REMOTE_ADDR'] = forwarded_for else: # World before RFC7239 forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO") if forwarded_proto: req.environ['wsgi.url_scheme'] = forwarded_proto forwarded_host = req.environ.get("HTTP_X_FORWARDED_HOST") if forwarded_host: req.environ['HTTP_HOST'] = forwarded_host forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR") if forwarded_for: req.environ['REMOTE_ADDR'] = forwarded_for v = req.environ.get("HTTP_X_FORWARDED_PREFIX") if v: req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME'] oslo.middleware-3.34.0/oslo_middleware/locale/0000775000175100017510000000000013227207404021326 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/en_GB/0000775000175100017510000000000013227207404022300 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013227207404024065 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware.po0000666000175100017510000000157413227207123027605 0ustar zuulzuul00000000000000# Translations template for oslo.middleware. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.middleware # project. # # Translators: # Andi Chandler , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.middleware 3.7.1.dev18\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-04-19 23:53+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-11-03 11:03+0000\n" "Last-Translator: Andi Chandler \n" "Language: en-GB\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: English (United Kingdom)\n" msgid "Request is too large." msgstr "Request is too large." oslo.middleware-3.34.0/oslo_middleware/locale/fr/0000775000175100017510000000000013227207404021735 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/fr/LC_MESSAGES/0000775000175100017510000000000013227207404023522 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware.po0000666000175100017510000000157113227207123027237 0ustar zuulzuul00000000000000# Translations template for oslo.middleware. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.middleware # project. # # Translators: # Maxime COQUEREL , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.middleware 3.7.1.dev18\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-04-19 23:53+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-09-17 09:06+0000\n" "Last-Translator: Maxime COQUEREL \n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: French\n" msgid "Request is too large." msgstr "Demande trop importante." oslo.middleware-3.34.0/oslo_middleware/locale/de/0000775000175100017510000000000013227207404021716 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/de/LC_MESSAGES/0000775000175100017510000000000013227207404023503 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware.po0000666000175100017510000000156513227207123027223 0ustar zuulzuul00000000000000# Translations template for oslo.middleware. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.middleware # project. # # Translators: # Andreas Jaeger , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.middleware 3.7.1.dev18\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-04-19 23:53+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-09-07 08:09+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: German\n" msgid "Request is too large." msgstr "Die Anfrage ist zu groß." oslo.middleware-3.34.0/oslo_middleware/healthcheck/0000775000175100017510000000000013227207404022332 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo_middleware/healthcheck/__main__.py0000666000175100017510000000440113227207123024423 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse from six.moves import SimpleHTTPServer # noqa from six.moves import socketserver import webob from oslo_middleware import healthcheck class HttpHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): @webob.dec.wsgify def dummy_application(req): return 'test' app = healthcheck.Healthcheck(dummy_application, {'detailed': True}) req = webob.Request.blank("/healthcheck", accept='text/html', method='GET') res = req.get_response(app) self.send_response(res.status_code) for header_name, header_value in res.headerlist: self.send_header(header_name, header_value) self.end_headers() self.wfile.write(res.body) self.wfile.close() def positive_int(blob): value = int(blob) if value < 0: msg = "%r is not a positive integer" % blob raise argparse.ArgumentTypeError(msg) return value def create_server(port=0): handler = HttpHandler server = socketserver.TCPServer(("", port), handler) return server def main(args=None): """Runs a basic http server to show healthcheck functionality.""" parser = argparse.ArgumentParser() parser.add_argument("-p", "--port", help="Unused port to run the tiny" " http server on (or zero to select a" " random unused port)", type=positive_int, required=True) args = parser.parse_args(args=args) server = create_server(args.port) print("Serving at port: %s" % server.server_address[1]) server.serve_forever() if __name__ == '__main__': main() oslo.middleware-3.34.0/oslo_middleware/healthcheck/pluginbase.py0000666000175100017510000000276013227207123025042 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import six class HealthcheckResult(object): """Result of a ``healthcheck`` method call should be this object.""" def __init__(self, available, reason, details=None): self.available = available self.reason = reason self.details = details @six.add_metaclass(abc.ABCMeta) class HealthcheckBaseExtension(object): def __init__(self, oslo_conf, conf): self.oslo_conf = oslo_conf self.conf = conf @abc.abstractmethod def healthcheck(self, server_port): """method called by the healthcheck middleware return: HealthcheckResult object """ def _conf_get(self, key, group='healthcheck'): if key in self.conf: # Validate value type self.oslo_conf.set_override(key, self.conf[key], group=group) return getattr(getattr(self.oslo_conf, group), key) oslo.middleware-3.34.0/oslo_middleware/healthcheck/opts.py0000666000175100017510000000340413227207123023672 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_config import cfg HEALTHCHECK_OPTS = [ cfg.StrOpt('path', default='/healthcheck', deprecated_for_removal=True, help='The path to respond to healtcheck requests on.'), cfg.BoolOpt('detailed', default=False, help='Show more detailed information as part of the response'), cfg.ListOpt('backends', default=[], help='Additional backends that can perform health checks and ' 'report that information back as part of a request.'), ] DISABLE_BY_FILE_OPTS = [ cfg.StrOpt('disable_by_file_path', default=None, help='Check the presence of a file to determine if an ' 'application is running on a port. Used by ' 'DisableByFileHealthcheck plugin.'), ] DISABLE_BY_FILES_OPTS = [ cfg.ListOpt('disable_by_file_paths', default=[], help='Check the presence of a file based on a port to ' 'determine if an application is running on a port. ' 'Expects a "port:path" list of strings. Used by ' 'DisableByFilesPortsHealthcheck plugin.'), ] oslo.middleware-3.34.0/oslo_middleware/healthcheck/__init__.py0000666000175100017510000004103613227207123024447 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import gc import json import platform import socket import sys import traceback from debtcollector import removals import jinja2 from oslo_utils import reflection from oslo_utils import strutils from oslo_utils import timeutils import six import stevedore import webob.dec import webob.exc import webob.response try: import greenlet except ImportError: greenlet = None from oslo_middleware import base from oslo_middleware.healthcheck import opts def _find_objects(t): return [o for o in gc.get_objects() if isinstance(o, t)] def _expand_template(contents, params): tpl = jinja2.Template(source=contents, undefined=jinja2.StrictUndefined) return tpl.render(**params) class Healthcheck(base.ConfigurableMiddleware): """Healthcheck application used for monitoring. It will respond 200 with "OK" as the body. Or a 503 with the reason as the body if one of the backends reports an application issue. This is useful for the following reasons: * Load balancers can 'ping' this url to determine service availability. * Provides an endpoint that is similar to 'mod_status' in apache which can provide details (or no details, depending on if configured) about the activity of the server. * *(and more)* Example requests/responses (**not** detailed mode):: $ curl -i -X HEAD "http://0.0.0.0:8775/healthcheck" HTTP/1.1 204 No Content Content-Type: text/plain; charset=UTF-8 Content-Length: 0 Date: Fri, 11 Sep 2015 18:55:08 GMT $ curl -i -X GET "http://0.0.0.0:8775/healthcheck" HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 2 Date: Fri, 11 Sep 2015 18:55:43 GMT OK $ curl -X GET -i -H "Accept: application/json" "http://0.0.0.0:8775/healthcheck" HTTP/1.0 200 OK Date: Wed, 24 Aug 2016 06:09:58 GMT Content-Type: application/json Content-Length: 63 { "detailed": false, "reasons": [ "OK" ] } $ curl -X GET -i -H "Accept: text/html" "http://0.0.0.0:8775/healthcheck" HTTP/1.0 200 OK Date: Wed, 24 Aug 2016 06:10:42 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 239 Healthcheck Status

Result of 1 checks:

Reason
OK

Example requests/responses (**detailed** mode):: $ curl -X GET -i -H "Accept: application/json" "http://0.0.0.0:8775/healthcheck" HTTP/1.0 200 OK Date: Wed, 24 Aug 2016 06:11:59 GMT Content-Type: application/json Content-Length: 3480 { "detailed": true, "gc": { "counts": [ 293, 10, 5 ], "threshold": [ 700, 10, 10 ] }, "greenthreads": [ ... ], "now": "2016-08-24 06:11:59.419267", "platform": "Linux-4.2.0-27-generic-x86_64-with-Ubuntu-14.04-trusty", "python_version": "2.7.6 (default, Jun 22 2015, 17:58:13) \\n[GCC 4.8.2]", "reasons": [ { "class": "HealthcheckResult", "details": "Path '/tmp/dead' was not found", "reason": "OK" } ], "threads": [ ... ] } $ curl -X GET -i -H "Accept: text/html" "http://0.0.0.0:8775/healthcheck" HTTP/1.0 200 OK Date: Wed, 24 Aug 2016 06:36:07 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 6838 Healthcheck Status

Server status

Server hostname:
...
Current time:
2016-08-24 06:36:07.302559
Python version:
2.7.6 (default, Jun 22 2015, 17:58:13)
       [GCC 4.8.2]
Platform:
Linux-4.2.0-27-generic-x86_64-with-Ubuntu-14.04-trusty

Garbage collector:

Counts:
(77, 1, 6)
Thresholds:
(700, 10, 10)

Result of 1 checks:

Kind Reason Details
HealthcheckResult OK Path '/tmp/dead' was not found

1 greenthread(s) active:

  File "oslo_middleware/healthcheck/__main__.py", line 94, in <module>
           main()
         File "oslo_middleware/healthcheck/__main__.py", line 90, in main
           server.serve_forever()
         ...
       

1 thread(s) active:

  File "oslo_middleware/healthcheck/__main__.py", line 94, in <module>
           main()
         File "oslo_middleware/healthcheck/__main__.py", line 90, in main
           server.serve_forever()
         ....
       
Example of paste configuration: .. code-block:: ini [app:healthcheck] use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_disable [pipeline:public_api] pipeline = healthcheck sizelimit [...] public_service Multiple filter sections can be defined if it desired to have pipelines with different healthcheck configuration, example: .. code-block:: ini [composite:public_api] use = egg:Paste#urlmap / = public_api_pipeline /healthcheck = healthcheck_public [composite:admin_api] use = egg:Paste#urlmap / = admin_api_pipeline /healthcheck = healthcheck_admin [pipeline:public_api_pipeline] pipeline = sizelimit [...] public_service [pipeline:admin_api_pipeline] pipeline = sizelimit [...] admin_service [app:healthcheck_public] use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_public_disable [filter:healthcheck_admin] use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_admin_disable """ NAMESPACE = "oslo.middleware.healthcheck" HEALTHY_TO_STATUS_CODES = { True: webob.exc.HTTPOk.code, False: webob.exc.HTTPServiceUnavailable.code, } HEAD_HEALTHY_TO_STATUS_CODES = { True: webob.exc.HTTPNoContent.code, False: webob.exc.HTTPServiceUnavailable.code, } PLAIN_RESPONSE_TEMPLATE = """ {% for reason in reasons %} {% if reason %}{{reason}}{% endif -%} {% endfor %} """ HTML_RESPONSE_TEMPLATE = """ Healthcheck Status {% if detailed -%}

Server status

{% if hostname -%} Server hostname:
{{hostname|e}}
{%- endif %} Current time:
{{now|e}}
Python version:
{{python_version|e}}
Platform:
{{platform|e}}

Garbage collector:

Counts:
{{gc.counts|e}}
Thresholds:
{{gc.threshold|e}}

{%- endif %}

Result of {{results|length}} checks:

{% if detailed -%} {% else %} {%- endif %} {% for result in results -%} {% if result.reason -%} {% if detailed -%} {%- endif %} {% if detailed -%} {%- endif %} {%- endif %} {%- endfor %}
Kind Reason Details Reason
{{result.class|e}}{{result.reason|e}}{{result.details|e}}

{% if detailed -%} {% if greenthreads -%}

{{greenthreads|length}} greenthread(s) active:

{% for stack in greenthreads -%} {%- endfor %}
{{stack|e}}

{%- endif %} {% if threads -%}

{{threads|length}} thread(s) active:

{% for stack in threads -%} {%- endfor %}
{{stack|e}}
{%- endif %} {%- endif %} """ def __init__(self, *args, **kwargs): super(Healthcheck, self).__init__(*args, **kwargs) self.oslo_conf.register_opts(opts.HEALTHCHECK_OPTS, group='healthcheck') self._path = self._conf_get('path') self._show_details = self._conf_get('detailed') self._backends = stevedore.NamedExtensionManager( self.NAMESPACE, self._conf_get('backends'), name_order=True, invoke_on_load=True, invoke_args=(self.oslo_conf, self.conf)) self._accept_to_functor = collections.OrderedDict([ # Order here matters... ('text/plain', self._make_text_response), ('text/html', self._make_html_response), ('application/json', self._make_json_response), ]) self._accept_order = tuple(six.iterkeys(self._accept_to_functor)) # When no accept type matches instead of returning 406 we will # always return text/plain (because sending an error from this # middleware actually can cause issues). self._default_accept = 'text/plain' self._ignore_path = False def _conf_get(self, key, group='healthcheck'): return super(Healthcheck, self)._conf_get(key, group=group) @removals.remove( message="The healthcheck middleware must now be configured as " "an application, not as a filter") @classmethod def factory(cls, global_conf, **local_conf): return super(Healthcheck, cls).factory(global_conf, **local_conf) @classmethod def app_factory(cls, global_conf, **local_conf): """Factory method for paste.deploy. :param global_conf: dict of options for all middlewares (usually the [DEFAULT] section of the paste deploy configuration file) :param local_conf: options dedicated to this middleware (usually the option defined in the middleware section of the paste deploy configuration file) """ conf = global_conf.copy() if global_conf else {} conf.update(local_conf) o = cls(application=None, conf=conf) o._ignore_path = True return o @staticmethod def _get_threadstacks(): threadstacks = [] try: active_frames = sys._current_frames() except AttributeError: pass else: buf = six.StringIO() for stack in six.itervalues(active_frames): traceback.print_stack(stack, file=buf) threadstacks.append(buf.getvalue()) buf.seek(0) buf.truncate() return threadstacks @staticmethod def _get_greenstacks(): greenstacks = [] if greenlet is not None: buf = six.StringIO() for gt in _find_objects(greenlet.greenlet): traceback.print_stack(gt.gr_frame, file=buf) greenstacks.append(buf.getvalue()) buf.seek(0) buf.truncate() return greenstacks @staticmethod def _pretty_json_dumps(contents): return json.dumps(contents, indent=4, sort_keys=True) @staticmethod def _are_results_healthy(results): for result in results: if not result.available: return False return True def _make_text_response(self, results, healthy): params = { 'reasons': [result.reason for result in results], 'detailed': self._show_details, } body = _expand_template(self.PLAIN_RESPONSE_TEMPLATE, params) return (body.strip(), 'text/plain') def _make_json_response(self, results, healthy): if self._show_details: body = { 'detailed': True, 'python_version': sys.version, 'now': str(timeutils.utcnow()), 'platform': platform.platform(), 'gc': { 'counts': gc.get_count(), 'threshold': gc.get_threshold(), }, } reasons = [] for result in results: reasons.append({ 'reason': result.reason, 'details': result.details or '', 'class': reflection.get_class_name(result, fully_qualified=False), }) body['reasons'] = reasons body['greenthreads'] = self._get_greenstacks() body['threads'] = self._get_threadstacks() else: body = { 'reasons': [result.reason for result in results], 'detailed': False, } return (self._pretty_json_dumps(body), 'application/json') def _make_head_response(self, results, healthy): return ( "", "text/plain") def _make_html_response(self, results, healthy): try: hostname = socket.gethostname() except socket.error: hostname = None translated_results = [] for result in results: translated_results.append({ 'details': result.details or '', 'reason': result.reason, 'class': reflection.get_class_name(result, fully_qualified=False), }) params = { 'healthy': healthy, 'hostname': hostname, 'results': translated_results, 'detailed': self._show_details, 'now': str(timeutils.utcnow()), 'python_version': sys.version, 'platform': platform.platform(), 'gc': { 'counts': gc.get_count(), 'threshold': gc.get_threshold(), }, 'threads': self._get_threadstacks(), 'greenthreads': self._get_threadstacks(), } body = _expand_template(self.HTML_RESPONSE_TEMPLATE, params) return (body.strip(), 'text/html') @webob.dec.wsgify def process_request(self, req): if not self._ignore_path and req.path != self._path: return None results = [ext.obj.healthcheck(req.server_port) for ext in self._backends] healthy = self._are_results_healthy(results) if req.method == "HEAD": functor = self._make_head_response status = self.HEAD_HEALTHY_TO_STATUS_CODES[healthy] else: status = self.HEALTHY_TO_STATUS_CODES[healthy] accept_type = req.accept.best_match(self._accept_order) if not accept_type: accept_type = self._default_accept functor = self._accept_to_functor[accept_type] body, content_type = functor(results, healthy) return webob.response.Response(status=status, body=body, charset='UTF-8', content_type=content_type) oslo.middleware-3.34.0/oslo_middleware/healthcheck/disable_by_file.py0000666000175100017510000001127013227207123026001 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import os from oslo_middleware.healthcheck import opts from oslo_middleware.healthcheck import pluginbase LOG = logging.getLogger(__name__) class DisableByFilesPortsHealthcheck(pluginbase.HealthcheckBaseExtension): """DisableByFilesPorts healthcheck middleware plugin This plugin checks presence of a file that is provided for a application running on a certain port to report if the service is unavailable or not. Example of middleware configuration: .. code-block:: ini [filter:healthcheck] paste.filter_factory = oslo_middleware:Healthcheck.factory path = /healthcheck backends = disable_by_files_ports disable_by_file_paths = 5000:/var/run/keystone/healthcheck_disable, \ 35357:/var/run/keystone/admin_healthcheck_disable # set to True to enable detailed output, False is the default detailed = False """ def __init__(self, *args, **kwargs): super(DisableByFilesPortsHealthcheck, self).__init__(*args, **kwargs) self.oslo_conf.register_opts(opts.DISABLE_BY_FILES_OPTS, group='healthcheck') self.status_files = {} paths = self._conf_get('disable_by_file_paths') self.status_files.update(self._iter_paths_ports(paths)) @staticmethod def _iter_paths_ports(paths): for port_path in paths: port_path = port_path.strip() if port_path: # On windows, drive letters are followed by colons, # which makes split() return 3 elements in this case port, path = port_path.split(":", 1) port = int(port) yield (port, path) def healthcheck(self, server_port): path = self.status_files.get(server_port) if not path: LOG.warning('DisableByFilesPorts healthcheck middleware' ' enabled without disable_by_file_paths set' ' for port %s', server_port) return pluginbase.HealthcheckResult(available=True, reason="OK") else: if not os.path.exists(path): return pluginbase.HealthcheckResult(available=True, reason="OK") else: return pluginbase.HealthcheckResult(available=False, reason="DISABLED BY FILE") class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension): """DisableByFile healthcheck middleware plugin This plugin checks presence of a file to report if the service is unavailable or not. Example of middleware configuration: .. code-block:: ini [filter:healthcheck] paste.filter_factory = oslo_middleware:Healthcheck.factory path = /healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_disable # set to True to enable detailed output, False is the default detailed = False """ def __init__(self, *args, **kwargs): super(DisableByFileHealthcheck, self).__init__(*args, **kwargs) self.oslo_conf.register_opts(opts.DISABLE_BY_FILE_OPTS, group='healthcheck') def healthcheck(self, server_port): path = self._conf_get('disable_by_file_path') if not path: LOG.warning('DisableByFile healthcheck middleware enabled ' 'without disable_by_file_path set') return pluginbase.HealthcheckResult( available=True, reason="OK", details="No 'disable_by_file_path' configuration value" " specified") elif not os.path.exists(path): return pluginbase.HealthcheckResult( available=True, reason="OK", details="Path '%s' was not found" % path) else: return pluginbase.HealthcheckResult( available=False, reason="DISABLED BY FILE", details="Path '%s' was found" % path) oslo.middleware-3.34.0/oslo_middleware/_i18n.py0000666000175100017510000000153013227207123021356 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='oslo_middleware') # The primary translation function using the well-known name "_" _ = _translators.primary oslo.middleware-3.34.0/oslo_middleware/opts.py0000666000175100017510000001477613227207123021445 0ustar zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __all__ = [ 'list_opts', 'list_opts_sizelimit', 'list_opts_ssl', 'list_opts_cors', 'list_opts_http_proxy_to_wsgi', 'list_opts_healthcheck', ] import copy import itertools from oslo_middleware import cors from oslo_middleware.healthcheck import opts as healthcheck_opts from oslo_middleware import http_proxy_to_wsgi from oslo_middleware import sizelimit from oslo_middleware import ssl def list_opts(): """Return a list of oslo.config options for ALL of the middleware classes. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return list( itertools.chain( list_opts_sizelimit(), list_opts_ssl(), list_opts_cors(), list_opts_http_proxy_to_wsgi(), list_opts_healthcheck(), ) ) def list_opts_sizelimit(): """Return a list of oslo.config options for the sizelimit middleware. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return [ ('oslo_middleware', copy.deepcopy(sizelimit._opts)), ] def list_opts_ssl(): """Return a list of oslo.config options for the SSL middleware. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return [ ('oslo_middleware', copy.deepcopy(ssl.OPTS)), ] def list_opts_cors(): """Return a list of oslo.config options for the cors middleware. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return [ ('cors', copy.deepcopy(cors.CORS_OPTS)), ] def list_opts_http_proxy_to_wsgi(): """Return a list of oslo.config options for http_proxy_to_wsgi. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return [ ('oslo_middleware', copy.deepcopy(http_proxy_to_wsgi.OPTS)), ] def list_opts_healthcheck(): """Return a list of oslo.config options for healthcheck. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo.middleware' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ # standard opts and the most common plugin to turn up in sample config. # can figure out a better way of exposing plugin opts later if required. return [ ('healthcheck', copy.deepcopy(healthcheck_opts.HEALTHCHECK_OPTS + healthcheck_opts.DISABLE_BY_FILE_OPTS + healthcheck_opts.DISABLE_BY_FILES_OPTS)) ] oslo.middleware-3.34.0/oslo_middleware/__init__.py0000666000175100017510000000236713227207123022210 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __all__ = ['CatchErrors', 'CorrelationId', 'CORS', 'Debug', 'Healthcheck', 'HTTPProxyToWSGI', 'RequestId', 'RequestBodySizeLimiter', 'SSLMiddleware'] from oslo_middleware.catch_errors import CatchErrors from oslo_middleware.correlation_id import CorrelationId from oslo_middleware.cors import CORS from oslo_middleware.debug import Debug from oslo_middleware.healthcheck import Healthcheck from oslo_middleware.http_proxy_to_wsgi import HTTPProxyToWSGI from oslo_middleware.request_id import RequestId from oslo_middleware.sizelimit import RequestBodySizeLimiter from oslo_middleware.ssl import SSLMiddleware oslo.middleware-3.34.0/oslo_middleware/correlation_id.py0000666000175100017510000000201013227207123023427 0ustar zuulzuul00000000000000# Copyright (c) 2013 Rackspace Hosting # 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. from oslo_utils import uuidutils from oslo_middleware import base class CorrelationId(base.ConfigurableMiddleware): "Middleware that attaches a correlation id to WSGI request" def process_request(self, req): correlation_id = (req.headers.get("X_CORRELATION_ID") or uuidutils.generate_uuid()) req.headers['X_CORRELATION_ID'] = correlation_id oslo.middleware-3.34.0/oslo_middleware/stats.py0000666000175100017510000001103313227207123021575 0ustar zuulzuul00000000000000# Copyright (c) 2016 Cisco Systems # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import re import statsd import webob.dec from oslo_middleware import base LOG = logging.getLogger(__name__) VERSION_REGEX = re.compile("/(v[0-9]{1}\.[0-9]{1})") UUID_REGEX = re.compile( '.*(\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*a', re.IGNORECASE) # UUIDs without the - char, used in some places in Nova URLs. SHORT_UUID_REGEX = re.compile('.*(\.[0-9a-fA-F]{32}).*') class StatsMiddleware(base.ConfigurableMiddleware): """Send stats to statsd based on API requests. Examines the URL path and request method, and sends a stat count and timer to a statsd host based on the path/method. If your statsd is configured to send stats to Graphite, you'll end up with stat names of the form:: timer..... Note that URLs with versions in them (pretty much all of Openstack) are always processed to replace the dot with _, so for example v2.0 becomes v2_0, and v1.1 becomes v1_1, since a dot '.' has special meaning in Graphite. The original StatsD is written in nodejs. If you want a Python implementation, install Bucky instead as it's a drop-in replacement (and much nicer IMO). The Paste config must contain some parameters. Configure a filter like this:: [filter:stats] paste.filter_factory = oslo_middleware.stats:StatsMiddleware.factory name = my_application_name # e.g. 'glance' stats_host = my_statsd_host.example.com # Optional args to further process the stat name that's generated: remove_uuid = True remove_short_uuid = True # The above uuid processing is required in, e.g. Nova, if you want to # collect generic stats rather than one per server instance. """ def __init__(self, application, conf): super(StatsMiddleware, self).__init__(application, conf) self.application = application self.stat_name = conf.get('name') if self.stat_name is None: raise AttributeError('name must be specified') self.stats_host = conf.get('stats_host') if self.stats_host is None: raise AttributeError('stats_host must be specified') self.remove_uuid = conf.get('remove_uuid', False) self.remove_short_uuid = conf.get('remove_short_uuid', False) self.statsd = statsd.StatsClient(self.stats_host) @staticmethod def strip_short_uuid(path): """Remove short-form UUID from supplied path. Only call after replacing slashes with dots in path. """ match = SHORT_UUID_REGEX.match(path) if match is None: return path return path.replace(match.group(1), '') @staticmethod def strip_uuid(path): """Remove normal-form UUID from supplied path. Only call after replacing slashes with dots in path. """ match = UUID_REGEX.match(path) if match is None: return path return path.replace(match.group(1), '') @staticmethod def strip_dot_from_version(path): # Replace vN.N with vNN. match = VERSION_REGEX.match(path) if match is None: return path return path.replace(match.group(1), match.group(1).replace('.', '')) @webob.dec.wsgify def __call__(self, request): path = request.path path = self.strip_dot_from_version(path) # Remove leading slash, if any, so we can be sure of the number # of dots just below. path = path.lstrip('/') stat = "{name}.{method}".format( name=self.stat_name, method=request.method) if path != '': stat += '.' + path.replace('/', '.') if self.remove_short_uuid: stat = self.strip_short_uuid(stat) if self.remove_uuid: stat = self.strip_uuid(stat) LOG.debug("Incrementing stat count %s", stat) with self.statsd.timer(stat): return request.get_response(self.application) oslo.middleware-3.34.0/oslo_middleware/cors.py0000666000175100017510000004025213227207123021412 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. See the License for the specific language governing permissions and # limitations under the License. import copy import logging import debtcollector from oslo_config import cfg from oslo_middleware import base import six import webob.exc LOG = logging.getLogger(__name__) CORS_OPTS = [ cfg.ListOpt('allowed_origin', help='Indicate whether this resource may be shared with the ' 'domain received in the requests "origin" header. ' 'Format: "://[:]", no trailing ' 'slash. Example: https://horizon.example.com'), cfg.BoolOpt('allow_credentials', default=True, help='Indicate that the actual request can include user ' 'credentials'), cfg.ListOpt('expose_headers', default=[], help='Indicate which headers are safe to expose to the API. ' 'Defaults to HTTP Simple Headers.'), cfg.IntOpt('max_age', default=3600, help='Maximum cache age of CORS preflight requests.'), cfg.ListOpt('allow_methods', default=['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'PATCH'], # RFC 2616, RFC 5789 help='Indicate which methods can be used during the actual ' 'request.'), cfg.ListOpt('allow_headers', default=[], help='Indicate which header field names may be used during ' 'the actual request.') ] def set_defaults(**kwargs): """Override the default values for configuration options. This method permits a project to override the default CORS option values. For example, it may wish to offer a set of sane default headers which allow it to function with only minimal additional configuration. :param allow_credentials: Whether to permit credentials. :type allow_credentials: bool :param expose_headers: A list of headers to expose. :type expose_headers: List of Strings :param max_age: Maximum cache duration in seconds. :type max_age: Int :param allow_methods: List of HTTP methods to permit. :type allow_methods: List of Strings :param allow_headers: List of HTTP headers to permit from the client. :type allow_headers: List of Strings """ # Since 'None' is a valid config override, we have to use kwargs. Else # there's no good way for a user to override only one option, because all # the others would be overridden to 'None'. valid_params = set(k.name for k in CORS_OPTS if k.name != 'allowed_origin') passed_params = set(k for k in kwargs) wrong_params = passed_params - valid_params if wrong_params: raise AttributeError('Parameter(s) [%s] invalid, please only use [%s]' % (wrong_params, valid_params)) # Set global defaults. cfg.set_defaults(CORS_OPTS, **kwargs) class InvalidOriginError(Exception): """Exception raised when Origin is invalid.""" def __init__(self, origin): self.origin = origin super(InvalidOriginError, self).__init__( 'CORS request from origin \'%s\' not permitted.' % origin) class CORS(base.ConfigurableMiddleware): """CORS Middleware. This middleware allows a WSGI app to serve CORS headers for multiple configured domains. For more information, see http://www.w3.org/TR/cors/ """ simple_headers = [ 'Accept', 'Accept-Language', 'Content-Type', 'Cache-Control', 'Content-Language', 'Expires', 'Last-Modified', 'Pragma' ] def __init__(self, application, *args, **kwargs): super(CORS, self).__init__(application, *args, **kwargs) # Begin constructing our configuration hash. self.allowed_origins = {} self._init_conf() def sanitize(csv_list): try: return [str.strip(x) for x in csv_list.split(',')] except Exception: return None @classmethod def factory(cls, global_conf, **local_conf): """factory method for paste.deploy allowed_origin: Protocol, host, and port for the allowed origin. allow_credentials: Whether to permit credentials. expose_headers: A list of headers to expose. max_age: Maximum cache duration. allow_methods: List of HTTP methods to permit. allow_headers: List of HTTP headers to permit from the client. """ if ('allowed_origin' not in local_conf and 'oslo_config_project' not in local_conf): raise TypeError("allowed_origin or oslo_config_project " "is required") return super(CORS, cls).factory(global_conf, **local_conf) def _init_conf(self): '''Initialize this middleware from an oslo.config instance.''' # First, check the configuration and register global options. self.oslo_conf.register_opts(CORS_OPTS, 'cors') allowed_origin = self._conf_get('allowed_origin', 'cors') allow_credentials = self._conf_get('allow_credentials', 'cors') expose_headers = self._conf_get('expose_headers', 'cors') max_age = self._conf_get('max_age', 'cors') allow_methods = self._conf_get('allow_methods', 'cors') allow_headers = self._conf_get('allow_headers', 'cors') # Clone our original CORS_OPTS, and set the defaults to whatever is # set in the global conf instance. This is done explicitly (instead # of **kwargs), since we don't accidentally want to catch # allowed_origin. subgroup_opts = copy.deepcopy(CORS_OPTS) cfg.set_defaults(subgroup_opts, allow_credentials=allow_credentials, expose_headers=expose_headers, max_age=max_age, allow_methods=allow_methods, allow_headers=allow_headers) # If the default configuration contains an allowed_origin, don't # forget to register that. self.add_origin(allowed_origin=allowed_origin, allow_credentials=allow_credentials, expose_headers=expose_headers, max_age=max_age, allow_methods=allow_methods, allow_headers=allow_headers) # Iterate through all the loaded config sections, looking for ones # prefixed with 'cors.' for section in self.oslo_conf.list_all_sections(): if section.startswith('cors.'): debtcollector.deprecate('Multiple configuration blocks are ' 'deprecated and will be removed in ' 'future versions. Please consolidate ' 'your configuration in the [cors] ' 'configuration block.') # Register with the preconstructed defaults self.oslo_conf.register_opts(subgroup_opts, section) self.add_origin(**self.oslo_conf[section]) def add_origin(self, allowed_origin, allow_credentials=True, expose_headers=None, max_age=None, allow_methods=None, allow_headers=None): '''Add another origin to this filter. :param allowed_origin: Protocol, host, and port for the allowed origin. :param allow_credentials: Whether to permit credentials. :param expose_headers: A list of headers to expose. :param max_age: Maximum cache duration. :param allow_methods: List of HTTP methods to permit. :param allow_headers: List of HTTP headers to permit from the client. :return: ''' # NOTE(dims): Support older code that still passes in # a string for allowed_origin instead of a list if isinstance(allowed_origin, six.string_types): # TODO(krotscheck): https://review.openstack.org/#/c/312687/ LOG.warning('DEPRECATED: The `allowed_origin` keyword argument in ' '`add_origin()` should be a list, found String.') allowed_origin = [allowed_origin] if allowed_origin: for origin in allowed_origin: if origin in self.allowed_origins: LOG.warning('Allowed origin [%s] already exists, skipping' % (allowed_origin,)) continue self.allowed_origins[origin] = { 'allow_credentials': allow_credentials, 'expose_headers': expose_headers, 'max_age': max_age, 'allow_methods': allow_methods, 'allow_headers': allow_headers } def process_response(self, response, request=None): '''Check for CORS headers, and decorate if necessary. Perform two checks. First, if an OPTIONS request was issued, let the application handle it, and (if necessary) decorate the response with preflight headers. In this case, if a 404 is thrown by the underlying application (i.e. if the underlying application does not handle OPTIONS requests, the response code is overridden. In the case of all other requests, regular request headers are applied. ''' # Sanity precheck: If we detect CORS headers provided by something in # in the middleware chain, assume that it knows better. if 'Access-Control-Allow-Origin' in response.headers: return response # Doublecheck for an OPTIONS request. if request.method == 'OPTIONS': return self._apply_cors_preflight_headers(request=request, response=response) # Apply regular CORS headers. self._apply_cors_request_headers(request=request, response=response) # Finally, return the response. return response @staticmethod def _split_header_values(request, header_name): """Convert a comma-separated header value into a list of values.""" values = [] if header_name in request.headers: for value in request.headers[header_name].rsplit(','): value = value.strip() if value: values.append(value) return values def _apply_cors_preflight_headers(self, request, response): """Handle CORS Preflight (Section 6.2) Given a request and a response, apply the CORS preflight headers appropriate for the request. """ # If the response contains a 2XX code, we have to assume that the # underlying middleware's response content needs to be persisted. # Otherwise, create a new response. if 200 > response.status_code or response.status_code >= 300: response = base.NoContentTypeResponse(status=webob.exc.HTTPOk.code) # Does the request have an origin header? (Section 6.2.1) if 'Origin' not in request.headers: return response # Is this origin registered? (Section 6.2.2) try: origin, cors_config = self._get_cors_config_by_origin( request.headers['Origin']) except InvalidOriginError: return response # If there's no request method, exit. (Section 6.2.3) if 'Access-Control-Request-Method' not in request.headers: LOG.debug('CORS request does not contain ' 'Access-Control-Request-Method header.') return response request_method = request.headers['Access-Control-Request-Method'] # Extract Request headers. If parsing fails, exit. (Section 6.2.4) try: request_headers = \ self._split_header_values(request, 'Access-Control-Request-Headers') except Exception: LOG.debug('Cannot parse request headers.') return response # Compare request method to permitted methods (Section 6.2.5) permitted_methods = cors_config['allow_methods'] if request_method not in permitted_methods: LOG.debug('Request method \'%s\' not in permitted list: %s' % (request_method, permitted_methods)) return response # Compare request headers to permitted headers, case-insensitively. # (Section 6.2.6) permitted_headers = [header.upper() for header in (cors_config['allow_headers'] + self.simple_headers)] for requested_header in request_headers: upper_header = requested_header.upper() if upper_header not in permitted_headers: LOG.debug('Request header \'%s\' not in permitted list: %s' % (requested_header, permitted_headers)) return response # Set the default origin permission headers. (Sections 6.2.7, 6.4) response.headers['Vary'] = 'Origin' response.headers['Access-Control-Allow-Origin'] = origin # Does this CORS configuration permit credentials? (Section 6.2.7) if cors_config['allow_credentials']: response.headers['Access-Control-Allow-Credentials'] = 'true' # Attach Access-Control-Max-Age if appropriate. (Section 6.2.8) if 'max_age' in cors_config and cors_config['max_age']: response.headers['Access-Control-Max-Age'] = \ str(cors_config['max_age']) # Attach Access-Control-Allow-Methods. (Section 6.2.9) response.headers['Access-Control-Allow-Methods'] = request_method # Attach Access-Control-Allow-Headers. (Section 6.2.10) if request_headers: response.headers['Access-Control-Allow-Headers'] = \ ','.join(request_headers) return response def _get_cors_config_by_origin(self, origin): if origin not in self.allowed_origins: if '*' in self.allowed_origins: origin = '*' else: LOG.debug('CORS request from origin \'%s\' not permitted.' % origin) raise InvalidOriginError(origin) return origin, self.allowed_origins[origin] def _apply_cors_request_headers(self, request, response): """Handle Basic CORS Request (Section 6.1) Given a request and a response, apply the CORS headers appropriate for the request to the response. """ # Does the request have an origin header? (Section 6.1.1) if 'Origin' not in request.headers: return # Is this origin registered? (Section 6.1.2) try: origin, cors_config = self._get_cors_config_by_origin( request.headers['Origin']) except InvalidOriginError: return # Set the default origin permission headers. (Sections 6.1.3 & 6.4) if 'Vary' in response.headers: response.headers['Vary'] += ',Origin' else: response.headers['Vary'] = 'Origin' response.headers['Access-Control-Allow-Origin'] = origin # Does this CORS configuration permit credentials? (Section 6.1.3) if cors_config['allow_credentials']: response.headers['Access-Control-Allow-Credentials'] = 'true' # Attach the exposed headers and exit. (Section 6.1.4) if cors_config['expose_headers']: response.headers['Access-Control-Expose-Headers'] = \ ','.join(cors_config['expose_headers']) # NOTE(sileht): Shortcut for backwards compatibility filter_factory = CORS.factory oslo.middleware-3.34.0/oslo_middleware/base.py0000666000175100017510000001152113227207123021353 0ustar zuulzuul00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Base class(es) for WSGI Middleware.""" import six if six.PY2: from inspect import getargspec as getfullargspec else: from inspect import getfullargspec import webob.dec import webob.request import webob.response from oslo_config import cfg class NoContentTypeResponse(webob.response.Response): default_content_type = None # prevents webob assigning content type class NoContentTypeRequest(webob.request.Request): ResponseClass = NoContentTypeResponse class ConfigurableMiddleware(object): """Base WSGI middleware wrapper. These classes require an application to be initialized that will be called next. By default the middleware will simply call its wrapped app, or you can override __call__ to customize its behavior. """ @classmethod def factory(cls, global_conf, **local_conf): """Factory method for paste.deploy. :param global_conf: dict of options for all middlewares (usually the [DEFAULT] section of the paste deploy configuration file) :param local_conf: options dedicated to this middleware (usually the option defined in the middleware section of the paste deploy configuration file) """ conf = global_conf.copy() if global_conf else {} conf.update(local_conf) def middleware_filter(app): return cls(app, conf) return middleware_filter def __init__(self, application, conf=None): """Base middleware constructor :param conf: a dict of options or a cfg.ConfigOpts object """ self.application = application # NOTE(sileht): If the configuration come from oslo.config # just use it. if isinstance(conf, cfg.ConfigOpts): self.conf = {} self.oslo_conf = conf else: self.conf = conf or {} if "oslo_config_project" in self.conf: if 'oslo_config_file' in self.conf: default_config_files = [self.conf['oslo_config_file']] else: default_config_files = None if 'oslo_config_program' in self.conf: program = self.conf['oslo_config_program'] else: program = None self.oslo_conf = cfg.ConfigOpts() self.oslo_conf([], project=self.conf['oslo_config_project'], prog=program, default_config_files=default_config_files, validate_default_values=True) else: # Fallback to global object self.oslo_conf = cfg.CONF def _conf_get(self, key, group="oslo_middleware"): if key in self.conf: # Validate value type self.oslo_conf.set_override(key, self.conf[key], group=group) return getattr(getattr(self.oslo_conf, group), key) @staticmethod def process_request(req): """Called on each request. If this returns None, the next application down the stack will be executed. If it returns a response then that response will be returned and execution will stop here. """ return None @staticmethod def process_response(response, request=None): """Do whatever you'd like to the response.""" return response @webob.dec.wsgify(RequestClass=NoContentTypeRequest) def __call__(self, req): response = self.process_request(req) if response: return response response = req.get_response(self.application) args = getfullargspec(self.process_response)[0] if 'request' in args: return self.process_response(response, request=req) return self.process_response(response) class Middleware(ConfigurableMiddleware): """Legacy base WSGI middleware wrapper. Legacy interface that doesn't pass configuration options to the middleware when it's loaded via paste.deploy. """ @classmethod def factory(cls, global_conf, **local_conf): """Factory method for paste.deploy.""" return cls oslo.middleware-3.34.0/oslo_middleware/catch_errors.py0000666000175100017510000000272013227207123023120 0ustar zuulzuul00000000000000# Copyright (c) 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import re import webob.dec import webob.exc from oslo_middleware import base LOG = logging.getLogger(__name__) _TOKEN_RE = re.compile('^(X-\w+-Token):.*$', flags=re.MULTILINE) class CatchErrors(base.ConfigurableMiddleware): """Middleware that provides high-level error handling. It catches all exceptions from subsequent applications in WSGI pipeline to hide internal errors from API response. """ @webob.dec.wsgify def __call__(self, req): try: response = req.get_response(self.application) except Exception: req_str = _TOKEN_RE.sub(r'\1: *****', req.as_text()) LOG.exception('An error occurred during ' 'processing the request: %s', req_str) response = webob.exc.HTTPInternalServerError() return response oslo.middleware-3.34.0/oslo_middleware/version.py0000666000175100017510000000126713227207123022134 0ustar zuulzuul00000000000000# Copyright 2016 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. import pbr.version version_info = pbr.version.VersionInfo('oslo.middleware') oslo.middleware-3.34.0/oslo_middleware/ssl.py0000666000175100017510000000345113227207123021245 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. See the License for the specific language governing permissions and # limitations under the License. from debtcollector import removals from oslo_config import cfg from oslo_middleware import base OPTS = [ cfg.StrOpt('secure_proxy_ssl_header', default='X-Forwarded-Proto', deprecated_for_removal=True, help="The HTTP Header that will be used to determine what " "the original request protocol scheme was, even if it was " "hidden by a SSL termination proxy.") ] class SSLMiddleware(base.ConfigurableMiddleware): """SSL termination proxies middleware. This middleware overloads wsgi.url_scheme with the one provided in secure_proxy_ssl_header header. This is useful when behind a SSL termination proxy. """ def __init__(self, application, *args, **kwargs): removals.removed_module(__name__, "oslo_middleware.http_proxy_to_wsgi") super(SSLMiddleware, self).__init__(application, *args, **kwargs) self.oslo_conf.register_opts(OPTS, group='oslo_middleware') def process_request(self, req): self.header_name = 'HTTP_{0}'.format( self._conf_get('secure_proxy_ssl_header').upper() .replace('-', '_')) req.environ['wsgi.url_scheme'] = req.environ.get( self.header_name, req.environ['wsgi.url_scheme']) oslo.middleware-3.34.0/.testr.conf0000666000175100017510000000047713227207123017014 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list oslo.middleware-3.34.0/README.rst0000666000175100017510000000153213227207123016406 0ustar zuulzuul00000000000000=================================== oslo.middleware =================================== .. image:: https://img.shields.io/pypi/v/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Downloads Oslo middleware library includes components that can be injected into wsgi pipelines to intercept request/response flows. The base class can be enhanced with functionality like add/delete/modification of http headers and support for limiting size/connection etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.middleware/latest * Source: https://git.openstack.org/cgit/openstack/oslo.middleware * Bugs: https://bugs.launchpad.net/oslo.middleware oslo.middleware-3.34.0/doc/0000775000175100017510000000000013227207404015463 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/0000775000175100017510000000000013227207404016763 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/user/0000775000175100017510000000000013227207404017741 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/user/history.rst0000666000175100017510000000004013227207123022166 0ustar zuulzuul00000000000000.. include:: ../../../ChangeLog oslo.middleware-3.34.0/doc/source/conf.py0000777000175100017510000000532213227207123020267 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'oslo_config.sphinxext', 'openstackdocstheme', 'stevedore.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.middleware' bug_project = 'oslo.middleware' bug_tag = '' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.middleware' copyright = u'2014, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- 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 = 'openstackdocs' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} oslo.middleware-3.34.0/doc/source/configuration/0000775000175100017510000000000013227207404021632 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/configuration/index.rst0000666000175100017510000000314113227207123023472 0ustar zuulzuul00000000000000============================= Middlewares and configuration ============================= Middlewares can be configured in multiple fashion depending of the application needs. Here is some use-cases: Configuration from the application ---------------------------------- The application code will looks like:: from oslo_middleware import sizelimit from oslo_config import cfg conf = cfg.ConfigOpts() app = sizelimit.RequestBodySizeLimiter(your_wsgi_application, conf) Configuration with paste-deploy and the oslo.config --------------------------------------------------- The paste filter (in /etc/my_app/api-paste.ini) will looks like:: [filter:sizelimit] use = egg:oslo.middleware#sizelimit # In case of the application doesn't use the global oslo.config # object. The middleware must known the app name to load # the application configuration, by setting this: # oslo_config_project = my_app # In some cases, you may need to specify the program name for the project # as well. # oslo_config_program = my_app-api The oslo.config file of the application (eg: /etc/my_app/my_app.conf) will looks like:: [oslo_middleware] max_request_body_size=1000 Configuration with pastedeploy only ----------------------------------- The paste filter (in /etc/my_app/api-paste.ini) will looks like:: [filter:sizelimit] use = egg:oslo.middleware#sizelimit max_request_body_size=1000 This will override any configuration done via oslo.config .. note:: healtcheck middleware does not yet use oslo.config, see :doc:`../reference/healthcheck_plugins` oslo.middleware-3.34.0/doc/source/admin/0000775000175100017510000000000013227207404020053 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/admin/cross-project-cors.rst0000666000175100017510000001550413227207123024353 0ustar zuulzuul00000000000000.. _cross-project: ============================= Cross-origin resource sharing ============================= .. note:: This is a new feature in OpenStack Liberty. OpenStack supports :term:`Cross-Origin Resource Sharing (CORS)`, a W3C specification defining a contract by which the single-origin policy of a user agent (usually a browser) may be relaxed. It permits the javascript engine to access an API that does not reside on the same domain, protocol, or port. This feature is most useful to organizations which maintain one or more custom user interfaces for OpenStack, as it permits those interfaces to access the services directly, rather than requiring an intermediate proxy server. It can, however, also be misused by malicious actors; please review the security advisory below for more information. Enabling CORS with configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In most cases, CORS support is built directly into the service itself. To enable it, simply follow the configuration options exposed in the default configuration file, or add it yourself according to the pattern below. .. code-block:: ini [cors] allowed_origin = https://first_ui.example.com max_age = 3600 allow_methods = GET,POST,PUT,DELETE allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header Additional origins can be explicitly added. To express this in your configuration file, first begin with a ``[cors]`` group as above, into which you place your default configuration values. Then, add as many additional configuration groups as necessary, naming them ``[cors.{something}]`` (each name must be unique). The purpose of the suffix to ``cors.`` is legibility, we recommend using a reasonable human-readable string: .. code-block:: ini [cors.ironic_webclient] # CORS Configuration for a hypothetical ironic webclient, which overrides # authentication allowed_origin = https://ironic.example.com:443 allow_credentials = True [cors.horizon] # CORS Configuration for horizon, which uses global options. allowed_origin = https://horizon.example.com:443 [cors.wildcard] # CORS Configuration for the CORS specified domain wildcard, which only # permits HTTP GET requests. allowed_origin = * allow_methods = GET For more information about CORS configuration, see `cross-origin resource sharing `_ in OpenStack Configuration Reference. Enabling CORS with PasteDeploy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CORS can also be configured using PasteDeploy. First of all, ensure that OpenStack's ``oslo_middleware`` package (version 2.4.0 or later) is available in the Python environment that is running the service. Then, add the following configuration block to your ``paste.ini`` file. .. code-block:: ini [filter:cors] paste.filter_factory = oslo_middleware.cors:filter_factory allowed_origin = https://website.example.com:443 max_age = 3600 allow_methods = GET,POST,PUT,DELETE allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header .. note:: To add an additional domain in oslo_middleware v2.4.0, add another filter. In v3.0.0 and after, you may add multiple domains in the above ``allowed_origin`` field, separated by commas. Security concerns ~~~~~~~~~~~~~~~~~ CORS specifies a wildcard character ``*``, which permits access to all user agents, regardless of domain, protocol, or host. While there are valid use cases for this approach, it also permits a malicious actor to create a convincing facsimile of a user interface, and trick users into revealing authentication credentials. Please carefully evaluate your use case and the relevant documentation for any risk to your organization. .. note:: The CORS specification does not support using this wildcard as a part of a URI. Setting ``allowed_origin`` to ``*`` would work, while ``*.openstack.org`` would not. Troubleshooting ~~~~~~~~~~~~~~~ CORS is very easy to get wrong, as even one incorrect property will violate the prescribed contract. Here are some steps you can take to troubleshoot your configuration. Check the service log --------------------- The CORS middleware used by OpenStack provides verbose debug logging that should reveal most configuration problems. Here are some example log messages, and how to resolve them. Problem ------- ``CORS request from origin 'http://example.com' not permitted.`` Solution -------- A request was received from the origin ``http://example.com``, however this origin was not found in the permitted list. The cause may be a superfluous port notation (ports 80 and 443 do not need to be specified). To correct, ensure that the configuration property for this host is identical to the host indicated in the log message. Problem ------- ``Request method 'DELETE' not in permitted list: GET,PUT,POST`` Solution -------- A user agent has requested permission to perform a DELETE request, however the CORS configuration for the domain does not permit this. To correct, add this method to the ``allow_methods`` configuration property. Problem ------- ``Request header 'X-Custom-Header' not in permitted list: X-Other-Header`` Solution -------- A request was received with the header ``X-Custom-Header``, which is not permitted. Add this header to the ``allow_headers`` configuration property. Open your browser's console log ------------------------------- Most browsers provide helpful debug output when a CORS request is rejected. Usually this happens when a request was successful, but the return headers on the response do not permit access to a property which the browser is trying to access. Manually construct a CORS request --------------------------------- By using ``curl`` or a similar tool, you can trigger a CORS response with a properly constructed HTTP request. An example request and response might look like this. Request example: .. code-block:: console $ curl -I -X OPTIONS https://api.example.com/api -H "Origin: https://ui.example.com" Response example: .. code-block:: console HTTP/1.1 204 No Content Content-Length: 0 Access-Control-Allow-Origin: https://ui.example.com Access-Control-Allow-Methods: GET,POST,PUT,DELETE Access-Control-Expose-Headers: origin,authorization,accept,x-total,x-limit,x-marker,x-client,content-type Access-Control-Allow-Headers: origin,authorization,accept,x-total,x-limit,x-marker,x-client,content-type Access-Control-Max-Age: 3600 If the service does not return any access control headers, check the service log, such as ``/var/log/upstart/ironic-api.log`` for an indication on what went wrong. oslo.middleware-3.34.0/doc/source/admin/index.rst0000666000175100017510000000057713227207123021725 0ustar zuulzuul00000000000000====================== Cross-project features ====================== Many features are common to all the OpenStack services and are consistent in their configuration and deployment patterns. Unless explicitly noted, you can safely assume that the features in this chapter are supported and configured in a consistent manner. .. toctree:: :maxdepth: 2 cross-project-cors.rst oslo.middleware-3.34.0/doc/source/glossary.rst0000666000175100017510000000074213227207123021363 0ustar zuulzuul00000000000000======== Glossary ======== This glossary offers a list of terms and definitions to define a vocabulary for OpenStack-related concepts. C ~ .. glossary:: Cross-Origin Resource Sharing (CORS) A mechanism that allows many resources (for example, fonts, JavaScript) on a web page to be requested from another domain outside the domain from which the resource originated. In particular, JavaScript's AJAX calls can use the XMLHttpRequest mechanism. oslo.middleware-3.34.0/doc/source/install/0000775000175100017510000000000013227207404020431 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/install/index.rst0000666000175100017510000000032513227207123022272 0ustar zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install oslo.middleware Or, if you have virtualenvwrapper installed:: $ mkvirtualenv oslo.middleware $ pip install oslo.middlewareoslo.middleware-3.34.0/doc/source/index.rst0000666000175100017510000000040713227207123020625 0ustar zuulzuul00000000000000.. include:: ../../README.rst Contents ======== .. toctree:: :maxdepth: 2 install/index contributor/index configuration/index admin/index reference/index glossary Release Notes ============= .. toctree:: :maxdepth: 1 user/history oslo.middleware-3.34.0/doc/source/reference/0000775000175100017510000000000013227207404020721 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/reference/cors.rst0000666000175100017510000000542113227207123022423 0ustar zuulzuul00000000000000=============== CORS Middleware =============== This middleware provides a comprehensive, configurable implementation of the CORS_ (Cross Origin Resource Sharing) specification as oslo-supported python wsgi middleware. .. note:: While this middleware supports the use of the `*` wildcard origin in the specification, this feature is not recommended for security reasons. It is provided to simplify basic use of CORS, practically meaning "I don't care how this is used." In an intranet setting, this could lead to leakage of data beyond the intranet and therefore should be avoided. Quickstart ---------- First, include the middleware in your application:: from oslo_middleware import cors app = cors.CORS(your_wsgi_application) Secondly, add as many allowed origins as you would like:: app.add_origin(allowed_origin='https://website.example.com:443', allow_credentials=True, max_age=3600, allow_methods=['GET','PUT','POST','DELETE'], allow_headers=['X-Custom-Header'], expose_headers=['X-Custom-Header']) # ... add more origins here. Configuration for oslo_config ----------------------------- A factory method has been provided to simplify configuration of your CORS domain, using oslo_config:: from oslo_middleware import cors from oslo_config import cfg app = cors.CORS(your_wsgi_application, cfg.CONF) In your application's config file, then include a configuration block something like this:: [cors] allowed_origin=https://website.example.com:443,https://website2.example.com:443 max_age=3600 allow_methods=GET,POST,PUT,DELETE allow_headers=X-Custom-Header expose_headers=X-Custom-Header Configuration for pastedeploy ----------------------------- If your application is using pastedeploy, the following configuration block will add CORS support.:: [filter:cors] use = egg:oslo.middleware#cors allowed_origin=https://website.example.com:443,https://website2.example.com:443 max_age=3600 allow_methods=GET,POST,PUT,DELETE allow_headers=X-Custom-Header expose_headers=X-Custom-Header If your application is using pastedeploy, but would also like to use the existing configuration from oslo_config in order to simplify the points of configuration, this may be done as follows.:: [filter:cors] use = egg:oslo.middleware#cors oslo_config_project = oslo_project_name # Optional field, in case the program name is different from the project: oslo_config_program = oslo_project_name-api Configuration Options --------------------- .. show-options:: oslo.middleware.cors Module Documentation -------------------- .. automodule:: oslo_middleware.cors :members: .. _CORS: http://www.w3.org/TR/cors/ oslo.middleware-3.34.0/doc/source/reference/api.rst0000666000175100017510000000041413227207123022223 0ustar zuulzuul00000000000000===== API ===== .. automodule:: oslo_middleware :members: Configuration Options ===================== RequestBodySizeLimiter ~~~~~~~~~~~~~~~~~~~~~~ .. show-options:: oslo.middleware.sizelimit SSLMiddleware ~~~~~~~~~~~~~ .. show-options:: oslo.middleware.ssl oslo.middleware-3.34.0/doc/source/reference/healthcheck_plugins.rst0000666000175100017510000000051313227207123025456 0ustar zuulzuul00000000000000================================ Healthcheck middleware plugins ================================ .. automodule:: oslo_middleware.healthcheck :members: .. automodule:: oslo_middleware.healthcheck.disable_by_file :members: Available Plugins ------------------ .. list-plugins:: oslo.middleware.healthcheck :detailed: oslo.middleware-3.34.0/doc/source/reference/index.rst0000666000175100017510000000016713227207123022566 0ustar zuulzuul00000000000000============================== oslo.middleware Reference ============================== .. toctree:: :glob: * oslo.middleware-3.34.0/doc/source/contributor/0000775000175100017510000000000013227207404021335 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/doc/source/contributor/index.rst0000666000175100017510000000012413227207123023173 0ustar zuulzuul00000000000000============== Contributing ============== .. include:: ../../../CONTRIBUTING.rst oslo.middleware-3.34.0/setup.cfg0000666000175100017510000000461213227207404016544 0ustar zuulzuul00000000000000[metadata] name = oslo.middleware summary = Oslo Middleware library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/oslo.middleware/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = oslo_middleware [entry_points] oslo.config.opts = oslo.middleware = oslo_middleware.opts:list_opts oslo.middleware.cors = oslo_middleware.opts:list_opts_cors oslo.middleware.sizelimit = oslo_middleware.opts:list_opts_sizelimit oslo.middleware.ssl = oslo_middleware.opts:list_opts_ssl oslo.middleware.http_proxy_to_wsgi = oslo_middleware.opts:list_opts_http_proxy_to_wsgi oslo.middleware.healthcheck = oslo_middleware.opts:list_opts_healthcheck oslo.middleware.healthcheck = disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck paste.app_factory = healthcheck = oslo_middleware:Healthcheck.app_factory paste.filter_factory = catch_errors = oslo_middleware:CatchErrors.factory correlation_id = oslo_middleware:CorrelationId.factory cors = oslo_middleware:CORS.factory debug = oslo_middleware:Debug.factory healthcheck = oslo_middleware:Healthcheck.factory http_proxy_to_wsgi = oslo_middleware:HTTPProxyToWSGI.factory request_id = oslo_middleware:RequestId.factory sizelimit = oslo_middleware:RequestBodySizeLimiter.factory ssl = oslo_middleware:SSLMiddleware.factory [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = oslo_middleware/locale domain = oslo_middleware [update_catalog] domain = oslo_middleware output_dir = oslo_middleware/locale input_file = oslo_middleware/locale/oslo_middleware.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = oslo_middleware/locale/oslo_middleware.pot [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 oslo.middleware-3.34.0/babel.cfg0000666000175100017510000000002013227207123016434 0ustar zuulzuul00000000000000[python: **.py] oslo.middleware-3.34.0/setup.py0000666000175100017510000000200613227207123016426 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # 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) oslo.middleware-3.34.0/oslo.middleware.egg-info/0000775000175100017510000000000013227207404021500 5ustar zuulzuul00000000000000oslo.middleware-3.34.0/oslo.middleware.egg-info/requires.txt0000664000175100017510000000034213227207403024076 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 oslo.config>=5.1.0 oslo.context>=2.19.2 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 six>=1.10.0 stevedore>=1.20.0 WebOb>=1.7.1 debtcollector>=1.2.0 statsd>=3.2.1 oslo.middleware-3.34.0/oslo.middleware.egg-info/entry_points.txt0000664000175100017510000000222413227207403024775 0ustar zuulzuul00000000000000[oslo.config.opts] oslo.middleware = oslo_middleware.opts:list_opts oslo.middleware.cors = oslo_middleware.opts:list_opts_cors oslo.middleware.healthcheck = oslo_middleware.opts:list_opts_healthcheck oslo.middleware.http_proxy_to_wsgi = oslo_middleware.opts:list_opts_http_proxy_to_wsgi oslo.middleware.sizelimit = oslo_middleware.opts:list_opts_sizelimit oslo.middleware.ssl = oslo_middleware.opts:list_opts_ssl [oslo.middleware.healthcheck] disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck [paste.app_factory] healthcheck = oslo_middleware:Healthcheck.app_factory [paste.filter_factory] catch_errors = oslo_middleware:CatchErrors.factory correlation_id = oslo_middleware:CorrelationId.factory cors = oslo_middleware:CORS.factory debug = oslo_middleware:Debug.factory healthcheck = oslo_middleware:Healthcheck.factory http_proxy_to_wsgi = oslo_middleware:HTTPProxyToWSGI.factory request_id = oslo_middleware:RequestId.factory sizelimit = oslo_middleware:RequestBodySizeLimiter.factory ssl = oslo_middleware:SSLMiddleware.factory oslo.middleware-3.34.0/oslo.middleware.egg-info/SOURCES.txt0000664000175100017510000000523613227207404023372 0ustar zuulzuul00000000000000.coveragerc .mailmap .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/glossary.rst doc/source/index.rst doc/source/admin/cross-project-cors.rst doc/source/admin/index.rst doc/source/configuration/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/api.rst doc/source/reference/cors.rst doc/source/reference/healthcheck_plugins.rst doc/source/reference/index.rst doc/source/user/history.rst oslo/__init__.py oslo.middleware.egg-info/PKG-INFO oslo.middleware.egg-info/SOURCES.txt oslo.middleware.egg-info/dependency_links.txt oslo.middleware.egg-info/entry_points.txt oslo.middleware.egg-info/not-zip-safe oslo.middleware.egg-info/pbr.json oslo.middleware.egg-info/requires.txt oslo.middleware.egg-info/top_level.txt oslo/middleware/__init__.py oslo_middleware/__init__.py oslo_middleware/_i18n.py oslo_middleware/base.py oslo_middleware/catch_errors.py oslo_middleware/correlation_id.py oslo_middleware/cors.py oslo_middleware/debug.py oslo_middleware/http_proxy_to_wsgi.py oslo_middleware/opts.py oslo_middleware/request_id.py oslo_middleware/sizelimit.py oslo_middleware/ssl.py oslo_middleware/stats.py oslo_middleware/version.py oslo_middleware/healthcheck/__init__.py oslo_middleware/healthcheck/__main__.py oslo_middleware/healthcheck/disable_by_file.py oslo_middleware/healthcheck/opts.py oslo_middleware/healthcheck/pluginbase.py oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware.po oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware.po oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware.po oslo_middleware/tests/__init__.py oslo_middleware/tests/test_base.py oslo_middleware/tests/test_catch_errors.py oslo_middleware/tests/test_correlation_id.py oslo_middleware/tests/test_cors.py oslo_middleware/tests/test_entry_points.py oslo_middleware/tests/test_healthcheck.py oslo_middleware/tests/test_http_proxy_to_wsgi.py oslo_middleware/tests/test_opts.py oslo_middleware/tests/test_request_id.py oslo_middleware/tests/test_sizelimit.py oslo_middleware/tests/test_ssl.py oslo_middleware/tests/test_stats.py releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml releasenotes/notes/compat_headers-55a635b8ec01b6f1.yaml releasenotes/notes/global_request_id-a8ec7260fbd76444.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.pooslo.middleware-3.34.0/oslo.middleware.egg-info/pbr.json0000664000175100017510000000005613227207403023156 0ustar zuulzuul00000000000000{"git_version": "abfd726", "is_release": true}oslo.middleware-3.34.0/oslo.middleware.egg-info/dependency_links.txt0000664000175100017510000000000113227207403025545 0ustar zuulzuul00000000000000 oslo.middleware-3.34.0/oslo.middleware.egg-info/not-zip-safe0000664000175100017510000000000113227207344023731 0ustar zuulzuul00000000000000 oslo.middleware-3.34.0/oslo.middleware.egg-info/PKG-INFO0000664000175100017510000000345413227207403022602 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.middleware Version: 3.34.0 Summary: Oslo Middleware library Home-page: https://docs.openstack.org/oslo.middleware/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: =================================== oslo.middleware =================================== .. image:: https://img.shields.io/pypi/v/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.middleware.svg :target: https://pypi.python.org/pypi/oslo.middleware/ :alt: Downloads Oslo middleware library includes components that can be injected into wsgi pipelines to intercept request/response flows. The base class can be enhanced with functionality like add/delete/modification of http headers and support for limiting size/connection etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.middleware/latest * Source: https://git.openstack.org/cgit/openstack/oslo.middleware * Bugs: https://bugs.launchpad.net/oslo.middleware Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.middleware-3.34.0/oslo.middleware.egg-info/top_level.txt0000664000175100017510000000002013227207403024221 0ustar zuulzuul00000000000000oslo_middleware