././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0287519 keystonemiddleware-10.9.0/0000775000175000017500000000000000000000000015523 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/.coveragerc0000664000175000017500000000016100000000000017642 0ustar00zuulzuul00000000000000[run] branch = True source = keystonemiddleware omit = keystonemiddleware/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/.stestr.conf0000664000175000017500000000011700000000000017773 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./keystonemiddleware/tests/unit} top_dir=./././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/.zuul.yaml0000664000175000017500000000033300000000000017463 0ustar00zuulzuul00000000000000- project: templates: - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - check-requirements - lib-forward-testing-python3 - release-notes-jobs-python3 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/AUTHORS0000664000175000017500000002026100000000000016574 0ustar00zuulzuul00000000000000Abhishek Sharma Adam Young Alan Pevec Alexander Makarov Alexei Kornienko Alistair Coles Alvaro Lopez Garcia AmalaBasha Andreas Jaeger Andreas Jaeger Andrey Kurilin Andrey Pavlov Anh Tran Anthony Young Artem Vasilyev Arun Kant Ayumu Ueha Ben Nemec Bernhard M. Wiedemann Boris Bobrov Boris Bobrov Brad Pokorny Brant Knudson Brian Cline Brian Lamar Brian Waldon Bryan D. Payne Bryan Davidson Chang Bo Guo ChangBo Guo(gcb) Chmouel Boudjnah Chmouel Boudjnah Chris Dent Christian Berendt Christian Schwede Chuck Short Chuck Thier Clark Boylan Clint Byrum Colleen Murphy Colleen Murphy Colleen Murphy Corey Bryant Cyril Roelandt D G Lee Dan Prince Dan Radez Daniel Gollub Davanum Srinivas Dave Chen Dave Wilde David Höppner <0xffea@gmail.com> David Olorundare David Stanek Dazhao Dean Troyer Deepak Dirk Mueller Divyesh Khandeshi Dmitry Tantsur Dolph Mathews Donagh McCabe Doug Hellmann Emilien Macchi Eoghan Glynn Eric Brown Eric Guo Flavio Percoco Florent Flament Gage Hugo Ghanshyam Mann Gordon Chung Guang Yee Guang Yee Guang Yee Hangdong Zhang Harry Rybacki Henry Nash Hiromu Asahina Igor A. Lukyanenkov Ilya Kharin Ilya Pekelny Iswarya_Vakati Jaewoo Park Jakub Ruzicka Jamie Lennox Jamie Lennox Jamie Lennox Janonymous Jens Harbott Jeremy Stanley Jesse Andrews Ji-Wei Joe Gordon Joe Gordon Joe Heck Joel Friedly John Dennis Jorge Merlino Julien Danjou Kevin Benton Kevin L. Mitchell Kieran Spear Kristi Nikolla Kui Shi Kun Huang Lance Bragstad Lance Bragstad Lauren Taylor Leehom Li (feli5) Lei Zhang Liem Nguyen Lin Hua Cheng LiuNanke Lucas Alvares Gomes Marek Denis Maru Newby Masahito Muroi Matt Fischer Matthew Edmonds Matthew Treinish Matthieu Huin Mehdi Abaakouk Mehdi Abaakouk Michael J Fork Michael Johnson Michal Arbet Mitsuhiro SHIGEMATSU Monty Taylor Morgan Fainberg Morgan Fainberg Mouad Benchchaoui Navid Pustchi Nguyen Hung Phuong Nguyen Van Duc Ondřej Nový OpenStack Release Bot Pete Zaitcev Peter Portante Pádraig Brady Q.hongtao Qiu Yu Rafael Durán Castañeda Rodrigo Duarte Sousa Rodrigo Duarte Sousa Roman Bodnarchuk Roxana Gherle Rui Yuan Dou Sahid Orentino Ferdjaoui Samuel de Medeiros Queiroz Sean McGinnis Sean Perry Sean Winn Shevek Spencer Yu Stefan Nica Stephen Finucane Steve Martinelli Steve Martinelli Steven Hardy Stuart McLaren Takashi Kajinami Takashi Kajinami Thomas Bechtold Thomas Goirand Thomas Goirand Thomas Herve Thomas Herve Tim Burke Tin Lam Tin Lam Tom Cocozzello Tom Fifield Tony Breeds Tristan Cacqueray Van Hung Pham Victor Stinner Vieri <15050873171@163.com> Vishakha Agarwal Vishvananda Ishaya Watanabe Koya Wu Wenxiang Yaguang Tang Yang Youseok Yatin Kumbhare Yi Feng Yusuke Niimi Zhenguo Niu Zhi Yan Liu ZhiQiang Fan ZhiQiang Fan ZhongShengping Zhongyue Luo Zhongyue Luo ankita_wagh ayoung bhagyashris dengzhaosen gordon chung guang-yee guang-yee huangtianhua iswarya_vakati ji-xuepeng lawrancejing lingyongxu liushuobj liuxiaoyang lrqrun mathrock melissaml nachiappan-veerappan-nachiappan niuke pengyuesheng sunyonggen termie ubuntu ushen wanghong wanghui wangxiyuan xingzhou xuhaigang zhang-jinnan zlyqqq ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/CONTRIBUTING.rst0000664000175000017500000000104700000000000020166 0ustar00zuulzuul00000000000000If 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/keystonemiddleware ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/ChangeLog0000664000175000017500000013563300000000000017310 0ustar00zuulzuul00000000000000CHANGES ======= 10.9.0 ------ * Fix memcached dependencies's doc bug 10.8.0 ------ * Imported Translations from Zanata * Remove Python 3.8 support * Use oslo.utils to escape IPv6 address * Get rid of pkg\_resources * Replace deprecated constant\_time\_compare * Bump hacking (slightly) * Update master for stable/2024.2 * s3token: Fix usage of removed Identity v2 API * Imported Translations from Zanata 10.7.1 ------ 10.7.0 ------ * reno: Update master for unmaintained/zed * Remove old excludes * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria * Update master for stable/2024.1 * Remove six again * Imported Translations from Zanata 10.6.0 ------ * reno: Update master for unmaintained/yoga * tox: Drop envdir * Update python classifier in setup.cfg * Remove unnecessary setup\_hook * Python 3.12: do not use utcnow() * Update master for stable/zed 10.5.0 ------ * Update master for stable/2023.2 * External OAuth2.0 Authorization Server Support * External OAuth2.0 Authorization Server Support * Use TOX\_CONSTRAINTS\_FILE 10.4.1 ------ * auth\_token: fix issue when data in cache gets corrupted * Imported Translations from Zanata 10.4.0 ------ * Remove six * Bump hacking to 6.0.x * tox: Trivial formatting changes * Make tox.ini tox 4.0.0 compatible/fix gate * Switch to 2023.1 Python3 unit tests and generic template name 10.3.0 ------ * Add timeout for requests * OAuth 2.0 Mutual-TLS Support * Update master for stable/2023.1 * Add missing doc requirements 10.2.0 ------ * Remove cache invalidation when using expired token * Fix pep8 gate * Support SASL for memcached * Imported Translations from Zanata 10.1.0 ------ * OAuth2.0 Client Credentials Grant Flow Support 10.0.1 ------ * Fix logging notifier unit test * Bump tox minversion to 3.18.0 * Imported Translations from Zanata 10.0.0 ------ * setup.cfg: Replace dashes by underscores * Update python testing as per zed cycle teting runtime 9.5.0 ----- * Update Python 3 job template * Drop lower-constraints.txt and its testing * Update master for stable/yoga 9.4.0 ----- * Update master for stable/xena * Add Python 3 only classifier * Update master for stable/wallaby * Update master for stable/victoria * Add oslo.config.opts entrypoint for audit middleware options * Remove references to 'sys.version\_info' 9.3.0 ----- * Imported Translations from Zanata * Switch to eventlet-safe oslo.cache's MemcacheClientPool * Updating lower-constraints job as non voting 9.2.0 ----- * [goal] Migrate testing to ubuntu focal 9.1.0 ----- * Imported Translations from Zanata * Change the default Identity endpoint to internal * Switch to newer openstackdocstheme and reno versions * Remove translation sections from setup.cfg * Use unittest.mock instead of third party mock * Update master for stable/ussuri 9.0.0 ----- * Update hacking for Python3 * Have middlewarearchitecture doc reference auth\_type option * Remove universal wheel configuration * [ussuri][goal] Drop python 2.7 support and testing * Imported Translations from Zanata 8.0.0 ----- * Rename \_v3\_to\_v2\_catalog to \_normalize\_catalog * Change ec2 URLs to v3 * Remove v2.0 functionality * Remove keystoneclient exception usage in tests * Fix DeprecationWarning: invalid escape sequence issues * Switch to Ussuri jobs * Generate pdf documentation * Update master for stable/train * Update the constraints url * Update invalid link for README * Make tests pass in 2022 * Fix misspell word 7.0.1 ----- * Comment html\_static\_path entry in docs conf.py * Bump the openstackdocstheme extension to 1.20 * Blacklist sphinx 2.1.0 (autodoc bug) 7.0.0 ----- * Add validation of app cred access rules * Add Python 3 Train unit tests * Remove Diablo compatibility tests * Fix bandit warning * Remove PKI/PKIZ support 6.1.0 ----- * Add a new option to choose the Identity endpoint * print auth version for request strategy in debug * Blacklist bandit 1.6.0 & cap sphinx for 2.7 * OpenDev Migration Patch * Bump memcached minimum version * Fix string format error * Update the min version of tox * Run lower-constraints on Bionic and update python-keystoneclient * Run lower-constraints job on Xenial * Update master for stable/stein * Drop py35 jobs * Fix debug tox environment 6.0.0 ----- * Fix service\_token\_role\_required option * add python 3.7 unit test job * trivial: fix convention in release note * Add auth invalidation in auth\_token for identity endpoint update * Remove testr.conf as it's been replaced by stestr * Make sure audit middleware use own context * Trivial: Update pypi url to new url * Change openstack-dev to openstack-discuss * Added request\_id and global\_request\_id to CADF notifications * Add py36 tox environment * Documentation Fix - auth\_url Port Number * Stop supporting revocation list * Fix audit target service selection * Skip the services with no endpoints when parsing service catalog 5.3.0 ----- * Respect delay\_auth\_decision when Keystone is unavailable * Use templates for cover and lower-constraints * Remove tox\_install.sh * No need to compare CONF content * add lib-forward-testing-python3 test job * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * add releasenotes to readme.rst * Handle DiscoveryFailure errors * Update reno for stable/rocky * Replace port 35357 with 5000 5.2.0 ----- * Fix KeystoneMiddleware memcachepool abstraction * Document endpoint interface and region behavior * fix tox python3 overrides * Follow the new PTI for document build * Switch coverage tox env to stestr * Fix the title in index.rst * Don't rely on pbr ChangeLog for docs 5.1.0 ----- * Introduce new header for system-scoped tokens * Imported Translations from Zanata * Fix the doc CI failure * Double quote www\_authenticate\_uri * Only include response body if there's a response * Properly zero out max\_retries in test\_http\_error\_not\_cached\_token 5.0.0 ----- * add lower-constraints job * Update links in README * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Update home-page url * Remove empty files * Fix the AttributeError: \_\_exit\_\_ error * Add arguments for MemcacheClientPool init * Remove kwargs\_to\_fetch\_token * Identify the keystone service when raising 503 * Add option to disable using oslo\_message notifier * Updated from global requirements * Imported Translations from Zanata * Update reno for stable/queens * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata 4.21.0 ------ * Imported Translations from Zanata 4.20.0 ------ * cfg.CONF must not be used directly * Fix docs builds * Log TokenNotFound at INFO level instead of WARNING * rel-note and doc for lazy loading of oslo\_cache * lazy loading of oslo\_cache * Expect paste.deploy and gnocchi/panko options 4.19.0 ------ * Updated from global requirements * Use oslo\_cache in auth\_token middleware * Remove setting of version/release from releasenotes * Updated from global requirements * Imported Translations from Zanata 4.18.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix py3 byte/string error * Rename auth\_uri to www\_authenticate\_uri * Updates for stestr * Issue a deprecation warning for validating PKI tokens * Fix gate error caused by mocked URLs * Correct docs usage of keystoneauth1 session * Update config docs to reflect non-deprecated methods * Add doc8 rule and check doc/source files * Updated from global requirements * Remove use of positional decorator * strip whitespace from token * Update reno for stable/pike * Remove notice about system time * Updated from global requirements * Updated from global requirements * Update comment about fetch token kwargs 4.17.0 ------ * Update URLs in documentation * Updated from global requirements * Enable sphinx todo extension * Replace six.iteritems() with .items() * Change locations of docs for intersphinx * Redundant adminURL in test\_gives\_v2\_catalog * Switch from oslosphinx to openstackdocstheme * Updated from global requirements * Using assertFalse(A) instead of assertEqual(False, A) * Removing double spaces * Updated from global requirements * Fix html\_last\_updated\_fmt for Python3 * add a log when the option in conf can't be identitied * Updated from global requirements * Updated from global requirements 4.16.0 ------ * Fix oslo.messaging deprecation of get\_transport * Updated from global requirements * Replace pycrypto with cryptography * Updated from global requirements * Update driver config parameter from string to list * Updated from global requirements * Remove log translations * Added "warning-is-error" sphinx check for docs * Updated from global requirements * Imported Translations from Zanata 4.15.0 ------ * Remove deprecated oslo.messaging aliases parameter * Pass located tests directory in oslo debug * Remove old comment referencing fixed bug * Bump the token deferral message from info to debug * Remove unused logging import * Updated from global requirements * Fixed man\_pages no value warning when making docs * Use https for \*.openstack.org references * Imported Translations from Zanata * Updated from global requirements * Update reno for stable/ocata 4.14.0 ------ * Updated from global requirements * fix broken links * use oslo.log instead of logging * Removes unnecessary utf-8 coding * Remove references to Python 3.4 * Switch tox unit test command to use ostestr 4.13.1 ------ * Add Constraints support * Auth token, set the correct charset 4.13.0 ------ * Limit deprecated token message to single warning * auth\_token: set correct charset when replying with 401 * Updated from global requirements 4.12.0 ------ * Pass ?allow\_expired * Updated from global requirements * clean up a few doc building warnings * Add docutils contraint on 0.13.1 to fix building * Updated from global requirements * Updated from global requirements * Updated from global requirements 4.11.0 ------ * Drop MANIFEST.in - it's not needed by pbr * Show team and repo badges on README * Updated from global requirements * Deprecate PKI token format options * Updated from global requirements * Mock log only after app creation * Updated from global requirements * Update .coveragerc after the removal of respective directory * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add service token to user token plugin * Specify that unknown arguments can be passed to fetch\_token * Enable release notes translation * Changed the home-page link 4.10.0 ------ * Return and use an app wherever possible * Refactor audit tests to use create\_middleware * Use oslo\_messaging conf fixture * Extract oslo\_messaging specific audit tests * Use the mocking fixture in notifier tests * Updated from global requirements * Use method constant\_time\_compare from oslo.utils * Raise NotImplementedError instead of NotImplemented * Updated from global requirements * Updated from global requirements * Update code to use Newton as the code name * standardize release note page ordering * Update reno for stable/newton * Globalize authentication failure error * Updated from global requirements 4.9.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 4.8.0 ----- * Updated from global requirements * Updated from global requirements * Fix description of option \`cache\` 4.7.0 ----- * Add Python 3.5 classifier * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use jsonutils instead of ast for loading the service catalog * Use AccessInfo in UserAuthPlugin instead of custom * Remove the \_is\_v2 and \_is\_v3 helpers * Remove oslo-incubator 4.6.0 ----- * Updated from global requirements * Use extras for oslo.messaging dependency * Refactor API tests to not run middleware * Refactor audit api tests into their own file * Refactor create\_event onto the api object * Extract a common notifier pattern * Break out the API piece into its own file * Use createfile fixture in audit test * Move audit into its own folder * use local config options if available in audit middleware * Use oslo.config fixture in audit tests * Pop oslo\_config\_config before doing paste convert * Updated from global requirements * Fix typo 'olso' to 'oslo' * Config: no need to set default=None * Fix an issue with oslo\_config\_project paste config * Updated from global requirements * Pass X\_IS\_ADMIN\_PROJECT header from auth\_token * Clean up middleware architecture * Updated from global requirements * Add a fixture method to add your own token data * Move auth token opts calculation into auth\_token * Make audit middleware use common config object * Consolidate user agent calculation * Create a Config object * Updated from global requirements * Updated from global requirements * Improve documentation for auth\_uri * PEP257: Ignore D203 because it was deprecated * Updated from global requirements * Use method split\_path from oslo.utils * Updated from global requirements * Make sure audit can handle API requests which does not require a token * Updated from global requirements * Updated from global requirements * Updated from global requirements * Determine project name from oslo\_config or local config 4.5.1 ----- * Fix AttributeError on cached-invalid token checks 4.5.0 ----- * Updated from global requirements * Updated from global requirements * Fix D105: Missing docstring in magic method (PEP257) * Fix D200: One-line docstring should fit on one line with quotes (PEP257) * Fix D202: No blank lines allowed after function docstring (PEP257) * Adding audit middleware specific notification driver conf * remove old options from documentation * generate sample config automatically * Return default value for pkg\_version if missing * Updated from global requirements * Fix D204 PEP257 violation and enable D301 and D209 * Fix D400 PEP257 violation * Fix D401 PEP257 violation and enable H403 * Update config options * s3token config with auth URI * Updated from global requirements * Return JSON for Unauthorized message * Updated from global requirements * Fix doc build if git is absent * PEP257: add flake8-docstring testing * Only confirm token binding on one token * Create signing\_dir upon first usage * Updated from global requirements * Updated from global requirements * Handle cache invalidate outside cache object * Update reno for stable/mitaka * Remove bandit.yaml in favor of defaults * use the same context across a request * Updated from global requirements * Update documentation for running tests * Updated from global requirements * Add back a bandit tox job 4.3.0 ----- * argparse expects a list not a dictionary * update deprecation message to indicate when deprecations were made * Updated from global requirements * Split oslo\_config and list all opts * Updated from global requirements * Make pep8 \*the\* linting interface * Remove clobbering of passed oslo\_config\_config * Updated from global requirements * Use positional instead of keystoneclient version * Updated from global requirements * Remove Babel from requirements.txt 4.2.0 ----- * Updated from global requirements * Deprecate in-process cache * Revert "Disable memory caching of tokens" * Revert "Don't cache signed tokens" * Updated from global requirements * Remove bandit tox environment * Remove unnecessary \_reject\_request function * Group common PKI validation code - Refactor * Group common PKI validation code - Tests * Remove except Exception handler * Fix tests to work with keystoneauth1 2.2.0 * Bandit profile updates * Replace deprecated library function os.popen() with subprocess 4.1.0 ----- * Add project\_name to the auth\_token fixture * Revert "Stop using private keystoneclient functions" * create release notes for ksm 4.1.0 * Don't cache signed tokens * Disable memory caching of tokens * Updated from global requirements * Use oslo\_config choices support * Stop using private keystoneclient functions * Use fixture for mock patch * auth\_token verify revocation by audit\_id * Updated from global requirements * Deprecated tox -downloadcache option removed * Updated from global requirements * Make BaseAuthProtocol public * Use load\_from\_options\_getter for auth plugins * Configuration is outdated * Updated from global requirements * Use keystoneauth for auth\_token fixture * Don't list deprecated opts in sample config * Updated from global requirements * Put py34 first in the env order of tox 4.0.0 ----- * Add release notes for keystonemiddleware * Updated from global requirements * Adding parse of protocol v4 of AWS auth to ec2\_token * Add a mock-fixture for keystonemiddleware auth\_protocol * Add domain and trust details to user plugin * Remove py26 target from tox.ini * Use keystoneauth * Updated from global requirements * Address hacking check H405 * update middlewarearchitecture.rst * Make "Auth Token confirmed use of %s apis" debug level * Define entry points for filter factories for Paste Deployment * Updated from global requirements * Updated from global requirements 3.0.0 ----- * Updated from global requirements * drop use of norm\_ns 2.4.1 ----- * Updated from global requirements * Straighten up exceptions imports * Separate setting catalog on headers from others 2.4.0 ----- * Updated from global requirements * Updated from global requirements * Remove auth headers in AuthProtocol * Use request helpers for token\_info/token\_auth * Make \_\_all\_\_ immutable * Move response status check to the call * only make token invalid when it really is * auto-generate release history * Add shields.io version/downloads links/badges into README.rst * Updated from global requirements * Change ignore-errors to ignore\_errors * Ensure auth\_plugin options are in generated CONF * Cleanup a few auth\_token comments 2.3.0 ----- * Updated from global requirements * Remove unused group parameter from tests * auth\_token tests use clean config * Docstring updates * Use ConnectionRefused for auth\_token tests 2.2.0 ----- * Seperate standalone cache tests * Import \_memcache\_pool normally * Create Environment cache pool * Handle memcache pool arguments collectively * Updated from global requirements * Allow specifying a region name to auth\_token * Updated from global requirements * Allow to use oslo.config without global CONF * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Move common request processing to base class * Fix rst * py34 not py33 is tested and supported * Refactor extract method for offline validation * Send the correct user-agent to Keystone * Move enforcement and time validation to base class * Separate the fetch and validate parts of auth\_token * Fixes modules index generated by Sphinx 2.1.0 ----- * Add token\_auth helper to request * Add user\_token and service\_token to request * Create a simple base class from AuthProtocol * Switch from deprecated oslo\_utils.timeutils.strtime * Updated from global requirements * Refactor \_confirm\_token\_bind takes AccessInfo * Make token bind work with a request * Rename \_LOG to log in auth\_token middleware * Don't allow webob to set a default content type * Prevent a UnicodeDecodeError in the s3token middleware * Remove install\_venv\_common and fix typo in memorycache 2.0.0 ----- * Ensure cache keys are a known/fixed length * Updated from global requirements * Refactor request methods onto request object * validate\_token returns AccessInfo * Updated from global requirements * Fixes a spelling error in a test name * Remove custom header handling * Unit tests catch deprecated function usage * Common base class for unit tests * Stop using function deprecated in py34 * Move bandit requirement to test-requirements.txt * Fetch user token from request rather than env * Remove the \_msg\_format function * Base use webob * Don't rely on token\_info for header building * Move project included validation * Depend on keystoneclient for expiration checking * Don't store expire into memcache * Removes discover from test-reqs * Drop py2.6 support for keystone middleware * Create new user plugin tests * Add an explicit test failure condition when auth\_token is missing * Fixup test-requirements-py3.txt * Fix list\_opts test to not check all deps * Refactor certificate fetch functions * tox env for Bandit * Cleanup token hashes generated by cache * Updated from global requirements * Improved handling of endpoints missing urls * Refactor: extract echo\_app from enclosing class * Add keystone v3 API to fetch revocation list * Simplify request making in auth\_token tests * Change auth\_token to use keystoneclient * Deprecate auth\_token authentication * Updated from global requirements 1.6.1 ----- * Ignore cover directory * Remove superfluous / spammy log line * Drop use of 'oslo' namespace package * Port keystonemiddleware to Python 3 * Remove unused iso8601 dependency * Update README to work with release tools 1.6.0 ----- * Uncap library requirements for liberty * Remove retry parameter * Fix s3\_token middleware parsing insecure option * Updated from global requirements * Pull echo service out of auth\_token * Fix typos in keystonemiddleware * Rename requests mock object in testing * Update auth\_token config docs * Crosslink to other sites that are owned by Keystone * Move \_memcache\_pool into auth\_token * Move unit tests into tests.unit 1.5.0 ----- * Allow loading auth plugins via overrides * Updated from global requirements * Delay denial when service token is invalid * Updated from global requirements * Move UserAuthPlugin into its own file * Extract IdentityServer into file * Extract all TokenCache related classes to file * Break default auth plugin into file * Extract revocations to file * Extract SigningDirectory into file * Separate exceptions into their own file * Updated from global requirements * Updated from global requirements * Move auth\_token into its own folder * Updated from global requirements 1.4.0 ----- * Refactor auth\_token revocation list members to new class * Refactor extract class for signing directory * Turn our auth plugin into a token interface * iso expires should be returned in one place * move add event creation logic to keystonemiddleware * Updated from global requirements * Sync with oslo-incubator * Use oslo.context instead of incubator code * Refactor auth\_uri handling * make audit event scoped to request session and not middleware * Updated from global requirements * Remove custom string truth handling * Updated from global requirements * incorrect reference in enabling audit middleware * Updated from global requirements * Enforce check F821 and H304 * Switch from oslo.config to oslo\_config * Switch from oslo.serialization to oslo\_serialization * Switch from oslo.utils to oslo\_utils * Add python-memcached to test-requirements * Correct failures for check E122 * Correct failures for check H703 * Updated from global requirements * Correct failures for check H238 * Move to hacking 0.10 * Updated from global requirements * Use a test fixture for mocking time * Fix environ keys missing HTTP\_ prefix * support micro version if sent * Fix passing parameters to log message * Correct incorrect rst in docstrings * remove unused variable in \_IdentityServer 1.3.1 ----- * Fix auth\_token does version request for no token * Adds Memcached dependencies doc * fallback to online validation if offline validation fails 1.3.0 ----- * documentation for audit middleware * remove the unused method \_will\_expire\_soon * Updated from global requirements * Use newer requests-mock syntax * Allow loading other auth methods in auth\_token * Auth token tests create temp cert directory * Add a test to ensure the version check error * Split identity server into v2 and v3 * Workflow documentation is now in infra-manual * Use real discovery object in auth\_token middleware * Updated from global requirements * Make everything in audit middleware private * Updated from global requirements * Adding audit middleware to keystonemiddleware * Fix paste config option conversion for auth options * Auth token supports deprecated names for paste conf options * Correct tests to use strings in conf * Change occurrences of keystone to identity server * Updated from global requirements * Updated from global requirements * Updated from global requirements * I18n * Adds space after # in comments * Update python-keystoneclient reference * Use Discovery fixtures for auth token tests * Convert authentication into a plugin * Add versions to requests * Use an adapter in IdentityServer * Use connection retrying from keystoneclient * Updated from global requirements * Use correct name of oslo debugger script * Use new ksc features in User Token Plugin * Remove netaddr package requirement * add context to keystonemiddleware * Updated from global requirements * Improve help strings * Updated from global requirements * Changing the value type of http\_connect\_timeout * Revert "Support service user and project in non-default domain" * Replace httpretty with requests-mock * Encode middleware error message as bytes * Docstring cleanup * Remove HTTP\_X\_STORAGE\_TOKEN doc * Fix reference to middleware architecture doc * Clean up the middleware docs * Update oslo-incubator and switch to oslo.{utils,serialization} * Refactor auth\_token cache 1.2.0 ----- * Add an optional advanced pool of memcached clients * Fix auth\_token for old oslo.config * Support service user and project in non-default domain * Add composite authentication support * Fix test failure after discovery hack * Updated from global requirements * BaseAuthTokenMiddlewareTest.setUp call super normally * Remove unused iso8601 * Use oslo\_debug\_helper and remove our own version * convert the conf value into correct type * Always add auth URI to unauthorized requests * Work toward Python 3.4 support and testing * warn against sorting requirements * Always supply a username to auth\_token tests setup * Create an Auth Plugin to pass to users * Updated from global requirements 1.1.1 ----- * Hash for PKIZ * auth\_token cached token handling * Add a test for re-caching a token * Updated from global requirements * Remove intersphinx mappings * Use oslosphinx in keystonemiddlware for documentation * Updated from global requirements * Convert auth\_token middleware to use sessions 1.1.0 ----- * Updated from global requirements * Remove mox dependency * move webob from test-requirements to requirements * remove unused dep: stevedore * remove unused dep: prettytable * Example JSON files should be human-readable * Updated from global requirements * Mark keystonemiddleware as being a universal wheel * Use keystoneclient fixtures in middleware tests * prefer identity API v3 over v2 in auth\_token * Clean up openstack-common.conf * Sync with oslo-incubator 569979adf * Refactor auth\_token, move identity server members to class 1.0.0 ----- * Expose an entry point to list auth\_token middleware config options * Privatize Everything * Privatize Everything * add CONTRIBUTING.rst * add README * Update setup.cfg to remove keystoneclient ref * Bring over debug\_helper.sh * Update requirement files * Update .gitignore files * Correct Doc location and update for middleware only * Move Docs to the right location * Remove .update-venv * Update middleware and tests for new package * Update requirements * Update MANIFEST.in * Remove unused testing files from keystoneclient * Move examples split to new location * Move ec2\_token to new location * Add in original keystoneclient test-requirements.txt * Initial oslo-incubator sync * Cleanup unused testr.conf file * Move tests to new location * Moving middleware to new location * Initial commit * Fix 500 error if request body is not JSON object * auth\_token \_cache\_get checks token expired * auth\_token \_cache\_get checks token expired * Using six.u('') instead of u'' * Session Documentation * Link to docstrings in using-api-v3 * Refactor auth\_token token cache members to class * Refactor auth\_token token cache members to class * Add service\_name to URL discovery * Don't use mock non-exist method assert\_called\_once * Remove \_factory methods from auth plugins * Make get\_oauth\_params conditional for specific oauthlib versions * Changes exception raised by v3.trusts.update() * Add role assignments as concept in Client API V3 docs * Fix tests to use UUID strings rather than ints for IDs * Clean up oauth auth plugin code * Add endpoint handling to Token/Endpoint auth * Add support for extensions-list * auth\_token middleware hashes tokens with configurable algorithm * auth\_token middleware hashes tokens with configurable algorithm * Remove left over vim headers * Add /role\_assignments endpoint support * Authenticate via oauth * Auth Plugin invalidation * Move DisableModuleFixture to utils * replace string format arguments with function parameters * Fixes an erroneous type check in a test * auth\_token hashes PKI token once * auth\_token hashes PKI token once * Compressed Signature and Validation * Compressed Signature and Validation * Compressed Signature and Validation * OAuth request/access token and consumer support for oauth client API * Regions Management * Discovery URL querying functions * Move auth\_token tests not requiring v2/v3 to new class * Cached tokens aren't expired * Cached tokens aren't expired * Move auth\_token cache pool tests out of NoMemcache * Fixed the size limit tests in Python 3 * Make auth\_token return a V2 Catalog * Make auth\_token return a V2 Catalog * Fix client fixtures * fixed typos found by RETF rules * fixed typos found by RETF rules * auth\_token configurable check of revocations for cached * auth\_token configurable check of revocations for cached * Remove unused AdjustedBaseAuthTokenMiddlewareTest * auth\_token test remove unused fake\_app parameter * Fix typo in BaseAuthTokenMiddlewareTest * Enhance tests for auth\_token middleware * Limited use trusts * Debug log when token found in revocation list * Ensure that cached token is not revoked * Fix the catalog format of a sample token * remove universal\_newlines * replace double quotes with single * Deprecate admin\_token option in auth\_token * Create a V3 Token Generator * Implement endpoint filtering functionality on the client side * Fix typo of ANS1 to ASN1 * Fix typo of ANS1 to ASN1 * Add new error for invalid response * Rename HTTPError -> HttpError * Add CRUD operations for Federation Mapping Rules * Don't use generic kwargs in v2 Token Generation * Update docs for auth\_token middleware config options * Allow session to return an error response object * Add service name to catalog * Hash functions support different hash algorithms * Add CRUD operations for Identity Providers * eliminate race condition fetching certs * eliminate race condition fetching certs * Allow passing auth plugin as a parameter * Prefer () to continue line per PEP8 * Prefer () to continue line per PEP8 * Use \`HttpNotImplemented\` in \`tests.v3.test\_trusts\` * Ensure JSON headers in Auth Requests * Create a test token generator and use it * Safer noqa handling * Rename request\_uri to identity\_uri * Tests should use identity\_uri by default * Replace auth fragements with identity\_uri * Replace auth fragements with identity\_uri * Remove releases.rst from keystone docs * Handle URLs via the session and auth\_plugins * Add a method for changing a user's password in v3 * sanity check memcached availability before running tests against it * Change the default version discovery URLs * add functional test for cache pool * Add a positional decorator * add pooling for cache references * add pooling for cache references * use v3 api to get certificates * use v3 api to get certificates * Don't use a connection pool unless provided * Reference docstring for auth\_token fields * Docs link to middlewarearchitecture * Uses explicit imports for \_ * Discover should support other services * Replace httplib.HTTPSConnection in ec2\_token * Revert "Add request/access token and consumer..." * Revert "Authenticate via oauth" * Fix doc build errors * Fix doc build errors * Fix doc build errors * Generate module docs * Authenticate via oauth * Add request/access token and consumer support for keystoneclient * Add 'methods' to all v3 test tokens * Use AccessInfo in auth\_token middleware * Add 'methods' to all v3 test tokens * Handle Token/Endpoint authentication * Split sample PKI token generation * Fix retry logic * Fix state modifying catalog tests * Remove reference to non-existent shell doc * increase default revocation\_cache\_time * Make keystoneclient not log auth tokens * improve configuration help text in auth\_token * Log the command output on CertificateConfigError * V3 xml responses should use v3 namespace * Enforce scope mutual exclusion for trusts * Token Revocation Extension * Atomic write of certificate files and revocation list * Privatize auth construction parameters * Set the right permissions for signing\_dir in tests * deprecate XML support in favor of JSON * Capitalize Client API title consistently * Remove http\_handler config option in auth\_token * Rely on OSLO.config * Use admin\_prefix consistently * demonstrate auth\_token behavior with a simple echo service * Remove redundant default value None for dict.get * Remove redundant default value None for dict.get * correct typo of config option name in error message * remove extra indentation * refer to non-deprecated config option in help * Create V3 Auth Plugins * Create V2 Auth Plugins * Fix role\_names call from V3 AccessInfo * Interactive prompt for create user * Replace assertEqual(None, \*) with assertIsNone in tests * Ensure domains.list filtered results are correct * Test query-string for list actions with filter arguments * Fix keystone command man page * Add link to the v3 client api doc * Fix references to auth\_token in middlewarearchitecture doc * Use WebOb directly in ec2\_token middleware * Don't use private last\_request variable * Python: Pass bytes to derive\_keys() * Use WebOb directly for locale testing * Make sure to unset all variable starting with OS\_ * Python3: use six.moves.urllib.parse.quote instead of urllib.quote * Remove vim header * Remove vim header * Remove vim header * Python3: httpretty.last\_request().body is now bytes * Python3: fix test\_insecure * Deprecate s3\_token middleware * Python3: webob.Response.body must be bytes * Python 3: call functions from memcache\_crypt.py with bytes as input * Python 3: call functions from memcache\_crypt.py with bytes as input * Use requests library in S3 middleware * Use requests library in S3 middleware * Python 3: make tests from v2\_0/test\_access.py pass * Python 3: make tests from v2\_0/test\_access.py pass * Create Authentication Plugins * Fix debug curl commands for included data * Add back --insecure option to CURL debug * Use HTTPretty in S3 test code * Provide a conversion function for creating session * Update reference to middlewarearchitecture doc * Update middlewarearchitecture config options docs * Remove support for old Swift memcache interface * Remove support for old Swift memcache interface * Replace urllib/urlparse with six.moves.\* * Python 3: fix tests/test\_utils.py * Python 3: Fix an str vs bytes issue in tempfile * Return role names by AccessInfo.role\_names * Copy s3\_token middleware from keystone * Copy s3\_token middleware from keystone * build auth context from middleware * Fix E12x warnings found by Pep8 1.4.6 * Fix typos in documents and comments * Fix typos in documents and comments * Consistently support kwargs across all v3 CRUD Manager ops * Use six to make dict work in Python 2 and Python 3 * Python 3: set webob.Response().body to a bytes value * Remove test\_print\_{dict,list}\_unicode\_without\_encode * Tests use cleanUp rather than tearDown * Adjust import items according to hacking import rule * Adjust import items according to hacking import rule * Adjust import items according to hacking import rule * Replace assertTrue with explicit assertIsInstance * Fix discover command failed to read extension list issue * Fix incorrect assertTrue usage * Make assertQueryStringIs usage simpler * auth\_token tests use assertIs/Not/None * Make common log import consistent * Python 3: Use HTTPMessage.get() rather than HTTPMessage.getheader() * auth\_token tests close temp file descriptor * Tests cleanup temporary files * Removes use of timeutils.set\_time\_override * Controllable redirect handling * Verify token binding in auth\_token middleware * Verify token binding in auth\_token middleware * Fix auth\_token middleware test invalid cross-device link issue * Add unit tests for generic/shell.py * Rename using-api.rst to using-api-v2.rst * Documents keystone v3 API usage - part 1 * v3 test utils, don't modify input parameter * Fix error in v3 credentials create/update * Rename instead of writing directly to revoked file * Correctly handle auth\_url/token authentication * Remove debug specific handling * Fix missed management\_url setter in v3 client * Add service catalog to domain scoped token fixture * Change assertEquals to assertIsNone * Avoid meaningless comparison that leads to a TypeError * Python3: replace urllib by six.moves.urllib * Fix --debug handling in the shell * Rename tokenauth to authtoken in the doc * use six.StringIO for compatibility with io.StringIO in python3 * Properly handle Regions in keystoneclient * Use testresources for example files * Discover supported APIs * Warn user about unsupported API version * Add workaround for OSError raised by Popen.communicate() * Use assertIn where appropriate * Extract a base Session object * Do not format messages before they are logged * keystoneclient requires an email address when creating a user * Fix typo in keystoneclient * Encode the text before print it to console * Opt-out of service catalog * Opt-out of service catalog * Opt-out of service catalog * Remove deprecated auth\_token middleware * "publicurl" should be required on endpoint-create * Update the management url for every fetched token * Fix python3 incompatible use of urlparse * Convert revocation list file last modified to UTC * Convert revocation list file last modified to UTC * Migrate the keystone.common.cms to keystoneclient * Migrate the keystone.common.cms to keystoneclient * Avoid returning stale token via auth\_token property * Remove SERVICE\_TOKEN and SERVICE\_ENDPOINT env vars * Make ROOTDIR determination more robust * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Replace OpenStack LLC with OpenStack Foundation * Add AssertRequestHeaderEqual test helper and make use of it * python3: Make iteritems py3k compat * Normalize datetimes to account for tz * Normalize datetimes to account for tz * assertEquals is deprecated, use assertEqual (H602) * remove the nova dependency in the ec2\_token middleware * Fix H202 assertRaises Exception * Fix H202 assertRaises Exception * Refactor for testability of an upcoming change * Refactor for testability of an upcoming change * Allow v2 client authentication with trust\_id * Fix misused assertTrue in unit tests * Add auth\_uri in conf to avoid unnecessary warning * Move tests in keystoneclient * Set example timestamps to 2038-01-18T21:14:07Z * Replace HttpConnection in auth\_token with Requests * Replace HttpConnection in auth\_token with Requests * Support client generate literal ipv6 auth\_uri base on auth\_host * Log user info in auth\_token middleware * Changed header from LLC to Foundation based on trademark policies * python3: Use from future import unicode\_literals * Fix and enable gating on F841 * Use OSLO jsonutils instead of json module * Allow configure the number of http retries * Use hashed token for invalid PKI token cache key * Make auth\_token middleware fetching respect prefix * Move all opens in auth\_token to be in context * Refactor Keystone to use unified logging from Oslo * Refactor verify signing dir logic * Fixes files with wrong bitmode * Don't cache tokens as invalid on network errors * Fix a typo in fetch\_revocation\_list * auth\_uri (public ep) should not default to auth\_\* values (admin ep) * Adds help in keystone\_authtoken config opts * python3: Add basic compatibility support * remove swift dependency of s3 middleware * flake8: fix alphabetical imports and enable H306 * Drop webob from auth\_token.py * no logging on cms failure * rm improper assert syntax * Fix and enable gating on H402 * Raise key length defaults * Fix auth\_token.py bad signing\_dir log message * Fix and enable H401 * Revert environment module usage in middleware * Fix the cache interface to use time= by default * Change memcache config entry name in Keystone to be consistent with Oslo * Change memcache config entry name in Keystone to be consistent with Oslo * Fix memcache encryption middleware * Fix memcache encryption middleware * Isolate eventlet code into environment * Provide keystone CLI man page * Check Expiry * Check Expiry * import only modules (flake8 H302) * Satisfy flake8 import rules F401 and F403 * Default signing\_dir to secure temp dir (bug 1181157) * Use testr instead of nose * Securely create signing\_dir (bug 1174608) * adding notes about dealing with exceptions in the client * Fix v3 with UUID and memcache expiring * Fix v3 with UUID and memcache expiring * Allow keystoneclient to work with older keystone installs * Wrap config module and require manual setup (bug 1143998) * Config value for revocation list timeout * Cache tokens using memorycache from oslo * Cache tokens using memorycache from oslo * xml\_body returns backtrace on XMLSyntaxError * Make auth\_token lazy load the auth\_version * Doc info and other readability improvements * Retry http\_request and json\_request failure * Use v2.0 api by default in auth\_token middleware * Fix auth-token middleware to understand v3 tokens * Fix auth-token middleware to understand v3 tokens * Remove test dep on name of dir (bug 1124283) * bug 1131840: fix auth and token data for XML translation * Rework S3Token middleware tests * v3 token API * Use oslo-config-2013.1b3 * Allow configure auth\_token http connect timeout * Allow configure auth\_token http connect timeout * Fix spelling mistakes * Mark password config options with secret * Fixes 'not in' operator usage * Fix thinko in self.middleware.cert\_file\_missing * Limit the size of HTTP requests * Blueprint memcache-protection: enable memcache value encryption/integrity check * Blueprint memcache-protection: enable memcache value encryption/integrity check * Warning message is not logged for valid token-less request * Use os.path to find ~/keystone-signing (bug 1078947) * Remove iso8601 dep in favor of openstack.common * remove unused import * Bug 1052674: added support for Swift cache * URL-encode user-supplied tokens (bug 974319) * Fix middleware logging for swift * Remove swift auth * Don't try to split a list of memcache servers * Import auth\_token middleware from keystoneclient * Throw validation response into the environment * Add auth-token code to keystoneclient, along with supporting files * Add auth-token code to keystoneclient, along with supporting files * Use the right subprocess based on os monkeypatch * Make initial structural changes to keystoneclient in preparation to moving auth\_token here from keystone. No functional change should occur from this commit (even though it did refresh a newer copy of openstack.common.setup.py, none of the newer updates are in functions called from this client) * fixes bug 1074172 * HACKING compliance: consistent use of 'except' * auth\_token hash pki key PKI tokens on hash in memcached when accessed by auth\_token middelware * Move 'opentack.context' and 'openstack.params' definitions to keystone.common.wsgi * Replace refs to 'Keystone API' with 'Identity API' * replacing PKI token detection from content length to content prefix. (bug 1060389) * updating base keystoneclient documentation * updating keystoneclient doc theme * Backslash continuation cleanup * Check for expected cfg impl (bug 1043479) * Fix PEP8 issues * Fix auth\_token middleware to fetch revocation list as admin * allow middleware configuration from app config * Change underscores in new cert options to dashes * PKI Token revocation * Use user home dir as default for cache * Set default signing\_dir based on os USER * Test for Cert by name * Cryptographically Signed tokens * Prevent service catalog injection in auth\_token * Admin Auth URI prefix * Support 2-way SSL with Keystone server if it is configured to enforce 2-way SSL. See also https://review.openstack.org/#/c/7706/ for the corresponding review for the 2-way SSL addition to Keystone * Change CLI options to use dashes * Keystone should use openstack.common.jsonutils * Removed unused import * Reorder imports by full module path * Pass serviceCatalog in auth\_token middleware * 400 on unrecognized content type (bug 1012282) * PEP8 fixes * Move docs to doc * fix importing of optional modules in auth\_token * blueprint 2-way-ssl * Fixes some pep8 warning/errors * Update swift\_auth documentation * Add ACL check using : format * Use X\_USER\_NAME and X\_ROLES headers * Allow other middleware overriding authentication * Backslash continuation removal (Keystone folsom-1) * Added 'NormalizingFilter' middleware * Make sure we parse delay\_auth\_decision as boolean * Exit on error in a S3 way * Add a \_ at the end of reseller\_prefix default * additional logging to support debugging auth issue * Add support to swift\_auth for tokenless authz * Improve swift\_auth test coverage + Minor fixes * S3 tokens cleanups * updating docs to include creating service accts * Rename tokenauth to authtoken * Remove nova-specific middlewares * Remove glance\_auth\_token middleware * Update username -> name in token response * Refactor keystone.common.logging use (bug 948224) * Allow connect to another tenant * Improved legacy tenancy resolution (bug 951933) * Fix iso8601 import/use and date comparaison * Add simple set of tests for auth\_token middleware * Add token caching via memcache * Added license header (bug 929663) * Make sure we have a port number before int it * HTTP\_AUTHORIZATION was used in proxy mode * Add reseller admin capability * improve auth\_token middleware * Unpythonic code in redux in auth\_token.py * Handle KeyError in \_get\_admin\_auth\_token * Provide request to Middleware.process\_response() * Set tenantName to 'admin' in get\_admin\_auth\_token * XML de/serialization (bug 928058) * Update auth\_token middleware so it sets X\_USER\_ID * Fix case of admin role in middleware * Remove extraneous \_validate\_claims() arg * Fix copyright dates and remove duplicate Apache licenses * Re-adds admin\_pass/user to auth\_tok middleware * Update docs for Swift and S3 middlewares * Added Apache 2.0 License information * Update swift token middleware * Add s3\_token * Fixes role checking for admin check * Add tests for core middleware * termie all the things * be more safe with getting json aprams * fix keystoneclient tests * pep8 cleanup * doc updates * fix middleware * update some names * fix some imports * re-indent * check for membership * add more middleware * woops * add legacy middleware ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/HACKING.rst0000664000175000017500000000121400000000000017317 0ustar00zuulzuul00000000000000Keystone Style Commandments =========================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Exceptions ---------- When dealing with exceptions from underlying libraries, translate those exceptions to an instance or subclass of ClientException. ======= Testing ======= Keystone Middleware uses testtools and testr for its unittest suite and its test runner. Basic workflow around our use of tox and testr can be found at https://wiki.openstack.org/testr. If you'd like to learn more in depth: https://testtools.readthedocs.org/ https://testrepository.readthedocs.org/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/LICENSE0000664000175000017500000002717400000000000016543 0ustar00zuulzuul00000000000000Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1) Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1) Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7) All rights reserved. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. --- License for python-keystoneclient versions prior to 2.1 --- All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0287519 keystonemiddleware-10.9.0/PKG-INFO0000644000175000017500000000741400000000000016624 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: keystonemiddleware Version: 10.9.0 Summary: Middleware for OpenStack Identity Home-page: https://docs.openstack.org/keystonemiddleware/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: keystoneauth1>=3.12.0 Requires-Dist: oslo.cache>=1.26.0 Requires-Dist: oslo.config>=5.2.0 Requires-Dist: oslo.context>=2.19.2 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.log>=3.36.0 Requires-Dist: oslo.serialization>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: pbr>=2.0.0 Requires-Dist: pycadf>=1.1.0 Requires-Dist: PyJWT>=2.4.0 Requires-Dist: python-keystoneclient>=3.20.0 Requires-Dist: requests>=2.14.2 Requires-Dist: WebOb>=1.7.1 Provides-Extra: audit-notifications Requires-Dist: oslo.messaging>=5.29.0; extra == "audit-notifications" Provides-Extra: test Requires-Dist: hacking~=6.1.0; extra == "test" Requires-Dist: flake8-docstrings~=1.7.0; extra == "test" Requires-Dist: coverage>=4.0; extra == "test" Requires-Dist: cryptography>=3.0; extra == "test" Requires-Dist: fixtures>=3.0.0; extra == "test" Requires-Dist: oslotest>=3.2.0; extra == "test" Requires-Dist: stevedore>=1.20.0; extra == "test" Requires-Dist: requests-mock>=1.2.0; extra == "test" Requires-Dist: stestr>=2.0.0; extra == "test" Requires-Dist: testresources>=2.0.0; extra == "test" Requires-Dist: testtools>=2.2.0; extra == "test" Requires-Dist: python-binary-memcached>=0.29.0; extra == "test" Requires-Dist: python-memcached>=1.59; extra == "test" Requires-Dist: WebTest>=2.0.27; extra == "test" Requires-Dist: oslo.messaging>=5.29.0; extra == "test" Requires-Dist: PyJWT>=2.4.0; extra == "test" Requires-Dist: bandit>=1.1.0; extra == "test" ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/keystonemiddleware.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://opendev.org/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware * Release notes: https://docs.openstack.org/releasenotes/keystonemiddleware/ For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/README.rst0000664000175000017500000000264300000000000017217 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/keystonemiddleware.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://opendev.org/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware * Release notes: https://docs.openstack.org/releasenotes/keystonemiddleware/ For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.988751 keystonemiddleware-10.9.0/config-generator/0000775000175000017500000000000000000000000020754 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/config-generator/keystonemiddleware.conf0000664000175000017500000000015300000000000025521 0ustar00zuulzuul00000000000000[DEFAULT] output_file = etc/keystone.conf.sample wrap_width = 79 namespace = keystonemiddleware.auth_token ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.988751 keystonemiddleware-10.9.0/doc/0000775000175000017500000000000000000000000016270 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/.gitignore0000664000175000017500000000002300000000000020253 0ustar00zuulzuul00000000000000build/ source/api/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/Makefile0000664000175000017500000000616200000000000017735 0ustar00zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/keystonemiddleware.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystonemiddleware.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.988751 keystonemiddleware-10.9.0/doc/ext/0000775000175000017500000000000000000000000017070 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/ext/__init__.py0000664000175000017500000000000000000000000021167 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/ext/apidoc.py0000664000175000017500000000310100000000000020674 0ustar00zuulzuul00000000000000# Copyright 2014 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. # NOTE(blk-u): Uncomment the [pbr] section in setup.cfg and remove this # Sphinx extension when https://launchpad.net/bugs/1260495 is fixed. import os.path as path from sphinx import apidoc # NOTE(blk-u): pbr will run Sphinx multiple times when it generates # documentation. Once for each builder. To run this extension we use the # 'builder-inited' hook that fires at the beginning of a Sphinx build. # We use ``run_already`` to make sure apidocs are only generated once # even if Sphinx is run multiple times. run_already = False def run_apidoc(app): global run_already if run_already: return run_already = True package_dir = path.abspath(path.join(app.srcdir, '..', '..', 'keystonemiddleware')) source_dir = path.join(app.srcdir, 'api') apidoc.main(['apidoc', package_dir, '-f', '-H', 'keystonemiddleware Modules', '-o', source_dir]) def setup(app): app.connect('builder-inited', run_apidoc) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/requirements.txt0000664000175000017500000000120200000000000021547 0ustar00zuulzuul00000000000000# For generating sphinx documentation doc8>=0.6.0 # Apache-2.0 openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD # PDF Docs sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD # For autodoc builds oslotest>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD python-binary-memcached>=0.29.0 # MIT python-memcached>=1.56 # PSF WebTest>=2.0.27 # MIT oslo.messaging>=5.29.0 # Apache-2.0 pycadf>=1.1.0 # Apache-2.0 PyJWT>=2.4.0 # MIT keystoneauth1>=3.12.0 # Apache-2.0 oslo.cache>=1.26.0 # Apache-2.0 python-keystoneclient>=3.20.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.988751 keystonemiddleware-10.9.0/doc/source/0000775000175000017500000000000000000000000017570 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/audit.rst0000664000175000017500000000761500000000000021441 0ustar00zuulzuul00000000000000.. 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. .. _middleware: ================ Audit middleware ================ The Keystone middleware library provides an optional WSGI middleware filter which allows the ability to audit API requests for each component of OpenStack. The audit middleware filter utilises environment variables to build the CADF event. .. figure:: ./images/audit.png :width: 100% :align: center :alt: Figure 1: Audit middleware in Nova pipeline The figure above shows the middleware in Nova's pipeline. Enabling audit middleware ========================= To enable auditing, oslo.messaging_ should be installed. If not, the middleware will log the audit event instead. Auditing can be enabled for a specific project by editing the project's api-paste.ini file to include the following filter definition: :: [filter:audit] paste.filter_factory = keystonemiddleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf The filter should be included after Keystone middleware's auth_token middleware so it can utilise environment variables set by auth_token. Below is an example using Nova's WSGI pipeline:: [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = faultwrap sizelimit authtoken keystonecontext ratelimit audit osapi_compute_app_v2 keystone_nolimit = faultwrap sizelimit authtoken keystonecontext audit osapi_compute_app_v2 .. _oslo.messaging: http://www.github.com/openstack/oslo.messaging Configure audit middleware ========================== To properly audit api requests, the audit middleware requires an api_audit_map.conf to be defined. The project's corresponding api_audit_map.conf file is included in the `pyCADF library`_. The location of the mapping file should be specified explicitly by adding the path to the 'audit_map_file' option of the filter definition:: [filter:audit] paste.filter_factory = keystonemiddleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf Additional options can be set:: [filter:audit] paste.filter_factory = pycadf.middleware.audit:filter_factory audit_map_file = /etc/nova/api_audit_map.conf service_name = test # opt to set HTTP_X_SERVICE_NAME environ variable ignore_req_list = GET,POST # opt to ignore specific requests Audit middleware can be configured to use its own exclusive notification driver and topic(s) value. This can be useful when the service is already using oslo messaging notifications and wants to use a different driver for auditing e.g. service has existing notifications sent to queue via 'messagingv2' and wants to send audit notifications to a log file via 'log' driver. Example shown below:: [audit_middleware_notifications] driver = log When audit events are sent via 'messagingv2' or 'messaging', middleware can specify a transport URL if its transport URL needs to be different from the service's own messaging transport setting. Other Transport related settings are read from oslo messaging sections defined in service configuration e.g. 'oslo_messaging_rabbit'. Example shown below:: [audit_middleware_notifications] driver = messaging transport_url = rabbit://user2:passwd@host:5672/another_virtual_host .. _pyCADF library: https://github.com/openstack/pycadf/tree/master/etc/pycadf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/conf.py0000664000175000017500000001714400000000000021076 0ustar00zuulzuul00000000000000# keystonemiddleware documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import pbr.version sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # NOTE(blk-u): Path for our Sphinx extension, remove when # https://launchpad.net/bugs/1260495 is fixed. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'openstackdocstheme', 'oslo_config.sphinxconfiggen', 'sphinxcontrib.apidoc', 'sphinxcontrib.rsvgconverter', ] # sphinxcontrib.apidoc options apidoc_module_dir = '../../keystonemiddleware' apidoc_output_dir = 'api' apidoc_excluded_paths = [ 'tests/*', 'tests', 'test'] apidoc_separate_modules = True config_generator_config_file = '../../config-generator/keystonemiddleware.conf' sample_config_basename = '_static/keystonemiddleware' todo_include_todos = True # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. copyright = 'OpenStack Contributors' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['keystonemiddleware.'] # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' #man_pages = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. #html_theme_path = ["."] #html_theme = '_theme' html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " 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'] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'keystonemiddlewaredoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ ('index', 'doc-keystonemiddleware.tex', u'keystonemiddleware Documentation', u'Openstack Developers', 'manual'), ] # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'extraclassoptions': 'openany', 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', 'maxlistdepth': 10, } # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True keystoneclient = 'https://docs.openstack.org/python-keystoneclient/latest/' intersphinx_mapping = {'keystoneclient': (keystoneclient, None), } # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/keystonemiddleware' openstackdocs_bug_project = 'keystonemiddleware' openstackdocs_bug_tag = '' openstackdocs_pdf_link = True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9927511 keystonemiddleware-10.9.0/doc/source/images/0000775000175000017500000000000000000000000021035 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/images/audit.png0000664000175000017500000013714600000000000022665 0ustar00zuulzuul00000000000000PNG  IHDRz6csRGBgAMA a pHYsodIDATx^ ]uVץ*}]]iKn:D6Nb:$81+ 16؀mB @(B$z=8z $a,3z~ծ5kZk9^{ PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgK7>ARnm6ɖF^n]b:AR2k׮ŋ[&LC%u'ZY#z4(Žy%$'s7.[7LZ H7ԇdK|b:ARH;Ȟx XO9)uۧMU fMCbjիb2L1oM=TdiW4JPF] f")lI"̧ k ԅybLIulHB#drZaWNDxH.fIEV7*oZrʢ<SqZ!Ea%dS8'[ik3fV,W<:iuՊϙHʅ$'‰b>P8V + GYlEaj A#%ްl;{9t6 4! ln0V&65)nz¬mú6 0Ä9DaVoAa _bLZ־NVLي")jb4H@O!3/`s11Ix!0 ؒYI\fIT8hm9}62,.1S$1 p#VV oV'M0[Y2ts&H)nxД>`-!,ppU }M'CÊ-V Oغ9'Y-zb:bEOX‰bA.08@b* ;)`xfI%)"SZ"1lIO8XRhfVUrʱZC׆Bb]$w4IuCLz9)iTS[71^~?XTIx5~`$ 0S XURș'u_qVICyNF{.M*jRRH8+&C ЧFDRƒ W)-N6$'Bj0#L 6i\@R"$\K)iTS4i›{R)rH%u9SprV,L\/^VrI]twCZ%`EK;'YĊ嫈;,(r]J b`ŀb"-w7T^K$1Iu`p fro?Y[lI'5҄U\BW ۸}ei}Ūpo 𦖔+m"qJi@4,xٝ&¦")ub͙&ݟvSujERNYaVPdhx}>[BbKIDqH?H 7Ҹ(!$dYDR.aI t˜p0Kdf,"\<"\Fb6{oY q"jER 1Yn`")WDEb' Iy`"[aA23o%mnb 3`EaEOXf0$E+Ƴ-Z"yJt‰ A$3tC}H WQ8'Ah6$YDR.aI t="04I7XrE:@- "1h-)(jby[*eay$N'B sdlGZx+5iaݤ*0Kj-pR,夺4j',KR?\-vDb\u冫Hd:ȘTXfr%)"Sdtl\LxrZ]!7_əh[!2Պ̢&D:10?t#S!l4uF̖8EnjG{d+2 .бXؙt2 "|Po[ #˗1"Ϟj%WiO\+gC;ڞ+/uo:㻷jj\MZm\j+6qqmqwq]UVkϥ=QKP)Λ#uτZw"sP\mϕBj]>'-qyڪWSqjj׾ڊkGC\[x]xpWոs'֮Mc?x[2ƚvyצ%n,czSiKzyC79sI;7m%n;yj()ɰ;{g[ {ړaO˹IԾÞ.9Sg^zڬ エWg{`ϑy<"91{fm~.Jgǵrvj0S}պX9fo-n\Ք-n\ZƵ/b7^p7>U5j\je -R4q)Ju>Q/}ȑZ}q7ͱ&.Ҋl!Bv^2۾~޲9=G_M>aYyUjno_\W9Q=B/}ܠyu+fH48. դP3wǺSn)qUg}#B[{'<JuH7p)N0H7L|Qt|~B׊rzdYB* bE; EChfy B!V<}/'YH&P!TJ@l ="րB {me.]8@1BjtBjt/1gj:Ba/XL!TJ@l{ˏ糗^f!'Xܼ*gB-疤OGϻ}M*Nk.]7w>Ҙ5wy BɻGL۾d]uxv"t{<ڋwRI=kWCC'(Zkq!fق۝'B!qO16@BL:]nX{gV ECnpH-nHҡ;$4Abߵ-+$YpB2:V:ھdfY TH7 T-pƼh䣥kˤ-3In40!qYD8[&Tخj'B!TA/o@P"݀PjtÃ/l1fM8i?h%Y,,op0i_#!B|A A<#\#KmEP2?X#{RO7$';خ}TOdԫ3}xXUˡ!PkCR TH7 T)Sg}kĔZ/-dm]87Ǭ^|Wpq%Dڎ8ΔuO"Í`jA*`,!/`bܥ${:h|g2Mt2 jqQ#\tԯSLIz"o;v MfQU vc^]͝O)h_|-!|x؆DVߊm"y:{Z[V.P?]rRR9X1V@ s jeywػ5.@LjYa5  DKonW2YHCQ5\fA-cB'᪤w UҗP!g=?ObQk|-L>bE;:+ >r 鹹_ݕ9WQtt#EUvrի6ݠxCVCn2WL%mN=qUR0jәTA9g)yMfEYԲ!s1Zrf'cٶJXTtC/EWYL6ci/dDŐnъO7jGvnb ;K2Bgeo@VU'~j?エM2ZVIzr~;`~ ?H5\R. ]!ّKD_gٙ4cȕ-ęXV+T0P5Buڬ #\Pgux5s )'`QvC*&,Z8 r`T! yڞZDHr ]\ E Fdk*tPgºev'=j<OڱNkĝ4U5bSɯf Mu؂U'Rō2*rzuN k$3YmLX0hpa]*yvI7M/ZYIzro7 K!$LaW` YH+#KjHw֚-'S_f4["{"dEaEeB-qO1墛6L& 0 b9y,'M5ٲ āQZE4anR~ Mä]<ׂdXr·֟ Zk: 7E^9uR*nu@5n>rЂZѫZ,zRF@naXy왝uih \:*Z1 0|6R޴rֲ/LY \˖]p۵coBC,sEh+;#waպ-g-Hf/x#h3K͗Ĭ5 @@`"ᾜQt l(kJx@cPCmY>J =3wV_Ҟw(ڣ/)gCUc!ZPSW1H& 0 blc\ml9&ޒͮ|dMM6I6Rf_qC-:ɳ IK{gUPb :Yܔu+KITʹOI6!L+NNV;bФ16O6vxs.0ej'8Xp!Sj!Bkv!>c,+NPEF\ؒ~%p^5_]k 75h!<|LjR r7/B-OW۪lC,bL%~[ݧؿ] 7֯OVekֿ^ BٳzŖGy2ihJog|A? A%for32Vc1,sG:aG2S`1E U1[7J:i+2;HRx7OZE*I-gR0b R[>!X4^6d2Jfm9KZdB_[6ɿbH뵢;l}P=I.a;$3_VbkE8q})o1bEv=f!Bz>1 )r<iг}MXi|ɒ]ܚX 8Ȣ$Ub,nJ -"Ił4}dɶh|gJSZkp!@6frci T U/'5Lƥ~Tpvt"_r'a'n>yvI7S.ZG'"BuɌbRFHsi7O""2o18Nn|*n(ELq bA پ[\aCtꕓdqSpT,MSG޴:{P2eq-6F#kCi%|؂PUbpRTW!]!EłjP>ivQN})ٳO/B!$H5B6.'h6bg amH]ov ~I!)jG*A$\!??8̖M;<CSMҺlCSI Rnho.Zov[=\RR)on@!p̻ǏY۽ܯP"=wpGȚH*B$-0M3} 6'a$^Ye֕KPq*v2dQn2}ܦ`aQdkJPnh>1ύoޱ%۵z˪E;ikvAbUt/>ōwUs*KD *>jT\*0>QҰL7lgN?yZjZ% ݀P%ݰPϊ痸EEWlY?~tB%ƵR\{Uڇ]jW/m\ ڊZm+-n\{jgLj%W:Zj+6UBm]ܸrx.n<}jT\*8'jPTU㜨RƇىzgCܧyY /47 Oڵi&~U e] ;[ 7(Ի=gۡ='^~ܡuaB;/vRvwaBjtBz@ϕIVtæcӆC{?dȑ/B!ԚzU]-r']ĉc%Pޭ-qM7\x߷c=ۥ?K#tB}WVd_eM?8}URDn>n\9MkrB>S;zd)3gZ9:1΢'Z[9wFh FvrgNpnk njyЙ,5*}}\D.ݠ )PH7]S Bm=ѧjO#z^ff6K׼]˯RPwagNZb;2iH7YY脮i.FlB J&vMP4M7}X B?w|/Psg};8kVR͌q4)Xom}8uk!^nԉ醆Z,׺W .o+yX[gAQUs9󿎏s WSB.7|/PXPWڦ R v+nY)/DIC5>;MnhQ7b5+yXt>kD~us<֮\r ҫ{6 B!ZP+?zgWyܤ&~&a ²zՊgO}!T鰹g V`w% <~c#Y|ńohA2O3h !oZ4)IPhU< 3OGLuyYfQpq,sC%לz>&r3I-M.` NBػ}- ?[O)hR&˦뚊kՌhAmE5sU?= ҒjC!EWtf,cLkypo-Y-HւͮhdKAkUH7UuO^mR//ׂV3r܉_"˯RM+O_EGզ+>UtSRnEzlv܅& B!ZPGz6x_RM9"5Ya6i'䚥'HA$OTqy} Bb-gUR.bsaZnt˝DYXHA_67u#ޮ@*%{^n(ADĉǶmK M#5ԝ7q]t|/>M7i گMkeǺZv"P\/w|>}_V18z{6ޯyʤkV,*_'&YBRЎT6^n#z~UmZx?8iÀK[T)\Cz|7Zv3c(Zq;2Bյbų-ryjt&Znp|G\9A3=7kL>HuٝQ7eAD(lT?Z ?p3lA?qavH܉*AQi2G}ЗMyeblt>eVq1}7\Gj?'5ߝu\ 'ǹLCJO>н -|dVa=1\H.rRw2wzC1K(Ʋ*aۍe=7/U6^nuX.Ԟqv1OK-9Yk˵Z$*VNjG]7syjST|\igN%3պm2kULپ9w~- MlY 2,*6Ϫ\xt\Y<ȒNSɯf M)> ӧE2[pbQ˙<.fK˙Q!pӆtsL_\9=qɒ yky fk0% !c x5ĜW (Y6b YUe3EpwIfX8{l=}h~~:xP(vr< cuo]oITfi-!ek!.6^_Naq YD'$fQ"8)cF a37'GV(9{A$t #xw5r,[ca aF7±؞l6ݏ}*v&oYXq+h!4Tug}Z]SN90ujR Z9ݐyr,A7k9- Cj ZۂPUK&Ϫ]x97YJijt2G2[p |ʬRmbfn*5tûǏ?s/\ziRn$% ]ɝ }⣗73 Y-G`yq ' |0' Ʌ` 6[V-!.S/K -*)8%۱܄#xZq@CfƼ}Cvb{ql_b,0OT^ll8c!,ۂaURA7F898!z;Vk8#D;3}>Wm۞;L/LCA+j!"HKWg7k9S SM*ulm@fMr\l]JZ.d)r ; i!z]bY n|_w"Rm㢫2>{qcܸrJ\2ݰzу-$ #X|kXH*%keevi-Aڊ򟐕 6e!vdpS&U6,9/K2 kY1^&Ge:w,\)j>}9>W-zlP\  i!^вBs+tqV+lq?K[GX$ss.PZŃl>NSWGWn(Kqg;-̣>h'l*RU=ʔ9\ ܹjѿN,nznq)X/2+ѲQOIn 0a d _kY`-VҦΉ g/-rڒ^NjZDw 6&8*^-ibtYb>9!<6tIm@7/4!Ė*S쐷i22=r NTѲngd)N!XRlĉ_[\5`eI;byj|m,r B$& Βn] lUrPFc1;`kb $0q1AqW-sybH3Ɗ媺aPLز iArJv&o9R<c/ѧUnPܙg6gCP*y'8Ut ^SNi][=OkR2\!֒6?݀Z;cHS!C!8BɲtEOQ 1BtB5~醾לzƿˤ\_Rs }.פw)XnMh*'?. u)a؈tB(hӚ=>jtaM7/O#q]Ior]P3guxܤ\GRs MѤdK]=[lbuX܂4,'݀ tܩ'~Zg?t腓 C]9msgf8M Avn@&5)6S%@k.?uWlu*ʞC(u&~pP#nqj{'YMM0?yc7CW \Ոtñm\/N}uMnbґ\r!I7 Bm{׻\Օw0zH;~F+R?PU+* IPw`T'JzlvɅH7 B MA:oj8W{_6bĈ;0SfJ@ak/uYB J7ԝ醽[\r!^}Wf3!jAgIMP}eȲ?L*AKԣ4[bc- 4ۦ0K7wK.tf3!jI5/꣏LE~pZ0OwvnHK8 eb7qję7ګ=uJ7[f3cM7e e\Hl!B-oOzWopiȊw&yȑ睷rĈFP.I-;Y:9+MX1VjGEt믾4|FyC42h`6`{<.U{J5P2u\H؏NpB}{oON50(^}5SN{uiX<l<%E:kW-=XbY[%(&F[aúr0) drpXWLF܈ :i*%Y!i Aj_r-ZrNPiF~A&Y zp)5, LLʃd%/>M7Au-|o>~'B OӉlG<9s={ܰvS;{k2:K?Aϝsgyl۶4\hr)E7~|IyĈq_B<}dXnUdB[ԍ."cja'>X;k;X#If^'M7$/7h_`uR`[t-t&ݠCbGHZvydGΆyE\**ʨUDN3/Qun*vZ't롡W{-zm5o(/>Xy=wmmU# BkN=5m +'.Ne% _rPKgdzGvoKGd+nԉ^t)L ._ۮ˰vS;^XW\2OM &4ԜYSPI UFͫ5ѵ)^Z6kPCF*`ZVWdo>[Ѻ,Z1 IKbc^/Nq0imYBhh vx -[{в^ v?}<ЗU o_SN5~|R4YK.vs՝_zӎ}l5)W#/ڣl>Z}Ws\QZPߺ[oŏ(BuԽ~ϙ8$w:n[G/'ԁ:my<}{?Z~L1\irAS`DW]T2K(q_B(d}\ r-j|4\ڤ,aZjfl)AlWA)LU:iE"A1<Ф416OvtyT<-Œ#I6ݹ+4mv`K۝tL#fzv񻀓t=Xg.tg'A[8Mw%2jF7io9ܳ4rE{t1;Mvpnߛ9_|ot|8a!?w?CULD}W_.w6TᮻT=c7~OLvCoO:r9?c۶uq'K nͦN!F\9&նtSTO 6T <]'=k VQ~:E*r]ܜi%mUܢ۝tL#[/~p*~qϋ}n׹B^ݳf- r ?)^ruǹCᮅ'$PP~Yg"C<[V-p'ae'7:]h?r>Uy`d*IrrߥμENK :_iDRxiXoRѭn>iB }Ssi<59}aTGFmƩiy(|~wi[om߾֝P94pz@eo:VtpՍ~lopz^OGpU[e ʧ]A75RF(sͺ5EMw+N+`y߀ڗ9;{ebj7$C&i[VUf'aE- XtJЊIUp<u%$'-Lܪ^B4"X؆wًg)~U,>M{`ҁSWʃ&Ҝ6$dMX0a\H?h'?쵹gmܘ4Bl7x^CԄCWw8&s]S.\:Ýuޠ6^2]S(yq٭nb̊z}*!8r][ܯʜ?nFmE5|Xءⴼb R1F>a~ɹ[ Ԧb,X[[;;)Uux/ĢW9ҝywCj+nQvsR Mu8%|v{snqL.iݰFqt>L?T knkdgd( ʼLݩKW4"Uz@ݗ (=nƇG,iT_5PKKik Οpt׮2gߜ6cˌS\WO=z6:J{qΙ]N:jW3_v-AӴ),R^ dYqT!OmKʌ]ԀuNs ^=j*UԜ}sl-Zf(;JN7.(DqEqZ;<8wï׫hئ &3- tûǏ?}iUPtW^=aڞLU^7WunxnhH7u-Jojtכ2zն2m-\.PrFVJ7Ȋw& %9w*nv+N~Bt݊?oGELQtJ&7ibJI7t]0m] 6sqP:`ƅo&4tH+n\vCܦK7İ醡U ͚?OO7H ιBؤ\} Zu\u@E%/!La%׭sLf3SLtðk䶠>vXRl4KM$VB/Ўfla\z)醊кr 5k{g>j-vhM tkx?uU VeLS|tgѪ޲iS^qg}' i=~\۽!T^_͎]٢t"dKa/qs cwi~RP]Hd6AKjZ% ݀PM{4,d GtC ǥeE!!D.aC ӧd.)V, 3xRH7hYxdCK7,3:  wi[Wk3|tCG% e7{,_#ʤ=|s~\H >P\c5 Un6} w鶬V7b~ms`s/[⣐ηc:H GY SQ;jH7?6ixg.?t1$7X>R\j)fla2LT+._(][r&HT\X-z 7=ygM& 01BW;~usXn6 . 1; iA8pO~ܫ}%^)f[lLD|kCHPhncUdY򔿤B*j3ZX7ctCPAuGs%.JoB|n 3'v* 1~>SzB,`oi ÊU\+/8bV9 JW_:R3H7 T!potH +_^z݄jS"U+Vn}-(U8Hc# ul~3">Baq [1mz|Ԃ#8( A6. =E \dã"gz 9tCx,V4S-醼MXm6k]4 v҄ܧSqm#Dq4-QD=bĈ;{4[} ؚZ529@nWݯ u^QN:X{u$CRtԦ Cl馌.oub{AcnVM] w~rխزVZtpwC}~I¢p@ JH.C>K7H#cՒn27vP^n-4 q319؃`ܧSqm#Dq49ݠI?Vs֔B&Ec?1qńo(*iͻ-Up;D_u*6Fy,vggtLҳfO1m"X"=D[ڷv"3p=ݠe{"RӲI?{qw\B@} ]%c*m*ZWi#s?Cmgv❤Po?yc7H7d{zsú4qiL*m 4[Ugl!дKqUWoq7K|թhP]@& K+䩷g^,Nubx2&,p˰Ul93pjO7TnŐ豢êsb:}͘>._dX1wFr(>.;׻vI7 B TId꨼M,|sUiUjL7|;?|ጛg?ftC;{K?W2{|9OllT!5kH7dj;v\4\rYD laB3go (ЏkTbrBگ]@-M1?ݹ+|ك1Ttz5n6SߝySL.K=jMuO7( CnbxCx ;Any]5P^ӟuK:YPzN5ntIį뢅mkH[q_BR7,QT[[oF+Jڐ,io%Rܓn؊DP8Y7sU sKy*1l4=0^x FXi% RkU**nT`Eaڠb7-_~*aul;fu-FZHDFETXdZ nos6ɽ+I]ScyGIKaOj6P13W͇t*7`|t;yz&RFȦBlM5A mlk9jCQ S55،֬!+jYP9X`R1T=z55iEZZWbUۈd jWŲ}P"ȶ:HhOW᭸y#,TH7UuiתCUF+6Xf ΛXnZPf R Z wfue| "%4%َX ʌUpD2PqZ.LPϊ痸ShP'bJuO7>ݹW >-nb]87?ɧ$]ҥ$G_sA B7GNѻJZ;׮}9owt%|ܨlW/_}lޛe?OUqg.aqZ7[c}4 Vo|LVoO&M'!}seqCiڢ8SS[q>iM~৉w49P<O+s\7݈[QoCeE'㙼S`f7̞ȒڨժXm#G%O/6TTk!쏍gj>捰d ڐL1P}yOm-y*pxj괖BU,5n0NUԺ(̊z A;:i]ҪlTCԨ ,r>{-9Syd3+S̿7,AnH%PRNSvQٯ^F>GYgθ[dO*1cӯY}ԴQ~=*83oMM/,OkOW*N7hGZz!pX1[m w7ܜuBn%w)RפQ;[hteƲ&hw>/Cޜ|f-\U,Ǖ8jڨf*F< gA48?Ԛ1yNH7h8=DRNU/YP2%1֡)n F=$0XyQDF$uR'- P |城)YgjxUaׂ>jYهVne(-ES sB94eoT6jNvJd37d9Ykl٤N_ڼncd_2SL捰NQ;On)sOɫ.F+6X-0 ȃ[S3z0ى0NU R.0-7`*3VCvg~Rs w{.lfrS851ŔjO7= =)VIK׺(6d}p1E,O1FŪ>捰Q;4|ܵZ xv حi.079*#3;mW&̬5+-IE2k Nn8Fϻ-K+՞n63)f:`b)7ߺ{A"x($'n0tWP=ݐh0YP-<Ol!c/_5BeB+GΚ*knوV,OM61vbV'ݐs%MT3H!1UlĶ.dQ- J;;C 5 7<o} XvT'n# C_2BUgъ ٢j9h[6iy5a׃^5ueVQ1muYԔ]&5.Neƪ}(.f_X yW!/`)8vهJJ7|s3 & MPRW1V:0=aϟp7:; emnwhZA;uljL78d7}3>|Qe[}?.:[mHώ%K "5k2.LBYCΒC\]a MfdfuyKJ0'yݐT@܂l+X܈dn) :@fvv2mch[qT~x`FXio % e;? 9vbKɫTqR+:ط<.'u'^Cnz-sl3j*lYTVűHe:00fL2PAktMCSA\aeY>|vݥF᮳+zզɫ'Zj6*a]sPkiξ9$h>/>ϭAZA횊z MS醊g;7ƹ B?Y5QNj7tZscg,#U<Vˬk!Bg7iDڴ?6jvTf,N{DTsF+6XơVnC^ʏI 3gci➔<":yb t6\HbG'L#T)fJ7Q2oZЄtÃ}%&̜1zr,xmᅱ"d U]d\8[Na Z"lK77,ijtC!-{QK7lA5M+~Vk~n g]q/[H%htjSRoD*q 7֯OVLk~3׹BZn(? |nheߴ8re~|܅bft_Gqٍ] *k_(6qYTU&-xmAp.M9A.AK7humBsA+ݠYH6AmnXpP+>eF-lP~gg kʚ ܥjOʢu{(ׂBTe9svقyȑ+'ߵP4[E;cy~si6-VvK}Ȋ*t"*#-`L;|%G8f3PŜY`ޜҌ6]˚ֲ-h5 avl8OlUS w~,VkjС|v K.5mLF?!S3̰ _*0yz2zZ+tCTH5ٺI-c?:}զLP8|?ksiaO|L $T3R; \,زA.떑#qɅ/ljPR VU~hkѰI7(Of/s86N-a bFqw~~͑a bdn '? yp'EKŠg)`>* h+BvLr*+6(YGjg7}%/a]ݚ Ζ 65Wl[V5O.*L+5tڬ  Avn@f tÝ71r9vl i> f3P]y99_4|^~UMԢngy#o`n1j-Nxsm-HTCoڸu2[cZ1GP{%C. ߚbrB7)8 B} Ivn*KUXpp?|!]J6,-i_hYUlPҲ9jtCxYl[$k*(庨{.OH7!Avn@fCi#Gڟ 4BH7db!hUG=DkH7=,7UM%DUij?[.μ?媤J7ؐWdʂgZoKlk\teBRPnm(t؊G9ڊ!RqX29V,n0$qBv1];Obͤ8=][o>"ݐE.'5pZv߂#y|2_pzeV7i0`?UMma&ZH7;;FRxB%$HTˡ|v㒋QFy8 ?,!N,eZ+-Id^AQ#eU@A-d:ʷ_L&M.10r;aE~+"s{Kh-w**}owinHCP"݀PM*y WmfK.r7inHVjhMԢng?Ё/8֊%B!1c#4Cz4_RjtBhġb~Q/7)B;oɌCX'C?Xd EeU_A)ӭ jt= X#:B=}ƥBS[2[3g[|*|[7-}E!ͻǏ+y!ʫѣ+~5[nRDnK.ջ %gaNiE*ղbz-"0E# G/.2)j*d%l+FZkow*`_ˇ?XD-r64Z|,a-Imne!J .a4 Ρ+6hҲ9jte3wf!~Y]FV;қ?w-M|D0ƭEV][}}gtC6Bj(Џk0K7_9Jf7i5,9e<T\j>MŰZ4l  +>T*y8Sx`KN-d((뮒re3+j .0B;a5Jɫ'6(<~ a [e C[ѾeITk` X%fz[18OTq5h C,Q*OR?P*5 _ Nnos6yf!pJ7t*ʥ3\mE U~?!FU އ'8ZF|)KFՇ¦rV߬(g !ڷ톢d!5(ق^a7hFⵚ# C-oK7Ȋw&&~U e] eSK1IJtÞϸL#S"0lT|('>rq4g^z/4Ty0aCLAu+ײVXWhk!ڐuCiI ȸk]꘭~3b{R:7CvۣPmhl-_L5:ݰ+^BA-;ּa ?G>*n@(_G% 7q)X/7sn6 pE{o#745'?KD$j3VϹj+89Oz@BzE.1Rg,t,qVZi 6'U~AZ:<8]]I/L ZM1r9>Wp2<UGEr)N0J7|cG.u)X_wlAзw?yU9/wo><, 5H|~c\{!]/q#<ٟl4}kʅKg:ܵp%#- } NUNp k.<3~.؏Np3][n^io\{=mWv(4ܺ:w jь~89z!jߛ4A؝o]SH#ozug/~O>s'CP"݀PF떑#q)X_8Ls_~Ưۗ7s'CP"݀PMz@ϕ*HVdΛy:e:ܳMe?fWAjX] 7_XO>];K\@PſuϷoMU94pOw Ͽ}gnpr2iq]I)6-d۱e^t+m-cuo7w-tc ~\P-8oy(Ĭ4 P#}U I O #yc_pwL6kB<">rgfz1_c:.WaCiŵV jƍYVlO'Tx|Z]!=[T{h+"[_W/; 4=t6jy(^w;$:G_|] l<9s7N-q'&!JͽϹOO`n6 5[/>R An/>=s8~"aM%\vqn8L<}?sٻ>W--ѯ?xK_yKgܽΝrԋݰ(mdoƾW)[I7@@I-2GV>%L]2?n*4)t_s쵹g ?ckw\u&˒;{&!J9uk)BҐѐ7! |O^>D OjnG#Yr5۱Y믺f4}|̼}`-B+z͏xqg2ah.ڛc#Gs]tsk*}w6/jmzwoI\DT׊~9SHC|Ayowg 4{ިiݸU!P+;yΦ司" ڛ |;oW{6 BRww?GMu= W;}| !t@{SUAzekxyv7Ahj)wyKU!P[Ȟ  MnrWluSz~cΈBm7w0DnhoM7qCk>7 4 >?/|!Wo}npr2ih.ڛj KBTVj9 P'{N50nho~us͝:{s_О׭LvSZg Ѐ~:Խ?w"C'5([=O_uա=/dovtqZK4 t )ڟֹEc+pWzE? K Sc=vPg3v9VR9ďPSoPܸ%񸶸Mkrݹ JōժP[qW+ŵōwqڑ-Zx?_̏?1iD5͉jj܉*Uu.U{x.54uK3H7B9[T;x?[ɏ]+cSj%CܸrU5~98Ƶ/vǵōoZV*n\ή6ڪbVkOƵ#qmLJ[rs}?x6/y9V[psD5DUչTZZNTNKo<%uԯnt@v7@hxkygɨi๫jBY=bEV2ah.ڟvB[}^W{;;BX>>bEg~2ah.ڞٹ0NZ􏦭!*j۞gֿb2ch.ڛ61KO(BCM[1q]cԴQW!Pel_2ch:ڛ_8bEg=0O/jsM^vn h@dtH77=G_1MVP+kz<!j[G@!~#\sBk=˜!upC2W H7=ytĔ=u È7 BC<!K|)Ȍdz0DnFpj=M[1CǾz`c !PupO _y1 _s{"BFLJ&CԧTM>jzw/5c7 !Ptxӊ;Y2-:H7 +v۾>bEs:?j4\t!ϽN_2'jH7 7n^5bEL͏YB Pe ?h@ד@ @`2n}#\tK{v !4zP2h H7 C 'r鳯ܶ ?A^6mıwтB!|ٳnZkM2 hH7 O3}Ĕ72.Р~7BC.}ğ6k>O}>I@k@`r_].7;AZyQF'<!ZDv=闌rse 09/{シQJ'<!ZA?=qS.~zփt@gw;zབྷֹ B4zgG!4 ?nڰ2h=H7tb Ϝ:ҩ縹 B ]jB n?}w\pC=;@KBtb>W_:uK$u๣srx!ZN/o}ﭣ4?xgʐn8|ai}/9o{f=y͢Է#jYs_:Y?~{?:/f_mIϹI{xe3nzG|f<{vt>p>|L{1dq>|LږTϝIw cҘtns05h Sy {sh=ϳ-=ɰ;u°y԰=eW ''mNgkn^~ثnc:qF|L\1s֦'iڇ,ǔVIr1S|LMr?3!i'̓awK{g[ {ړawala?U; '_W^L> w~ŝiJ癞yǴC 1T\cJ{j+ǔz|Lq1,3p:OsFM|Z״cÞR=ٞalaO{2N0y-5B DO/KL tpN ͂td㡍=q٨i;614 ÍQF{{@@s 0ܸx=y@ !ڞGę}G%!t@u6j9  Ж,ھ >5!~5mht@{ ݽ@Aձ4{LRhyH7. n -ʵO];jڨO/4ht@s@WRh+H7[$evt?Aˉ!0ds nJf`AytjU {H74wm_zԴQc۝T SH74[x+t@c Qn3xhcRHH7ԇ /8t$I7[ǎ{{OpQYx:tӺ@M7|/Bw>===/d/ 1togϞ$Pm~(t')ѺK=++Lu#Gر# vڄc ,\@U^~$ap22տ~#¤?soMNF%Ќ]9M7KI b[ )<*y&D:t(i%9p@4@d )nxW >VB䯵Om }8gc?Ӊ r7Hr'sӯ~7Q^Z+}O0ɣg P!ogC޸|K"T+5Ν;d{Cʧ|s'1@TH7C.}PԂIZF.^x ͛XAI9"ԁd)dOfѨ!#=<v oV`mO"֒vs78$q k=HZL',0aB(a|2%7Uq=_Lԝt7.xe 㶮֒v -,ȒLb~TW$N9$N'ԝM {@CׁDnᗿeԆVܭKZ|ݻ5aٲeϛ7dIjI9ER]:ݐK@tv6JjUZNPWr of  qAL}eQW?ϓKs7Z„ ]$e˖o/^Ik%K&w PbCzղTD+۷j̙I@g3gPr N-?qɂ+z!"|2ixWkY8mO 78nER 1EFiģ,@LR >I҆NCbh ~K/Nr K 2{d\AZ~k_|1Y֭(z˖-*ՊaFHL'3gZgG$pri,J-'7zj3*.nVᦆaρ}=_A@M7 I] ,bݸ|C={$+#dv]fIyt OԂYYDR.IH<*NPsR>vߊ"@0GM CBn! E].M𱾹[rrtÄGKݽsdrX,}7&吘rbT'T9i&S~t;dڵ+ PmW)V,)2&µ$NnGNIS{@w1醷~;I p}?ui<%N7\6HV.ŋ`: &~b̢"cR.\+MTx䴜Nn!17Tn݉0kZb?\:b%H)Cj;0KW>!)gx䴜Nn="Պ 0={ҋz$zTOlpi<%+N7LY|jy! t ˖-S7 a>_-T%L iB#pr )lB~$ۥ_y+<5M7߿?Itw/_ե \:ݰzdHVD/0cB d@x0D|D tģ0O ŒJ?ZN}PwowbHtM29OA濹O}=PI0܉0cKMtxݯ*SV>יsӓp޽fSq2jG%pgE{[ǖ@|ΝI`@;}+zV$K7~;fyvءuB Gz\ԵW*)@Qu/~K/%Yr:tHk%p$~@ôi:A~_?p@3d饗&00jJ7t!ԙJ.`E<ZQ%1ܰ憯<rjtB-Ƕ^h0m=֛A V~:P Kw-=oyNNL 3BFqNx@âx@ n{C3>qn@Ԉtdi tB- }G@!݀Pi;t՘s]V<L ?SL>qƖ]qv jyĈK. J5mԴQ<J7\bbًf;{-8i 櫨`3vrȮZנjtx5i$gGUrI@ޞ#=ItzuU"nXսJ'6<,*'8cAohЖl醊"O6]Uقj/xܱE-^V >ʴ9ZnF?S7zh-H  -k: P,ZX{C\+)Q1Jvv>Z;v'EL*URܠ+qσcxkD %l +j9mƽWwnb:yFa.ݵ4@ 8n qlIM>IkKܡLAa?x}Zn()<|(n׫:&*Z/Ѿ(J/1V] ԫ?񀫨Wu&{UmA+FD$ۨ*֬ O7&mnPj% E** ŢtEW>J0* VP؊kLOyrkDxpȔt7Yƫݟ%tvǭPnvsn4IY,p5)%%b(=hr>lGX5X~t#2K*Ag1y93zkw7; nvs`ɅUݫ8LM'Z'[%5X}Y2 {Lev_W L*GƼah 6FӛFM(Wpl5Xt?ҍdu,stCc+!c$BU6Փpd'Cl1PLK($ړNL7?G};BM-2{FzfT`瓎V# [etJFKF2enզ;|BE>zWU6T}(jL&M-HjE%ОtbAqEEn`RQڷB ci5eQnF,*WRaAT5X}2ZmLeGh2m^40\AJ6WUQQ3;j3yR([I]y.9cTh lS'RIFUzf#DڝJ7 0*8"A"H7ҦO4)t;j9nvtBhOZ( GK=!݀P+*$ K=!݀Pi뱭,;0ڍJ7;zk׭npB&ڝJ7H7@ChwH7#F^4٥3vj yN>koWptx2 Bt;ne1cǸ*jU5qD9?Ap+3 - tzaܩ#FאDJgvIxZ\uhh7Z(0$L1fI'&"3`Uݫc Olx"31|-$]bzUWCmh9HCPV$7k)sE8%W@{B^ *ߊ-`Opk% ]6AyX+!9(,V f/ ijz R`*ފjwnR1VXJ%8hO::0i$Ҷd-5JKXX^2ݠ0^k^P?ܧ`FkGEk6NF؊ZKXCtCŭXcskA2;jK=t ]ZB8`Yvp2n6X?IXBXLVwX,585kځ htC;}OxZ $K}%@!zd%$ 3L8P&Pq+vwC{&Ujg hwZ(ªU]?^8dAH7!NO񼌙 Mq6L,-ld޹_L^kq"N7;V20tjBo4tۄ WmcN<*$ !hw:4buWVb̟9IJ eVŭzb0c$I7 H7@ӡ醼8BsZqiFU:[qs/*?@Mhw:1`+n1V%i%j'x=#A.Kb-^4;5DPq+־UY qn,)itCw .ϊB;blZDA6ڱ%U;I% ĹaAK@̧`+va$Ё6 %W@{B^ :Avs3*dI@[hI%@{Bn@R%Оnh'n%@{B4isǝhvPt;-nha""ZN!݀Pˉt;j9m=tv'@AVTrU'-n&BhOH7hI;Qy%@{ҹ#F8iR|_1ٛ/A=hK;^1fTߵ+8l:Iv sƠ.-YD}@%@{ҡEl E4|Z$漢[uUIV*E&s~ba&8ݐҪU4'xRGE@B醣}Gou .j,Up(=P67GSNU5$ҡ; v#ӡ *7%ACRσOѨV&;-nh?Spbxz6+*^ve+J(7^WI'eA mLh;4\jrd?۴Yc7[7miZpОvjm`L֎vynv ,kf)Ea-(8wRo rvC*WFoZ~H|hV 놵2GIpًfqsw׆:oGE~7ȶ0,q>V;RAˋt;mnc:VTZ*Ȕ~o*񲄯-8p*2m䬢Ej6-eZ:(ޗ KLV2GbVx;4d{Nۢ킊GZъZ%K6P A֫mZjA vmNԀ6&lEU8H֎2]^itbW' -OF*VԲ|UxR 5b1]mbեtl[ڡXeHco ,N֦6x[A֦= #a94wA1A"w7 CreEh_;|vQm&׈S蒹n٦,!**$C;Ń*m7dn TbKj;t;\F  -k+E.pu$7EXZ]Pꌖ23v'2UݫԂɺotBciɡ=eqwI=ܱh2y>-[~!TI,ENXnr&qK**ړJ7\ ᮤx^EťZV.dB%kZQӑv݇I%/i; naBXrwG[H9ʼn˼hlB^ (oeq!뀤n2۠RK=!ݐXt^œ \7ZvAi?)HdBjt@eQϱGKO\l|,cu''*:SzUtHoΚ ר8RnTrI'vExRkpaZj'n|2݅Ay!8OSQAmw ԃCZ.Yz"63JZeLܶTtu;jA*շkZ״G$@B醾w>-z5BpWQ,kI+*T */ƭe!Uݫ,nKvftin|m9/N% ̶7U쮍P c!BdӃl뭥ԠV;jVH8H%]Q3;-n?S NE c'HI REGݢP˚@Uۗ8tw*v(GnXO Ź*%5":cEV1K^+a._+Z BiXny{.Yy"c[/X|Awowrm~A,wjUF$K";\6I'8߹u$Uze,OlxցtïT]1lAREerJ7b-ɂًf;d=o[HBL-# Ne<-`-Yq -ˁt@С3xXc(Je"2k9e6^ Uvo^]*Dg[LL84vZd"OutCY`Ѳ  erJ7bsk'H'#Ԕ-uڏM7HM>)s4 (np-=itC;}OxzW.w5BQB۝QWvź םd+Z#Y)݈gk^4ڱ{2Ia"O5%)J{_(R =vS [E(i*t!=έhOZ( sAW㤘ӂXHX!A!zTI |B#$Y)݈-`6P1B* eTAZ["&F3{]jYe 6 *vr!+5dAqnY%@{ҹ"fJr:uˬnTi bvvځuVSrI'n@o״iH. vC݀P+v g ZY!݀Pˉt;j9nvtB-' n@?S@Bc/>Mz!ԁJ >t!ԙJ. tBhOH7&Mts W{g_ׁ}G tÎw;f _1^Uέu4qDpLN ]{;Fai 0bγEDDڝN7(M7;^# % nBzUFI?ቼL9Wj%orURXEK.i[hE(醎hwH7ʘN7[.)bV¹M*8aa}XսJvsf1f/MD 'Y18W IU=cRjpa]*.g`JEۄ֕%#feYvea]4 _۝\NBf4uTCrAR/ˤ$KXP܄Lu-! {%W@{BѴ?Œ krkE%ܡUS A>bP準pF4\IGE;%ЂSfm%nQ8#NSrI'=[!J8%̡ nҺ^4;.n@J. tCb2ďQC1OptFSfՒKdԫ 2F4\ţ"itѾvݺz5Bt4i$ã3B@Y%Y"nHo"X N 0Uݫd V1g_6_~#f/ZjúVĆ'K N7}֔dI%phw:4`n\T/7e^e,!U!uղ!/ YP򴤃sCUtCR/9cЦÛ jUg(k+vhwH7 r"ZQUОP~ǝh5>?>u)W[/_yyUbVۊk^ڪ:5VrwqϥƋwNhOZ(N_ׁXݽImtZ%QMbI6s'q9Z5ԕhj[Iōk/\mUk Cq{W[p7^\*np7Dh_Z4 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C{dӬTIENDB`././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/images/graphs_authComp.svg0000664000175000017500000000570600000000000024712 0ustar00zuulzuul00000000000000 AuthComp AuthComp Auth Component AuthComp->Reject Reject Unauthenticated Requests Service OpenStack Service AuthComp->Service Forward Authenticated Requests Start->AuthComp ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/images/graphs_authCompDelegate.svg0000664000175000017500000000702000000000000026334 0ustar00zuulzuul00000000000000 AuthCompDelegate AuthComp Auth Component AuthComp->Reject Reject Requests Indicated by the Service Service OpenStack Service AuthComp->Service Forward Requests with Identiy Status Service->AuthComp Send Response OR Reject Message Start->AuthComp ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/index.rst0000664000175000017500000000330500000000000021432 0ustar00zuulzuul00000000000000======================================================= Python Middleware for OpenStack Identity API (Keystone) ======================================================= This is the middleware provided for integrating with the OpenStack Identity API and handling authorization enforcement based upon the data within the OpenStack Identity tokens. Also included is middleware that provides the ability to create audit events based on API requests. Contents: .. toctree:: :maxdepth: 1 middlewarearchitecture audit installation Related Identity Projects ========================= In addition to creating the Python Middleware for OpenStack Identity API, the Keystone team also provides `Identity Service`_, as well as `Python Client Library`_. .. _`Identity Service`: https://docs.openstack.org/keystone/latest/ .. _`Python Client Library`: https://docs.openstack.org/python-keystoneclient/latest/ Release Notes ============= `Release Notes`_ .. _Release Notes: https://docs.openstack.org/releasenotes/keystonemiddleware/ Contributing ============ Code is hosted `on GitHub`_. Submit bugs to the Keystone project on `Launchpad`_. Submit code to the ``openstack/keystonemiddleware`` project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/keystonemiddleware .. _Launchpad: https://launchpad.net/keystonemiddleware .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox -e py27`` if running with python 2.7. See the ``tox.ini`` file for other test environment options. Code Documentation ================== .. toctree:: :maxdepth: 1 api/modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/installation.rst0000664000175000017500000000110500000000000023020 0ustar00zuulzuul00000000000000============== Installation ============== Install using pip ----------------- At the command line:: $ pip install keystonemiddleware Or, if you want to use it in a virtualenvwrapper:: $ mkvirtualenv keystonemiddleware $ pip install keystonemiddleware Install optional dependencies ----------------------------- Certain keystonemiddleware features are only available if specific libraries are available. These libraries can be installed using pip as well. To install support for audit notifications:: $ pip install keystonemiddleware[audit_notifications] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/doc/source/middlewarearchitecture.rst0000664000175000017500000004561300000000000025053 0ustar00zuulzuul00000000000000.. Copyright 2011-2013 OpenStack Foundation All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ======================= Middleware Architecture ======================= Abstract ======== The keystonemiddleware architecture supports a common authentication protocol in use between the OpenStack projects. By using keystone as a common authentication and authorization mechanism, various OpenStack projects can leverage the existing authentication and authorization systems in use. In this document, we describe the architecture and responsibilities of the authentication middleware which acts as the internal API mechanism for OpenStack projects based on the WSGI standard. This documentation describes the implementation in :class:`keystonemiddleware.auth_token` Specification Overview ====================== 'Authentication' is the process of determining that users are who they say they are. Typically, 'authentication protocols' such as HTTP Basic Auth, Digest Access, public key, token, etc, are used to verify a user's identity. In this document, we define an 'authentication component' as a software module that implements an authentication protocol for an OpenStack service. Bearer tokens are currently the most common authentication protocol used within OpenStack. At a high level, an authentication middleware component is a proxy that intercepts HTTP calls from clients and populates HTTP headers in the request context for other WSGI middleware or applications to use. The general flow of the middleware processing is: * clear any existing authorization headers to prevent forgery * collect the token from the existing HTTP request headers * validate the token * if valid, populate additional headers representing the identity that has been authenticated and authorized * if invalid, or no token present, reject the request (HTTPUnauthorized) or pass along a header indicating the request is unauthorized (configurable in the middleware) * if the keystone service is unavailable to validate the token, reject the request with HTTPServiceUnavailable. .. _authComponent: Authentication Component ------------------------ The following shows the default behavior of an Authentication Component deployed in front of an OpenStack service. .. image:: images/graphs_authComp.svg :width: 100% :height: 180 :alt: An Authentication Component The Authentication Component, or middleware, will reject any unauthenticated requests, only allowing authenticated requests through to the OpenStack service. .. _authComponentDelegated: Authentication Component (Delegated Mode) ----------------------------------------- The Authentication Component may be configured to operate in a 'delegated mode'. In this mode, the decision to reject or accept an unauthenticated client is delegated to the OpenStack service. Here, requests are forwarded to the OpenStack service with an identity status message that indicates whether the identity of the client has been confirmed or is indeterminate. The consuming OpenStack service decides whether or not a rejection message should be sent to the client. .. image:: images/graphs_authCompDelegate.svg :width: 100% :height: 180 :alt: An Authentication Component (Delegated Mode) .. _deployStrategies: Deployment Strategy =================== The middleware is intended to be used inline with OpenStack WSGI components, based on the Oslo WSGI middleware class. It is typically deployed as a configuration element in a paste configuration pipeline of other middleware components, with the pipeline terminating in the service application. The middleware conforms to the python WSGI standard [PEP-333]_. In initializing the middleware, a configuration item (which acts like a python dictionary) is passed to the middleware with relevant configuration options. Configuration ------------- The middleware is configured within the config file of the main application as a WSGI component. Example for the auth_token middleware: .. code-block:: ini [app:myService] paste.app_factory = myService:app_factory [pipeline:main] pipeline = authtoken myService [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory .. literalinclude:: _static/keystonemiddleware.conf.sample If the ``auth_type`` configuration option is set, you may need to refer to the `Authentication Plugins `_ document for how to configure the auth_token middleware. For services which have a separate paste-deploy ini file, auth_token middleware can be alternatively configured in [keystone_authtoken] section in the main config file. For example in nova, all middleware parameters can be removed from ``api-paste.ini``: .. code-block:: ini [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory and set in ``nova.conf``: .. code-block:: ini [DEFAULT] auth_strategy=keystone [keystone_authtoken] identity_uri = http://127.0.0.1:5000 admin_user = admin admin_password = SuperSekretPassword admin_tenant_name = service # Any of the options that could be set in api-paste.ini can be set here. .. NOTE:: Middleware parameters in paste config take priority and must be removed to use options in the [keystone_authtoken] section. The following is an example of a service's auth_token middleware configuration when ``auth_type`` is set to ``password``. .. code-block:: ini [keystone_authtoken] auth_type = password project_domain_name = Default project_name = service user_domain_name = Default username = nova password = ServicePassword interface = public auth_url = http://127.0.0.1:5000 # Any of the options that could be set in api-paste.ini can be set here. If using an ``auth_type``, connection to the Identity service will be established on the ``interface`` as registered in the service catalog. In the case where you are using an ``auth_type`` and have multiple regions, also specify the ``region_name`` option to fetch the correct endpoint. If the service doesn't use the global oslo.config object (CONF), then the oslo config project name can be set it in paste config and keystonemiddleware will load the project configuration itself. Optionally the location of the configuration file can be set if oslo.config is not able to discover it. .. code-block:: ini [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory oslo_config_project = nova # oslo_config_file = /not_discoverable_location/nova.conf Configuration for external authorization ---------------------------------------- If an external authorization server is used from Keystonemiddleware, the configuration file settings for the main application must be changed. The system supports 5 authentication methods, tls_client_auth, client_secret_basic, client_secret_post, client_secret_jwt, and private_key_jwt, which are specified in auth_method. The required config depends on the authentication method. The two config file modifications required when using an external authorization server are described below. .. NOTE:: Settings for accepting https requests and mTLS connections depend on each OpenStack service that uses Keystonemiddleware. Change to use the ext_oauth2_token filter instead of authtoken: .. code-block:: ini [pipeline:main] pipeline = ext_oauth2_token myService [filter:ext_oauth2_token] paste.filter_factory = keystonemiddleware.external_oauth2_token:filter_factory Add the config group for external authentication server: .. code-block:: ini [ext_oauth2_auth] # Required if identity server requires client certificate. #certfile = # Required if identity server requires client private key. #keyfile = # A PEM encoded Certificate Authority to use when verifying HTTPs # connections. Defaults to system CAs. #cafile = # Verify HTTPS connections. #insecure = False # Request timeout value for communicating with Identity API server. #http_connect_timeout = # The endpoint for introspect API, it is used to verify that the OAuth 2.0 # access token is valid. #introspect_endpoint = # The Audience should be the URL of the Authorization Server's Token # Endpoint. The Authorization Server will verify that it is an intended # audience for the token. #audience = # The auth_method must use the authentication method specified by the # Authorization Server. The system supports 5 authentication methods such # as tls_client_auth, client_secret_basic, client_secret_post, # client_secret_jwt, private_key_jwt. #auth_method = client_secret_basic # The OAuth 2.0 Client Identifier valid at the Authorization Server. #client_id = # The OAuth 2.0 client secret. When the auth_method is client_secret_basic, # client_secret_post, or client_secret_jwt, the value is used, and # otherwise the value is ignored. #client_secret = # If the access token generated by the Authorization Server is bound to the # OAuth 2.0 certificate thumbprint, the value can be set to true, and then # the keystone middleware will verify the thumbprint. #thumbprint_verify = False # The jwt_key_file must use the certificate key file which has been # registered with the Authorization Server. When the auth_method is # private_key_jwt, the value is used, and otherwise the value is ignored. #jwt_key_file = # The jwt_algorithm must use the algorithm specified by the Authorization # Server. When the auth_method is client_secret_jwt, this value is often # set to HS256, when the auth_method is private_key_jwt, the value is often # set to RS256, and otherwise the value is ignored. #jwt_algorithm = # This value is used to calculate the expiration time. If after the # expiration time, the access token can not be accepted. When the # auth_method is client_secret_jwt or private_key_jwt, the value is used, # and otherwise the value is ignored. #jwt_bearer_time_out = 3600 # Specifies the method for obtaining the project ID that currently needs # to be accessed. #mapping_project_id = # Specifies the method for obtaining the project name that currently needs # to be accessed. #mapping_project_name = # Specifies the method for obtaining the project domain ID that currently # needs to be accessed. #mapping_project_domain_id = # Specifies the method for obtaining the project domain name that currently # needs to be accessed. #mapping_project_domain_name = # Specifies the method for obtaining the user ID. #mapping_user_id = client_id # Specifies the method for obtaining the user name. #mapping_user_name = username # Specifies the method for obtaining the domain ID which the user belongs. #mapping_user_domain_id = # Specifies the method for obtaining the domain name which the user # belongs. #mapping_user_domain_name = # Specifies the method for obtaining the list of roles in a project or # domain owned by the user. #mapping_roles = # Specifies the method for obtaining the scope information indicating # whether a token is system-scoped. #mapping_system_scope = # Specifies the method for obtaining the token expiration time. #mapping_expires_at = # Optionally specify a list of memcached server(s) to use for caching. # If left undefined, tokens will instead be cached in-process. #memcached_servers = # In order to prevent excessive effort spent validating tokens, the # middleware caches previously-seen tokens for a configurable duration # (in seconds). Set to -1 to disable caching completely. #token_cache_time = 300 # (Optional) If defined, indicate whether token data should be # authenticated or authenticated and encrypted. If MAC, token data is # authenticated (with HMAC) in the cache. If ENCRYPT, token data is # encrypted and authenticated in the cache. If the value is not one of # these options or empty, auth_token will raise an exception on # initialization. #memcache_security_strategy = # (Optional, mandatory if memcache_security_strategy is defined) # This string is used for key derivation. #memcache_secret_key = # (Optional) Number of seconds memcached server is considered dead before # it is tried again. #memcache_pool_dead_retry = 5 * 60 # (Optional) Maximum total number of open connections to every memcached # server. #memcache_pool_maxsize = 10 # (Optional) Socket timeout in seconds for communicating with a memcached # server. #memcache_pool_socket_timeout = 3 # (Optional) Number of seconds a connection to memcached is held unused in # the pool before it is closed. #memcache_pool_unused_timeout = 60 # (Optional) Number of seconds that an operation will wait to get a # memcached client connection from the pool. #memcache_pool_conn_get_timeout = 10 # (Optional) Use the advanced (eventlet safe) memcached client pool. #memcache_use_advanced_pool = True Improving response time ----------------------- Validating the identity of every client on every request can impact performance for both the OpenStack service and the identity service. As a result, keystonemiddleware is configurable to cache authentication responses from the identity service in-memory. It is worth noting that tokens invalidated after they've been stored in the cache may continue to work. Deployments using `memcached`_ may use the following keystonemiddleware configuration options instead of an in-memory cache. * ``memcached_servers``: (optional) if defined, the memcached server(s) to use for caching. It will be ignored if Swift MemcacheRing is used instead. * ``token_cache_time``: (optional, default 300 seconds) Set to -1 to disable caching completely. When deploying auth_token middleware with Swift, user may elect to use Swift MemcacheRing instead of the local Keystone memcache. The Swift MemcacheRing object is passed in from the request environment and it defaults to 'swift.cache'. However it could be different, depending on deployment. To use Swift MemcacheRing, you must provide the ``cache`` option. * ``cache``: (optional) if defined, the environment key where the Swift MemcacheRing object is stored. Memcached dependencies ====================== In order to use `memcached`_ it is necessary to install the `python-memcached`_ library. If data stored in `memcached`_ will need to be encrypted it is also necessary to install the `cryptography`_ library. These libs are not listed in the requirements.txt file. .. _`memcached`: http://memcached.org/ .. _`python-memcached`: https://pypi.org/project/python-memcached .. _`cryptography`: https://pypi.org/project/cryptography Memcache Protection =================== When using `memcached`_, tokens and authentication responses are stored in the cache as raw data. In the event the cache is compromised, all token and authentication responses will be readable. To mitigate this risk, ``auth_token`` middleware provides an option to authenticate and optionally encrypt the token data stored in the cache. * ``memcache_security_strategy``: (optional) if defined, indicate whether token data should be authenticated or authenticated and encrypted. Acceptable values are ``MAC`` or ``ENCRYPT``. If ``MAC``, token data is authenticated (with HMAC) in the cache. If ``ENCRYPT``, token data is encrypted and authenticated in the cache. If the value is not one of these options or empty, ``auth_token`` will raise an exception on initialization. * ``memcache_secret_key``: (optional, mandatory if ``memcache_security_strategy`` is defined) this string is used for key derivation. If ``memcache_security_strategy`` is defined and ``memcache_secret_key`` is absent, ``auth_token`` will raise an exception on initialization. Exchanging User Information =========================== The middleware expects to find a token representing the user with the header ``X-Auth-Token`` or ``X-Storage-Token``. `X-Storage-Token` is supported for swift/cloud files and for legacy Rackspace use. If the token isn't present and the middleware is configured to not delegate auth responsibility, it will respond to the HTTP request with HTTPUnauthorized, returning the header ``WWW-Authenticate`` with the value `Keystone uri='...'` to indicate where to request a token. The URI returned is configured with the ``www_authenticate_uri`` option. The authentication middleware extends the HTTP request with the header ``X-Identity-Status``. If a request is successfully authenticated, the value is set to `Confirmed`. If the middleware is delegating the auth decision to the service, then the status is set to `Invalid` if the auth request was unsuccessful. An ``X-Service-Token`` header may also be included with a request. If present, and the value of ``X-Auth-Token`` or ``X-Storage-Token`` has not caused the request to be denied, then the middleware will attempt to validate the value of ``X-Service-Token``. If valid, the authentication middleware extends the HTTP request with the header ``X-Service-Identity-Status`` having value `Confirmed` and also extends the request with additional headers representing the identity authenticated and authorised by the token. If ``X-Service-Token`` is present and its value is invalid and the ``delay_auth_decision`` option is True then the value of ``X-Service-Identity-Status`` is set to `Invalid` and no further headers are added. Otherwise if ``X-Service-Token`` is present and its value is invalid then the middleware will respond to the HTTP request with HTTPUnauthorized, regardless of the validity of the ``X-Auth-Token`` or ``X-Storage-Token`` values. Extended the request with additional User Information ----------------------------------------------------- :py:class:`keystonemiddleware.auth_token.AuthProtocol` extends the request with additional information if the user has been authenticated. See the "What we add to the request for use by the OpenStack service" section in :py:mod:`keystonemiddleware.auth_token` for the list of fields set by the auth_token middleware. References ========== .. [PEP-333] pep0333 Phillip J Eby. 'Python Web Server Gateway Interface v1.0.'' http://www.python.org/dev/peps/pep-0333/. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9927511 keystonemiddleware-10.9.0/keystonemiddleware/0000775000175000017500000000000000000000000021422 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/__init__.py0000664000175000017500000000000000000000000023521 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.996751 keystonemiddleware-10.9.0/keystonemiddleware/_common/0000775000175000017500000000000000000000000023051 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/_common/__init__.py0000664000175000017500000000000000000000000025150 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/_common/config.py0000664000175000017500000001323300000000000024672 0ustar00zuulzuul00000000000000# 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 importlib.metadata from oslo_config import cfg from oslo_log import log as logging import pbr from keystonemiddleware import exceptions from keystonemiddleware.i18n import _ CONF = cfg.CONF _NOT_SET = object() _LOG = logging.getLogger(__name__) def _conf_values_type_convert(group_name, all_options, conf): """Convert conf values into correct type.""" if not conf: return {} opts = {} opt_types = {} for group, options in all_options: # only accept paste overrides for the primary group if group != group_name: continue for o in options: type_dest = (getattr(o, 'type', str), o.dest) opt_types[o.dest] = type_dest # Also add the deprecated name with the same type and dest. for d_o in o.deprecated_opts: opt_types[d_o.name] = type_dest break for k, v in conf.items(): dest = k try: # 'here' and '__file__' come from paste.deploy # 'configkey' is added by panko and gnocchi if v is not None and k not in ['here', '__file__', 'configkey']: type_, dest = opt_types[k] v = type_(v) except KeyError: # nosec _LOG.warning( 'The option "%s" is not known to keystonemiddleware', k) except ValueError as e: raise exceptions.ConfigurationError( _('Unable to convert the value of option "%(key)s" into ' 'correct type: %(ex)s') % {'key': k, 'ex': e}) opts[dest] = v return opts class Config(object): def __init__(self, name, group_name, all_options, conf): local_oslo_config = conf.pop('oslo_config_config', None) local_config_project = conf.pop('oslo_config_project', None) local_config_file = conf.pop('oslo_config_file', None) # NOTE(wanghong): If options are set in paste file, all the option # values passed into conf are string type. So, we should convert the # conf value into correct type. self.paste_overrides = _conf_values_type_convert(group_name, all_options, conf) # NOTE(sileht, cdent): If we don't want to use oslo.config global # object there are two options: set "oslo_config_project" in # paste.ini and the middleware will load the configuration with a # local oslo.config object or the caller which instantiates # AuthProtocol can pass in an existing oslo.config as the # value of the "oslo_config_config" key in conf. If both are # set "oslo_config_config" is used. if local_config_project and not local_oslo_config: config_files = [local_config_file] if local_config_file else None local_oslo_config = cfg.ConfigOpts() local_oslo_config([], project=local_config_project, default_config_files=config_files, validate_default_values=True) if local_oslo_config: for group, opts in all_options: local_oslo_config.register_opts(opts, group=group) self.name = name self.oslo_conf_obj = local_oslo_config or cfg.CONF self.group_name = group_name self._user_agent = None def get(self, name, group=_NOT_SET): # try config from paste-deploy first try: return self.paste_overrides[name] except KeyError: if group is _NOT_SET: group = self.group_name return self.oslo_conf_obj[group][name] @property def project(self): """Determine a project name from all available config sources. The sources are checked in the following order: 1. The paste-deploy config for auth_token middleware 2. The keystone_authtoken or base group in the project's config 3. The oslo.config CONF.project property """ try: return self.get('project', group=self.group_name) except cfg.NoSuchOptError: try: # CONF.project will exist only if the service uses # oslo.config. It will only be set when the project # calls CONF(...) and when not set oslo.config oddly # raises a NoSuchOptError exception. return self.oslo_conf_obj.project except cfg.NoSuchOptError: return None @property def user_agent(self): if not self._user_agent: project = self.project or '' if project: try: version = importlib.metadata.version(project) except importlib.metadata.PackageNotFoundError: version = "unknown" project = "%s/%s " % (project, version) self._user_agent = "%skeystonemiddleware.%s/%s" % ( project, self.name, pbr.version.VersionInfo('keystonemiddleware').version_string()) return self._user_agent ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.996751 keystonemiddleware-10.9.0/keystonemiddleware/audit/0000775000175000017500000000000000000000000022530 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/audit/__init__.py0000664000175000017500000001652400000000000024651 0ustar00zuulzuul00000000000000# # 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. """ Build open standard audit information based on incoming requests. AuditMiddleware filter should be placed after keystonemiddleware.auth_token in the pipeline so that it can utilise the information the Identity server provides. """ import copy import functools from oslo_config import cfg from oslo_context import context as oslo_context from oslo_log import log as logging from pycadf import cadftaxonomy as taxonomy from pycadf import cadftype from pycadf import reason from pycadf import reporterstep from pycadf import resource from pycadf import timestamp import webob.dec from keystonemiddleware._common import config from keystonemiddleware.audit import _api from keystonemiddleware.audit import _notifier _LOG = None AUDIT_MIDDLEWARE_GROUP = 'audit_middleware_notifications' _AUDIT_OPTS = [ cfg.BoolOpt('use_oslo_messaging', default=True, help='Indicate whether to use oslo_messaging as the notifier. ' 'If set to False, the local logger will be used as the ' 'notifier. If set to True, the oslo_messaging package ' 'must also be present. Otherwise, the local will be used ' 'instead.'), cfg.StrOpt('driver', help='The Driver to handle sending notifications. Possible ' 'values are messaging, messagingv2, routing, log, test, ' 'noop. If not specified, then value from ' 'oslo_messaging_notifications conf section is used.'), cfg.ListOpt('topics', help='List of AMQP topics used for OpenStack notifications. If' ' not specified, then value from ' ' oslo_messaging_notifications conf section is used.'), cfg.StrOpt('transport_url', secret=True, help='A URL representing messaging driver to use for ' 'notification. If not specified, we fall back to the same ' 'configuration used for RPC.'), ] CONF = cfg.CONF CONF.register_opts(_AUDIT_OPTS, group=AUDIT_MIDDLEWARE_GROUP) def _log_and_ignore_error(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): try: return fn(*args, **kwargs) except Exception as e: _LOG.exception('An exception occurred processing ' 'the API call: %s ', e) return wrapper class AuditMiddleware(object): """Create an audit event based on request/response. The audit middleware takes in various configuration options such as the ability to skip audit of certain requests. The full list of options can be discovered here: https://docs.openstack.org/keystonemiddleware/latest/audit.html """ def __init__(self, app, **conf): self._application = app self._conf = config.Config('audit', AUDIT_MIDDLEWARE_GROUP, list_opts(), conf) global _LOG _LOG = logging.getLogger(conf.get('log_name', __name__)) self._service_name = conf.get('service_name') self._ignore_req_list = [x.upper().strip() for x in conf.get('ignore_req_list', '').split(',')] self._cadf_audit = _api.OpenStackAuditApi(conf.get('audit_map_file'), _LOG) self._notifier = _notifier.create_notifier(self._conf, _LOG) def _create_event(self, req): event = self._cadf_audit._create_event(req) # cache model in request to allow tracking of transistive steps. req.environ['cadf_event'] = event return event @_log_and_ignore_error def _process_request(self, request): self._notifier.notify(request.environ['audit.context'], 'audit.http.request', self._create_event(request).as_dict()) @_log_and_ignore_error def _process_response(self, request, response=None): # NOTE(gordc): handle case where error processing request if 'cadf_event' not in request.environ: self._create_event(request) event = request.environ['cadf_event'] if response: if response.status_int >= 200 and response.status_int < 400: result = taxonomy.OUTCOME_SUCCESS else: result = taxonomy.OUTCOME_FAILURE event.reason = reason.Reason( reasonType='HTTP', reasonCode=str(response.status_int)) else: result = taxonomy.UNKNOWN event.outcome = result event.add_reporterstep( reporterstep.Reporterstep( role=cadftype.REPORTER_ROLE_MODIFIER, reporter=resource.Resource(id='target'), reporterTime=timestamp.get_utc_now())) self._notifier.notify(request.environ['audit.context'], 'audit.http.response', event.as_dict()) @webob.dec.wsgify def __call__(self, req): if req.method in self._ignore_req_list: return req.get_response(self._application) # Cannot use a RequestClass on wsgify above because the `req` object is # a `WebOb.Request` when this method is called so the RequestClass is # ignored by the wsgify wrapper. req.environ['audit.context'] = \ oslo_context.get_admin_context().to_dict() self._process_request(req) try: response = req.get_response(self._application) except Exception: self._process_response(req) raise else: self._process_response(req, response) return response def list_opts(): """Return a list of oslo_config options available in audit middleware. The returned list includes all oslo_config options which may be registered at runtime by the project. 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. :returns: a list of (group_name, opts) tuples """ return [(AUDIT_MIDDLEWARE_GROUP, copy.deepcopy(_AUDIT_OPTS))] def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def audit_filter(app): return AuditMiddleware(app, **conf) return audit_filter # NOTE(jamielennox): Maintained here for public API compatibility. Service = _api.Service AuditMap = _api.AuditMap PycadfAuditApiConfigError = _api.PycadfAuditApiConfigError OpenStackAuditApi = _api.OpenStackAuditApi ClientResource = _api.ClientResource KeystoneCredential = _api.KeystoneCredential ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/audit/_api.py0000664000175000017500000002771200000000000024023 0ustar00zuulzuul00000000000000# 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 configparser import re from oslo_log import log as logging from oslo_serialization import jsonutils from pycadf import cadftaxonomy as taxonomy from pycadf import cadftype from pycadf import credential from pycadf import endpoint from pycadf import eventfactory as factory from pycadf import host from pycadf import identifier from pycadf import resource from pycadf import tag from urllib import parse as urlparse Service = collections.namedtuple('Service', ['id', 'name', 'type', 'admin_endp', 'public_endp', 'private_endp']) AuditMap = collections.namedtuple('AuditMap', ['path_kw', 'custom_actions', 'service_endpoints', 'default_target_endpoint_type']) class PycadfAuditApiConfigError(Exception): """Error raised when pyCADF fails to configure correctly.""" pass class ClientResource(resource.Resource): def __init__(self, project_id=None, request_id=None, global_request_id=None, **kwargs): super(ClientResource, self).__init__(**kwargs) if project_id is not None: self.project_id = project_id if request_id is not None: self.request_id = request_id if global_request_id is not None: self.global_request_id = global_request_id class KeystoneCredential(credential.Credential): def __init__(self, identity_status=None, **kwargs): super(KeystoneCredential, self).__init__(**kwargs) if identity_status is not None: self.identity_status = identity_status class OpenStackAuditApi(object): def __init__(self, cfg_file, log=logging.getLogger(__name__)): """Configure to recognize and map known api paths.""" path_kw = {} custom_actions = {} endpoints = {} default_target_endpoint_type = None if cfg_file: try: map_conf = configparser.ConfigParser() with open(cfg_file) as fh: map_conf.read_file(fh) try: default_target_endpoint_type = map_conf.get( 'DEFAULT', 'target_endpoint_type') except configparser.NoOptionError: # nosec # Ignore the undefined config option, # default_target_endpoint_type remains None which is valid. pass try: custom_actions = dict(map_conf.items('custom_actions')) except configparser.Error: # nosec # custom_actions remains {} which is valid. pass try: path_kw = dict(map_conf.items('path_keywords')) except configparser.Error: # nosec # path_kw remains {} which is valid. pass try: endpoints = dict(map_conf.items('service_endpoints')) except configparser.Error: # nosec # endpoints remains {} which is valid. pass except configparser.ParsingError as err: raise PycadfAuditApiConfigError( 'Error parsing audit map file: %s' % err) self._log = log self._MAP = AuditMap( path_kw=path_kw, custom_actions=custom_actions, service_endpoints=endpoints, default_target_endpoint_type=default_target_endpoint_type) @staticmethod def _clean_path(value): """Clean path if path has json suffix.""" return value[:-5] if value.endswith('.json') else value def get_action(self, req): """Take a given Request, parse url path to calculate action type. Depending on req.method: if POST: - path ends with 'action', read the body and use as action; - path ends with known custom_action, take action from config; - request ends with known path, assume is create action; - request ends with unknown path, assume is update action. if GET: - request ends with known path, assume is list action; - request ends with unknown path, assume is read action. if PUT, assume update action. if DELETE, assume delete action. if HEAD, assume read action. """ path = req.path[:-1] if req.path.endswith('/') else req.path url_ending = self._clean_path(path[path.rfind('/') + 1:]) method = req.method if url_ending + '/' + method.lower() in self._MAP.custom_actions: action = self._MAP.custom_actions[url_ending + '/' + method.lower()] elif url_ending in self._MAP.custom_actions: action = self._MAP.custom_actions[url_ending] elif method == 'POST': if url_ending == 'action': try: if req.json: body_action = list(req.json.keys())[0] action = taxonomy.ACTION_UPDATE + '/' + body_action else: action = taxonomy.ACTION_CREATE except ValueError: action = taxonomy.ACTION_CREATE elif url_ending not in self._MAP.path_kw: action = taxonomy.ACTION_UPDATE else: action = taxonomy.ACTION_CREATE elif method == 'GET': if url_ending in self._MAP.path_kw: action = taxonomy.ACTION_LIST else: action = taxonomy.ACTION_READ elif method == 'PUT' or method == 'PATCH': action = taxonomy.ACTION_UPDATE elif method == 'DELETE': action = taxonomy.ACTION_DELETE elif method == 'HEAD': action = taxonomy.ACTION_READ else: action = taxonomy.UNKNOWN return action def _get_service_info(self, endp): service = Service( type=self._MAP.service_endpoints.get( endp['type'], taxonomy.UNKNOWN), name=endp['name'], id=endp['endpoints'][0].get('id', endp['name']), admin_endp=endpoint.Endpoint( name='admin', url=endp['endpoints'][0].get('adminURL', taxonomy.UNKNOWN)), private_endp=endpoint.Endpoint( name='private', url=endp['endpoints'][0].get('internalURL', taxonomy.UNKNOWN)), public_endp=endpoint.Endpoint( name='public', url=endp['endpoints'][0].get('publicURL', taxonomy.UNKNOWN))) return service def _build_typeURI(self, req, service_type): """Build typeURI of target. Combines service type and corresponding path for greater detail. """ type_uri = '' prev_key = None for key in re.split('/', req.path): key = self._clean_path(key) if key in self._MAP.path_kw: type_uri += '/' + key elif prev_key in self._MAP.path_kw: type_uri += '/' + self._MAP.path_kw[prev_key] prev_key = key return service_type + type_uri def _build_target(self, req, service): """Build target resource.""" target_typeURI = ( self._build_typeURI(req, service.type) if service.type != taxonomy.UNKNOWN else service.type) target = resource.Resource(typeURI=target_typeURI, id=service.id, name=service.name) if service.admin_endp: target.add_address(service.admin_endp) if service.private_endp: target.add_address(service.private_endp) if service.public_endp: target.add_address(service.public_endp) return target def get_target_resource(self, req): """Retrieve target information. If discovery is enabled, target will attempt to retrieve information from service catalog. If not, the information will be taken from given config file. """ service_info = Service(type=taxonomy.UNKNOWN, name=taxonomy.UNKNOWN, id=taxonomy.UNKNOWN, admin_endp=None, private_endp=None, public_endp=None) catalog = {} try: catalog = jsonutils.loads(req.environ['HTTP_X_SERVICE_CATALOG']) except KeyError: self._log.warning( 'Unable to discover target information because ' 'service catalog is missing. Either the incoming ' 'request does not contain an auth token or auth ' 'token does not contain a service catalog. For ' 'the latter, please make sure the ' '"include_service_catalog" property in ' 'auth_token middleware is set to "True"') default_endpoint = None for endp in catalog: if not endp['endpoints']: self._log.warning( 'Skipping service %s as it have no endpoints.', endp['name']) continue endpoint_urls = endp['endpoints'][0] admin_urlparse = urlparse.urlparse( endpoint_urls.get('adminURL', '')) public_urlparse = urlparse.urlparse( endpoint_urls.get('publicURL', '')) req_url = urlparse.urlparse(req.host_url) if req_url.port and (req_url.netloc == admin_urlparse.netloc or req_url.netloc == public_urlparse.netloc): service_info = self._get_service_info(endp) break elif (self._MAP.default_target_endpoint_type and endp['type'] == self._MAP.default_target_endpoint_type): default_endpoint = endp else: if default_endpoint: service_info = self._get_service_info(default_endpoint) return self._build_target(req, service_info) def _create_event(self, req): correlation_id = identifier.generate_uuid() action = self.get_action(req) initiator = ClientResource( typeURI=taxonomy.ACCOUNT_USER, id=req.environ.get('HTTP_X_USER_ID', taxonomy.UNKNOWN), name=req.environ.get('HTTP_X_USER_NAME', taxonomy.UNKNOWN), host=host.Host(address=req.client_addr, agent=req.user_agent), credential=KeystoneCredential( token=req.environ.get('HTTP_X_AUTH_TOKEN', ''), identity_status=req.environ.get('HTTP_X_IDENTITY_STATUS', taxonomy.UNKNOWN)), project_id=req.environ.get('HTTP_X_PROJECT_ID', taxonomy.UNKNOWN), request_id=req.environ.get('openstack.request_id'), global_request_id=req.environ.get('openstack.global_request_id')) target = self.get_target_resource(req) event = factory.EventFactory().new_event( eventType=cadftype.EVENTTYPE_ACTIVITY, outcome=taxonomy.OUTCOME_PENDING, action=action, initiator=initiator, target=target, observer=resource.Resource(id='target')) event.requestPath = req.path_qs event.add_tag(tag.generate_name_value_tag('correlation_id', correlation_id)) return event ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/audit/_notifier.py0000664000175000017500000000340100000000000025056 0ustar00zuulzuul00000000000000# 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 try: import oslo_messaging except ImportError: oslo_messaging = None class _LogNotifier(object): def __init__(self, log): self._log = log def notify(self, context, event_type, payload): self._log.info('Event type: %(event_type)s, Context: %(context)s, ' 'Payload: %(payload)s', {'context': context, 'event_type': event_type, 'payload': payload}) class _MessagingNotifier(object): def __init__(self, notifier): self._notifier = notifier def notify(self, context, event_type, payload): self._notifier.info(context, event_type, payload) def create_notifier(conf, log): if oslo_messaging and conf.get('use_oslo_messaging'): transport = oslo_messaging.get_notification_transport( conf.oslo_conf_obj, url=conf.get('transport_url')) notifier = oslo_messaging.Notifier( transport, os.path.basename(sys.argv[0]), driver=conf.get('driver'), topics=conf.get('topics')) return _MessagingNotifier(notifier) else: return _LogNotifier(log) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154103.996751 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/0000775000175000017500000000000000000000000023563 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/__init__.py0000664000175000017500000011120600000000000025675 0ustar00zuulzuul00000000000000# Copyright 2010-2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. r""" Token-based Authentication Middleware. This WSGI component: * Verifies that incoming client requests have valid tokens by validating tokens with the auth service. * Rejects unauthenticated requests unless the auth_token middleware is in ``delay_auth_decision`` mode, which means the final decision is delegated to the downstream WSGI component (usually the OpenStack service). * Collects and forwards identity information based on a valid token such as user name, domain, project, etc. Refer to: https://docs.openstack.org/keystonemiddleware/latest/\ middlewarearchitecture.html Headers ------- The auth_token middleware uses headers sent in by the client on the request and sets headers and environment variables for the downstream WSGI component. Coming in from initial call from client or customer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HTTP_X_AUTH_TOKEN The client token being passed in. HTTP_X_SERVICE_TOKEN A service token being passed in. Used for communication between components ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ WWW-Authenticate HTTP header returned to a user indicating which endpoint to use to retrieve a new token. What auth_token adds to the request for use by the OpenStack service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When using composite authentication (a user and service token are present) additional service headers relating to the service user will be added. They take the same form as the standard headers but add ``_SERVICE_``. These headers will not exist in the environment if no service token is present. HTTP_X_IDENTITY_STATUS, HTTP_X_SERVICE_IDENTITY_STATUS Will be set to either ``Confirmed`` or ``Invalid``. The underlying service will only see a value of 'Invalid' if the middleware is configured to run in ``delay_auth_decision`` mode. As with all such headers, ``HTTP_X_SERVICE_IDENTITY_STATUS`` will only exist in the environment if a service token is presented. This is different than ``HTTP_X_IDENTITY_STATUS`` which is always set even if no user token is presented. This allows the underlying service to determine if a denial should use ``401 Unauthenticated`` or ``403 Forbidden``. HTTP_OPENSTACK_SYSTEM_SCOPE A string relaying system information about the token's scope. This attribute is only present if the token is system-scoped. The string ``all`` means the token is scoped to the entire deployment system. HTTP_X_DOMAIN_ID, HTTP_X_SERVICE_DOMAIN_ID Identity service managed unique identifier, string. Only present if this is a domain-scoped token. HTTP_X_DOMAIN_NAME, HTTP_X_SERVICE_DOMAIN_NAME Unique domain name, string. Only present if this is a domain-scoped token. HTTP_X_PROJECT_ID, HTTP_X_SERVICE_PROJECT_ID Identity service managed unique identifier, string. Only present if this is a project-scoped token. HTTP_X_PROJECT_NAME, HTTP_X_SERVICE_PROJECT_NAME Project name, unique within owning domain, string. Only present if this is a project-scoped token. HTTP_X_PROJECT_DOMAIN_ID, HTTP_X_SERVICE_PROJECT_DOMAIN_ID Identity service managed unique identifier of owning domain of project, string. Only present if this is a project-scoped v3 token. If this variable is set, this indicates that the PROJECT_NAME can only be assumed to be unique within this domain. HTTP_X_PROJECT_DOMAIN_NAME, HTTP_X_SERVICE_PROJECT_DOMAIN_NAME Name of owning domain of project, string. Only present if this is a project-scoped v3 token. If this variable is set, this indicates that the PROJECT_NAME can only be assumed to be unique within this domain. HTTP_X_USER_ID, HTTP_X_SERVICE_USER_ID Identity-service managed unique identifier, string. HTTP_X_USER_NAME, HTTP_X_SERVICE_USER_NAME User identifier, unique within owning domain, string. HTTP_X_USER_DOMAIN_ID, HTTP_X_SERVICE_USER_DOMAIN_ID Identity service managed unique identifier of owning domain of user, string. If this variable is set, this indicates that the USER_NAME can only be assumed to be unique within this domain. HTTP_X_USER_DOMAIN_NAME, HTTP_X_SERVICE_USER_DOMAIN_NAME Name of owning domain of user, string. If this variable is set, this indicates that the USER_NAME can only be assumed to be unique within this domain. HTTP_X_ROLES, HTTP_X_SERVICE_ROLES Comma delimited list of case-sensitive role names. HTTP_X_IS_ADMIN_PROJECT The string value 'True' or 'False' representing whether the user's token is scoped to the admin project. As historically there was no admin project this will default to True for tokens without this information to be backwards with existing policy files. HTTP_X_SERVICE_CATALOG service catalog (optional, JSON string). For compatibility reasons this catalog will always be in the V2 catalog format even if it is a v3 token. .. note:: This is an exception in that it contains 'SERVICE' but relates to a user token, not a service token. The existing user's catalog can be very large; it was decided not to present a catalog relating to the service token to avoid using more HTTP header space. HTTP_X_TENANT_ID *Deprecated* in favor of HTTP_X_PROJECT_ID. Identity service managed unique identifier, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_ID. HTTP_X_TENANT_NAME *Deprecated* in favor of HTTP_X_PROJECT_NAME. Project identifier, unique within owning domain, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_NAME. HTTP_X_TENANT *Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME. Identity server-assigned unique identifier, string. For v3 tokens, this will be set to the same value as HTTP_X_PROJECT_ID. HTTP_X_USER *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME. User name, unique within owning domain, string. HTTP_X_ROLE *Deprecated* in favor of HTTP_X_ROLES. Will contain the same values as HTTP_X_ROLES. Environment Variables ^^^^^^^^^^^^^^^^^^^^^ These variables are set in the request environment for use by the downstream WSGI component. keystone.token_info Information about the token discovered in the process of validation. This may include extended information returned by the token validation call, as well as basic information about the project and user. keystone.token_auth A keystoneauth1 auth plugin that may be used with a :py:class:`keystoneauth1.session.Session`. This plugin will load the authentication data provided to auth_token middleware. Configuration ------------- auth_token middleware configuration can be in the main application's configuration file, e.g. in ``nova.conf``: .. code-block:: ini [keystone_authtoken] auth_plugin = password auth_url = http://keystone:5000/ username = nova user_domain_id = default password = whyarewestillusingpasswords project_name = service project_domain_id = default Configuration can also be in the ``api-paste.ini`` file with the same options, but this is discouraged. Swift ----- When deploy auth_token middleware with Swift, user may elect to use Swift memcache instead of the local auth_token memcache. Swift memcache is passed in from the request environment and it's identified by the ``swift.cache`` key. However it could be different, depending on deployment. To use Swift memcache, you must set the ``cache`` option to the environment key where the Swift cache object is stored. """ import copy import re from keystoneauth1 import access from keystoneauth1 import adapter from keystoneauth1 import discover from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import loading from keystoneauth1.loading import session as session_loading import oslo_cache from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils import webob.dec from keystonemiddleware._common import config from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _base from keystonemiddleware.auth_token import _cache from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.auth_token import _identity from keystonemiddleware.auth_token import _opts from keystonemiddleware.auth_token import _request from keystonemiddleware.auth_token import _user_plugin from keystonemiddleware.i18n import _ _LOG = logging.getLogger(__name__) oslo_cache.configure(cfg.CONF) AUTH_TOKEN_OPTS = [ (_base.AUTHTOKEN_GROUP, _opts._OPTS + _auth.OPTS + loading.get_auth_common_conf_options()) ] def list_opts(): """Return a list of oslo_config options available in auth_token middleware. The returned list includes all oslo_config options which may be registered at runtime by the project. 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. NOTE: This function is no longer used for oslo_config sample generation. Some services rely on this function for listing ALL (including deprecated) options and registering them into their own config objects which we do not want for sample config files. See: :py:func:`keystonemiddleware.auth_token._opts.list_opts` for sample config files. :returns: a list of (group_name, opts) tuples """ return [(g, copy.deepcopy(o)) for g, o in AUTH_TOKEN_OPTS] def _path_matches(request_path, path_pattern): # The fnmatch module doesn't provide the ability to match * versus **, # so convert to regex. token_regex = (r'(?P{[^}]*})|' # {tag} # nosec r'(?P\*(?=$|[^\*]))|' # * r'(?P\*\*)|' # ** r'(?P[^{}\*])') # anything else path_regex = '' for match in re.finditer(token_regex, path_pattern): token = match.groupdict() if token['tag'] or token['wild']: path_regex += r'[^\/]+' if token['rec_wild']: path_regex += '.*' if token['literal']: path_regex += token['literal'] path_regex = r'^%s$' % path_regex return re.match(path_regex, request_path) class _BIND_MODE(object): DISABLED = 'disabled' PERMISSIVE = 'permissive' STRICT = 'strict' REQUIRED = 'required' KERBEROS = 'kerberos' class BaseAuthProtocol(object): """A base class for AuthProtocol token checking implementations. :param Callable app: The next application to call after middleware. :param logging.Logger log: The logging object to use for output. By default it will use a logger in the keystonemiddleware.auth_token namespace. :param str enforce_token_bind: The style of token binding enforcement to perform. """ def __init__(self, app, log=_LOG, enforce_token_bind=_BIND_MODE.PERMISSIVE, service_token_roles=None, service_token_roles_required=False, service_type=None): self.log = log self._app = app self._enforce_token_bind = enforce_token_bind self._service_token_roles = set(service_token_roles or []) self._service_token_roles_required = service_token_roles_required self._service_token_warning_emitted = False self._service_type = service_type @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest) def __call__(self, req): """Handle incoming request.""" response = self.process_request(req) if response: return response response = req.get_response(self._app) return self.process_response(response) def process_request(self, request): """Process request. If this method returns a value then that value will be used as the response. The next application down the stack will not be executed and process_response will not be called. Otherwise, the next application down the stack will be executed and process_response will be called with the generated response. By default this method does not return a value. :param request: Incoming request :type request: _request.AuthTokenRequest """ user_auth_ref = None serv_auth_ref = None allow_expired = False if request.service_token: self.log.debug('Authenticating service token') try: _, serv_auth_ref = self._do_fetch_token(request.service_token) self._validate_token(serv_auth_ref) self._confirm_token_bind(serv_auth_ref, request) except ksm_exceptions.InvalidToken: self.log.info('Invalid service token') request.service_token_valid = False else: # FIXME(jamielennox): The new behaviour for service tokens is # that they have to pass the policy check to be allowed. # Previously any token was accepted here. For now we will # continue to mark service tokens as valid if they are valid # but we will only allow service role tokens to do # allow_expired. In future we should reject any token that # isn't a service token here. role_names = set(serv_auth_ref.role_names) check = self._service_token_roles.intersection(role_names) role_check_passed = bool(check) # if service_token_roles_required then the service token is # only valid if the roles check out. Otherwise at this point it # is true because keystone has already validated it. if self._service_token_roles_required: request.service_token_valid = role_check_passed else: if not self._service_token_warning_emitted: self.log.warning('A valid token was submitted as ' 'a service token, but it was not ' 'a valid service token. This is ' 'incorrect but backwards ' 'compatible behaviour. This will ' 'be removed in future releases.') # prevent log spam on every single request self._service_token_warning_emitted = True request.service_token_valid = True # allow_expired always requires passing the role check. allow_expired = role_check_passed if request.user_token: self.log.debug('Authenticating user token') try: data, user_auth_ref = self._do_fetch_token( request.user_token, allow_expired=allow_expired) self._validate_token(user_auth_ref, allow_expired=allow_expired) if user_auth_ref.version != 'v2.0': self.validate_allowed_request(request, data['token']) if not request.service_token: self._confirm_token_bind(user_auth_ref, request) except ksm_exceptions.InvalidToken: self.log.info('Invalid user token') request.user_token_valid = False else: request.user_token_valid = True request.token_info = data request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref, serv_auth_ref) def _validate_token(self, auth_ref, allow_expired=False): """Perform the validation steps on the token. :param auth_ref: The token data :type auth_ref: keystoneauth1.access.AccessInfo :raises exc.InvalidToken: if token is rejected """ # 0 seconds of validity means it is invalid right now if (not allow_expired) and auth_ref.will_expire_soon(stale_duration=0): raise ksm_exceptions.InvalidToken(_('Token authorization failed')) def _do_fetch_token(self, token, **kwargs): """Helper method to fetch a token and convert it into an AccessInfo.""" # NOTE(edmondsw): strip the token to remove any whitespace that may # have been passed along in the header per bug 1689468 token = token.strip() data = self.fetch_token(token, **kwargs) try: return data, access.create(body=data, auth_token=token) except Exception: self.log.warning('Invalid token contents.', exc_info=True) raise ksm_exceptions.InvalidToken(_('Token authorization failed')) def fetch_token(self, token, **kwargs): """Fetch the token data based on the value in the header. Retrieve the data associated with the token value that was in the header. This can be from PKI, contacting the identity server or whatever is required. :param str token: The token present in the request header. :param dict kwargs: Additional keyword arguments may be passed through here to support new features. If an implementation is not aware of how to use these arguments it should ignore them. :raises exc.InvalidToken: if token is invalid. :returns: The token data :rtype: dict """ raise NotImplementedError() def process_response(self, response): """Do whatever you'd like to the response. By default the response is returned unmodified. :param response: Response object :type response: ._request._AuthTokenResponse """ return response def _invalid_user_token(self, msg=False): # NOTE(jamielennox): use False as the default so that None is valid if msg is False: msg = _('Token authorization failed') raise ksm_exceptions.InvalidToken(msg) def _confirm_token_bind(self, auth_ref, req): if self._enforce_token_bind == _BIND_MODE.DISABLED: return # permissive and strict modes don't require there to be a bind permissive = self._enforce_token_bind in (_BIND_MODE.PERMISSIVE, _BIND_MODE.STRICT) if not auth_ref.bind: if permissive: # no bind provided and none required return else: self.log.info('No bind information present in token.') self._invalid_user_token() # get the named mode if bind_mode is not one of the predefined if permissive or self._enforce_token_bind == _BIND_MODE.REQUIRED: name = None else: name = self._enforce_token_bind if name and name not in auth_ref.bind: self.log.info('Named bind mode %s not in bind information', name) self._invalid_user_token() for bind_type, identifier in auth_ref.bind.items(): if bind_type == _BIND_MODE.KERBEROS: if req.auth_type != 'negotiate': self.log.info('Kerberos credentials required and ' 'not present.') self._invalid_user_token() if req.remote_user != identifier: self.log.info('Kerberos credentials do not match ' 'those in bind.') self._invalid_user_token() self.log.debug('Kerberos bind authentication successful.') elif self._enforce_token_bind == _BIND_MODE.PERMISSIVE: self.log.debug('Ignoring Unknown bind for permissive mode: ' '%(bind_type)s: %(identifier)s.', {'bind_type': bind_type, 'identifier': identifier}) else: self.log.info( 'Couldn`t verify unknown bind: %(bind_type)s: ' '%(identifier)s.', {'bind_type': bind_type, 'identifier': identifier}) self._invalid_user_token() def validate_allowed_request(self, request, token): self.log.debug("Validating token access rules against request") app_cred = token.get('application_credential') if not app_cred: return access_rules = app_cred.get('access_rules') if access_rules is None: return if hasattr(self, '_conf'): my_service_type = self._conf.get('service_type') else: my_service_type = self._service_type if not my_service_type: self.log.warning('Cannot validate request with restricted' ' access rules. Set service_type in' ' [keystone_authtoken] to allow access rule' ' validation.') raise ksm_exceptions.InvalidToken(_('Token authorization failed')) # token can always be validated regardless of access rules if (my_service_type == 'identity' and request.method == 'GET' and request.path.endswith('/v3/auth/tokens')): return catalog = token['catalog'] # validate service type is in catalog catalog_svcs = [s for s in catalog if s['type'] == my_service_type] if len(catalog_svcs) == 0: self.log.warning('Cannot validate request with restricted' ' access rules. service_type in' ' [keystone_authtoken] is not a valid service' ' type in the catalog.') raise ksm_exceptions.InvalidToken(_('Token authorization failed')) if request.service_token: # The request may not match an allowed request, but the presence # of the service token indicates this is a chain of requests and # hence this request was not user-facing return for access_rule in access_rules: method = access_rule['method'] path = access_rule['path'] service = access_rule['service'] if request.method == method and \ service == my_service_type and \ _path_matches(request.path, path): return raise ksm_exceptions.InvalidToken(_('Token authorization failed')) class AuthProtocol(BaseAuthProtocol): """Middleware that handles authenticating client calls.""" def __init__(self, app, conf): log = logging.getLogger(conf.get('log_name', __name__)) log.info('Starting Keystone auth_token middleware') self._conf = config.Config('auth_token', _base.AUTHTOKEN_GROUP, list_opts(), conf) if self._conf.oslo_conf_obj is not cfg.CONF: oslo_cache.configure(self._conf.oslo_conf_obj) token_roles_required = self._conf.get('service_token_roles_required') if not token_roles_required: log.warning('AuthToken middleware is set with ' 'keystone_authtoken.service_token_roles_required ' 'set to False. This is backwards compatible but ' 'deprecated behaviour. Please set this to True.') super(AuthProtocol, self).__init__( app, log=log, enforce_token_bind=self._conf.get('enforce_token_bind'), service_token_roles=self._conf.get('service_token_roles'), service_token_roles_required=token_roles_required) # delay_auth_decision means we still allow unauthenticated requests # through and we let the downstream service make the final decision self._delay_auth_decision = self._conf.get('delay_auth_decision') self._include_service_catalog = self._conf.get( 'include_service_catalog') self._interface = self._conf.get('interface') self._auth = self._create_auth_plugin() self._session = self._create_session() self._identity_server = self._create_identity_server() self._www_authenticate_uri = self._conf.get('www_authenticate_uri') if not self._www_authenticate_uri: self._www_authenticate_uri = self._conf.get('auth_uri') if not self._www_authenticate_uri: self.log.warning( 'Configuring www_authenticate_uri to point to the public ' 'identity endpoint is required; clients may not be able to ' 'authenticate against an admin endpoint') # FIXME(dolph): drop support for this fallback behavior as # documented in bug 1207517. self._www_authenticate_uri = \ self._identity_server.www_authenticate_uri self._token_cache = self._token_cache_factory() def process_request(self, request): """Process request. Evaluate the headers in a request and attempt to authenticate the request. If authenticated then additional headers are added to the request for use by applications. If not authenticated the request will be rejected or marked unauthenticated depending on configuration. """ request.remove_auth_headers() self._token_cache.initialize(request.environ) resp = super(AuthProtocol, self).process_request(request) if resp: return resp if not request.user_token: # if no user token is present then that's an invalid request request.user_token_valid = False # NOTE(jamielennox): The service status is allowed to be missing if a # service token is not passed. If the service status is missing that's # a valid request. We should find a better way to expose this from the # request object. user_status = request.user_token and request.user_token_valid service_status = request.headers.get('X-Service-Identity-Status', 'Confirmed') if not (user_status and service_status == 'Confirmed'): if self._delay_auth_decision: self.log.debug('Deferring reject downstream') else: self.log.info('Rejecting request') message = _('The request you have made requires ' 'authentication.') body = {'error': { 'code': 401, 'title': 'Unauthorized', 'message': message, }} raise webob.exc.HTTPUnauthorized( body=jsonutils.dumps(body), headers=self._reject_auth_headers, charset='UTF-8', content_type='application/json') if request.user_token_valid: request.set_user_headers(request.token_auth.user) if self._include_service_catalog: request.set_service_catalog_headers(request.token_auth.user) if request.token_auth: request.token_auth._auth = self._auth request.token_auth._session = self._session if request.service_token and request.service_token_valid: request.set_service_headers(request.token_auth.service) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('Received request from %s', request.token_auth._log_format) def process_response(self, response): """Process Response. Add ``WWW-Authenticate`` headers to requests that failed with ``401 Unauthenticated`` so users know where to authenticate for future requests. """ if response.status_int == 401: response.headers.extend(self._reject_auth_headers) return response @property def _reject_auth_headers(self): header_val = 'Keystone uri="%s"' % self._www_authenticate_uri return [('WWW-Authenticate', header_val)] def fetch_token(self, token, allow_expired=False): """Retrieve a token from either a PKI bundle or the identity server. :param str token: token id :raises exc.InvalidToken: if token is rejected """ data = None try: cached = self._token_cache.get(token) if cached: # NOTE(jamielennox): Cached values used to be stored as a tuple # of data and expiry time. They no longer are but we have to # allow some time to transition the old format so if it's a # tuple just use the data. if len(cached) == 2: cached = cached[0] data = cached else: data = self._identity_server.verify_token( token, allow_expired=allow_expired) self._token_cache.set(token, data) except (ksa_exceptions.ConnectFailure, ksa_exceptions.DiscoveryFailure, ksa_exceptions.RequestTimeout, ksm_exceptions.ServiceError) as e: self.log.critical('Unable to validate token: %s', e) if self._delay_auth_decision: self.log.debug('Keystone unavailable; marking token as ' 'invalid and deferring auth decision.') raise ksm_exceptions.InvalidToken( 'Keystone unavailable: %s' % e) raise webob.exc.HTTPServiceUnavailable( 'The Keystone service is temporarily unavailable.') except ksm_exceptions.InvalidToken: self.log.debug('Token validation failure.', exc_info=True) self.log.warning('Authorization failed for token') raise except ksa_exceptions.EndpointNotFound: # Invalidate auth in adapter for identity endpoint update self._identity_server.invalidate() raise return data def _validate_token(self, auth_ref, **kwargs): super(AuthProtocol, self)._validate_token(auth_ref, **kwargs) if auth_ref.version == 'v2.0' and not auth_ref.project_id: msg = _('Unable to determine service tenancy.') raise ksm_exceptions.InvalidToken(msg) def _create_auth_plugin(self): # NOTE(jamielennox): Ideally this would use load_from_conf_options # however that is not possible because we have to support the override # pattern we use in _conf.get. This function therefore does a manual # version of load_from_conf_options with the fallback plugin inline. group = self._conf.get('auth_section') or _base.AUTHTOKEN_GROUP # NOTE(jamielennox): auth_plugin was deprecated to auth_type. _conf.get # doesn't handle that deprecation in the case of conf dict options so # we have to manually check the value plugin_name = (self._conf.get('auth_type', group=group) or self._conf.paste_overrides.get('auth_plugin')) if not plugin_name: return _auth.AuthTokenPlugin( log=self.log, auth_admin_prefix=self._conf.get('auth_admin_prefix', group=group), auth_host=self._conf.get('auth_host', group=group), auth_port=self._conf.get('auth_port', group=group), auth_protocol=self._conf.get('auth_protocol', group=group), identity_uri=self._conf.get('identity_uri', group=group), admin_token=self._conf.get('admin_token', group=group), admin_user=self._conf.get('admin_user', group=group), admin_password=self._conf.get('admin_password', group=group), admin_tenant_name=self._conf.get('admin_tenant_name', group=group) ) # Plugin option registration is normally done as part of the load_from # function rather than the register function so copy here. plugin_loader = loading.get_plugin_loader(plugin_name) plugin_opts = loading.get_auth_plugin_conf_options(plugin_loader) self._conf.oslo_conf_obj.register_opts(plugin_opts, group=group) getter = lambda opt: self._conf.get(opt.dest, group=group) # noqa return plugin_loader.load_from_options_getter(getter) def _create_session(self, **kwargs): # NOTE(jamielennox): Loading Session here should be exactly the # same as calling Session.load_from_conf_options(CONF, GROUP) # however we can't do that because we have to use _conf.get to # support the paste.ini options. kwargs.setdefault('cert', self._conf.get('certfile')) kwargs.setdefault('key', self._conf.get('keyfile')) kwargs.setdefault('cacert', self._conf.get('cafile')) kwargs.setdefault('insecure', self._conf.get('insecure')) kwargs.setdefault('timeout', self._conf.get('http_connect_timeout')) kwargs.setdefault('user_agent', self._conf.user_agent) return session_loading.Session().load_from_options(**kwargs) def _create_identity_server(self): adap = adapter.Adapter( self._session, auth=self._auth, service_type='identity', interface=self._interface, region_name=self._conf.get('region_name'), connect_retries=self._conf.get('http_request_max_retries')) auth_version = self._conf.get('auth_version') if auth_version is not None: auth_version = discover.normalize_version_number(auth_version) return _identity.IdentityServer( self.log, adap, include_service_catalog=self._include_service_catalog, requested_auth_version=auth_version, requested_auth_interface=self._interface) def _create_oslo_cache(self): # having this as a function makes test mocking easier region = oslo_cache.create_region() oslo_cache.configure_cache_region(self._conf.oslo_conf_obj, region) return region def _token_cache_factory(self): security_strategy = self._conf.get('memcache_security_strategy') cache_kwargs = dict( cache_time=int(self._conf.get('token_cache_time')), env_cache_name=self._conf.get('cache'), memcached_servers=self._conf.get('memcached_servers'), use_advanced_pool=self._conf.get('memcache_use_advanced_pool'), dead_retry=self._conf.get('memcache_pool_dead_retry'), maxsize=self._conf.get('memcache_pool_maxsize'), unused_timeout=self._conf.get('memcache_pool_unused_timeout'), conn_get_timeout=self._conf.get('memcache_pool_conn_get_timeout'), socket_timeout=self._conf.get('memcache_pool_socket_timeout'), sasl_enabled=self._conf.get('memcache_sasl_enabled'), username=self._conf.get('memcache_username'), password=self._conf.get('memcache_password'), ) if security_strategy.lower() != 'none': secret_key = self._conf.get('memcache_secret_key') return _cache.SecureTokenCache(self.log, security_strategy, secret_key, **cache_kwargs) else: return _cache.TokenCache(self.log, **cache_kwargs) def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return AuthProtocol(app, conf) return auth_filter def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return AuthProtocol(None, conf) # NOTE(jamielennox): Maintained here for public API compatibility. InvalidToken = ksm_exceptions.InvalidToken ServiceError = ksm_exceptions.ServiceError ConfigurationError = ksm_exceptions.ConfigurationError ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_auth.py0000664000175000017500000001733100000000000025242 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1 import discover from keystoneauth1.identity import v2 from keystoneauth1 import plugin from keystoneauth1 import token_endpoint from oslo_config import cfg from oslo_utils import netutils from keystonemiddleware.auth_token import _base from keystonemiddleware.i18n import _ class AuthTokenPlugin(plugin.BaseAuthPlugin): def __init__(self, auth_host, auth_port, auth_protocol, auth_admin_prefix, admin_user, admin_password, admin_tenant_name, admin_token, identity_uri, log): log.warning( "Use of the auth_admin_prefix, auth_host, auth_port, " "auth_protocol, identity_uri, admin_token, admin_user, " "admin_password, and admin_tenant_name configuration options was " "deprecated in the Mitaka release in favor of an auth_plugin and " "its related options. This class may be removed in a future " "release.") # NOTE(jamielennox): it does appear here that our default arguments # are backwards. We need to do it this way so that we can handle the # same deprecation strategy for CONF and the conf variable. if not identity_uri: log.warning('Configuring admin URI using auth fragments was ' 'deprecated in the Kilo release, and will be ' 'removed in the Newton release, ' "use 'identity_uri' instead.") identity_uri = '%s://%s:%s' % (auth_protocol, netutils.escape_ipv6(auth_host), auth_port) if auth_admin_prefix: identity_uri = '%s/%s' % (identity_uri, auth_admin_prefix.strip('/')) self._identity_uri = identity_uri.rstrip('/') # FIXME(jamielennox): Yes. This is wrong. We should be determining the # plugin to use based on a combination of discovery and inputs. Much # of this can be changed when we get keystoneclient 0.10. For now this # hardcoded path is EXACTLY the same as the original auth_token did. auth_url = '%s/v2.0' % self._identity_uri if admin_token: log.warning( "The admin_token option in auth_token middleware was " "deprecated in the Kilo release, and will be removed in the " "Newton release, use admin_user and admin_password instead.") self._plugin = token_endpoint.Token(auth_url, admin_token) else: self._plugin = v2.Password(auth_url, username=admin_user, password=admin_password, tenant_name=admin_tenant_name) self._LOG = log self._discover = None def get_token(self, *args, **kwargs): return self._plugin.get_token(*args, **kwargs) def get_endpoint(self, session, interface=None, version=None, **kwargs): """Return an endpoint for the client. There are no required keyword arguments to ``get_endpoint`` as a plugin implementation should use best effort with the information available to determine the endpoint. :param session: The session object that the auth_plugin belongs to. :type session: keystoneauth1.session.Session :param version: The version number required for this endpoint. :type version: tuple or str :param str interface: what visibility the endpoint should have. :returns: The base URL that will be used to talk to the required service or None if not available. :rtype: string """ if interface == plugin.AUTH_INTERFACE: return self._identity_uri if not version: # NOTE(jamielennox): This plugin can only be used within auth_token # and auth_token will always provide version= with requests. return None if not self._discover: self._discover = discover.Discover(session, url=self._identity_uri, authenticated=False) if not self._discover.url_for(version): # NOTE(jamielennox): The requested version is not supported by the # identity server. return None # NOTE(jamielennox): for backwards compatibility here we don't # actually use the URL from discovery we hack it up instead. :( # NOTE(blk-u): Normalizing the version is a workaround for bug 1450272. # This can be removed once that's fixed. Also fix the docstring for the # version parameter to be just "tuple". version = discover.normalize_version_number(version) if discover.version_match((2, 0), version): return '%s/v2.0' % self._identity_uri elif discover.version_match((3, 0), version): return '%s/v3' % self._identity_uri # NOTE(jamielennox): This plugin will only get called from auth_token # middleware. The middleware should never request a version that the # plugin doesn't know how to handle. msg = _('Invalid version asked for in auth_token plugin') raise NotImplementedError(msg) def invalidate(self): return self._plugin.invalidate() OPTS = [ cfg.StrOpt('auth_admin_prefix', default='', help='Prefix to prepend at the beginning of the path. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('auth_host', default='127.0.0.1', help='Host providing the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.IntOpt('auth_port', default=35357, help='Port of the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('auth_protocol', default='https', choices=('http', 'https'), help='Protocol of the admin Identity API endpoint. ' 'Deprecated, use identity_uri.'), cfg.StrOpt('identity_uri', help='Complete admin Identity API endpoint. This ' 'should specify the unversioned root endpoint ' 'e.g. https://localhost:35357/'), cfg.StrOpt('admin_token', secret=True, help='This option is deprecated and may be removed in ' 'a future release. Single shared secret with the ' 'Keystone configuration used for bootstrapping a ' 'Keystone installation, or otherwise bypassing ' 'the normal authentication process. This option ' 'should not be used, use `admin_user` and ' '`admin_password` instead.'), cfg.StrOpt('admin_user', help='Service username.'), cfg.StrOpt('admin_password', secret=True, help='Service user password.'), cfg.StrOpt('admin_tenant_name', default='admin', help='Service tenant name.'), ] cfg.CONF.register_opts(OPTS, group=_base.AUTHTOKEN_GROUP) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_base.py0000664000175000017500000000111100000000000025200 0ustar00zuulzuul00000000000000# 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. AUTHTOKEN_GROUP = 'keystone_authtoken' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_cache.py0000664000175000017500000003303700000000000025345 0ustar00zuulzuul00000000000000# 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 contextlib import hashlib from oslo_serialization import jsonutils from oslo_utils import timeutils from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt from keystonemiddleware.i18n import _ def _hash_key(key): """Turn a set of arguments into a SHA256 hash. Using a known-length cache key is important to ensure that memcache maximum key length is not exceeded causing failures to validate. """ if isinstance(key, str): # NOTE(morganfainberg): Ensure we are always working with a bytes # type required for the hasher. In python 2.7 it is possible to # get a text_type (unicode). In python 3.4 all strings are # text_type and not bytes by default. This encode coerces the # text_type to the appropriate bytes values. key = key.encode('utf-8') return hashlib.sha256(key).hexdigest() class _EnvCachePool(object): """A cache pool that has been passed through ENV variables.""" def __init__(self, cache): self._environment_cache = cache @contextlib.contextmanager def reserve(self): """Context manager to manage a pooled cache reference.""" yield self._environment_cache class _CachePool(list): """A lazy pool of cache references.""" def __init__(self, memcached_servers, log, arguments): self._memcached_servers = memcached_servers self._sasl_enabled = arguments.get("sasl_enabled", False) self._username = arguments.get("username", None) self._password = arguments.get("password", None) if not self._memcached_servers: log.warning( "Using the in-process token cache is deprecated as of the " "4.2.0 release and may be removed in the 5.0.0 release or " "the 'O' development cycle. The in-process cache causes " "inconsistent results and high memory usage. When the feature " "is removed the auth_token middleware will not cache tokens " "by default which may result in performance issues. It is " "recommended to use memcache for the auth_token token cache " "by setting the memcached_servers option.") @contextlib.contextmanager def reserve(self): """Context manager to manage a pooled cache reference.""" try: c = self.pop() except IndexError: # the pool is empty, so we need to create a new client if self._memcached_servers: if self._sasl_enabled: import bmemcached c = bmemcached.Client(self._memcached_servers, self._username, self._password) else: import memcache c = memcache.Client(self._memcached_servers, debug=0) else: c = _FakeClient() try: yield c finally: self.append(c) class _MemcacheClientPool(object): """An advanced memcached client pool that is eventlet safe.""" def __init__(self, memcache_servers, arguments, **kwargs): # NOTE(sileht): This will import python-memcached and # python-binary-memcached , we don't want it as hard # dependency, so lazy load it. self._sasl_enabled = arguments.pop("sasl_enabled", False) if self._sasl_enabled: from oslo_cache import _bmemcache_pool self._pool = _bmemcache_pool.BMemcacheClientPool(memcache_servers, arguments, **kwargs) else: from oslo_cache import _memcache_pool arguments.pop("username", None) arguments.pop("password", None) self._pool = _memcache_pool.MemcacheClientPool(memcache_servers, arguments, **kwargs) @contextlib.contextmanager def reserve(self): # NOTE(morgan): We must use "acquire" if we want all the added context # manager logic that places the connection back into the pool at the # end of it's use. with self._pool.acquire() as client: yield client class TokenCache(object): """Encapsulates the auth_token token cache functionality. auth_token caches tokens that it's seen so that when a token is re-used the middleware doesn't have to do a more expensive operation (like going to the identity server) to validate the token. initialize() must be called before calling the other methods. Store data in the cache store. Check if a token is in the cache and retrieve it using get(). """ _CACHE_KEY_TEMPLATE = 'tokens/%s' def __init__(self, log, cache_time=None, env_cache_name=None, memcached_servers=None, use_advanced_pool=True, dead_retry=None, socket_timeout=None, **kwargs): self._LOG = log self._cache_time = cache_time self._env_cache_name = env_cache_name self._memcached_servers = memcached_servers self._use_advanced_pool = use_advanced_pool self._arguments = { 'dead_retry': dead_retry, 'socket_timeout': socket_timeout, 'sasl_enabled': kwargs.pop("sasl_enabled", False), 'username': kwargs.pop("username", None), 'password': kwargs.pop("password", None) } self._memcache_pool_options = kwargs self._cache_pool = None self._initialized = False def _get_cache_pool(self, cache): if cache: return _EnvCachePool(cache) elif self._use_advanced_pool and self._memcached_servers: return _MemcacheClientPool(self._memcached_servers, self._arguments, **self._memcache_pool_options) else: if not self._use_advanced_pool: self._LOG.warning( "Using the eventlet-unsafe cache pool is deprecated." "It is recommended to use eventlet-safe cache pool" "implementation from oslo.cache. This can be enabled" "through config option memcache_use_advanced_pool = True") return _CachePool(self._memcached_servers, self._LOG, self._arguments) def initialize(self, env): if self._initialized: return self._cache_pool = self._get_cache_pool(env.get(self._env_cache_name)) self._initialized = True def _get_cache_key(self, token_id): """Get a unique key for this token id. Turn the token_id into something that can uniquely identify that token in a key value store. As this is generally the first function called in a key lookup this function also returns a context object. This context object is not modified or used by the Cache object but is passed back on subsequent functions so that decryption or other data can be shared throughout a cache lookup. :param str token_id: The unique token id. :returns: A tuple of a string key and an implementation specific context object """ # NOTE(jamielennox): in the basic implementation there is no need for # a context so just pass None as it will only get passed back later. unused_context = None return self._CACHE_KEY_TEMPLATE % _hash_key(token_id), unused_context def _deserialize(self, data, context): """Deserialize data from the cache back into python objects. Take data retrieved from the cache and return an appropriate python dictionary. :param str data: The data retrieved from the cache. :param object context: The context that was returned from _get_cache_key. :returns: The python object that was saved. """ # memory cache will handle deserialization for us return data def _serialize(self, data, context): """Serialize data so that it can be saved to the cache. Take python objects and serialize them so that they can be saved into the cache. :param object data: The data to be cached. :param object context: The context that was returned from _get_cache_key. :returns: The python object that was saved. """ # memory cache will handle serialization for us return data def get(self, token_id): """Return token information from cache. If token is invalid raise exc.InvalidToken return token only if fresh (not expired). """ if not token_id: # Nothing to do return key, context = self._get_cache_key(token_id) with self._cache_pool.reserve() as cache: serialized = cache.get(key) if serialized is None: return None if isinstance(serialized, str): serialized = serialized.encode('utf8') data = self._deserialize(serialized, context) if data is None: # In case decryption fails, e.g. data corrupted in memcached. return None if not isinstance(data, str): data = data.decode('utf-8') return jsonutils.loads(data) def set(self, token_id, data): """Store value into memcache.""" data = jsonutils.dumps(data) if isinstance(data, str): data = data.encode('utf-8') cache_key, context = self._get_cache_key(token_id) data_to_store = self._serialize(data, context) with self._cache_pool.reserve() as cache: cache.set(cache_key, data_to_store, time=self._cache_time) class SecureTokenCache(TokenCache): """A token cache that stores tokens encrypted. A more secure version of TokenCache that will encrypt tokens before caching them. """ def __init__(self, log, security_strategy, secret_key, **kwargs): super(SecureTokenCache, self).__init__(log, **kwargs) if not secret_key: msg = _('memcache_secret_key must be defined when a ' 'memcache_security_strategy is defined') raise exc.ConfigurationError(msg) if isinstance(security_strategy, str): security_strategy = security_strategy.encode('utf-8') if isinstance(secret_key, str): secret_key = secret_key.encode('utf-8') self._security_strategy = security_strategy self._secret_key = secret_key def _get_cache_key(self, token_id): context = memcache_crypt.derive_keys(token_id, self._secret_key, self._security_strategy) key = self._CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(context) return key, context def _deserialize(self, data, context): try: # unprotect_data will return None if raw_cached is None return memcache_crypt.unprotect_data(context, data) except Exception: msg = 'Failed to decrypt/verify cache data' self._LOG.exception(msg) # this should have the same effect as data not # found in cache return None def _serialize(self, data, context): return memcache_crypt.protect_data(context, data) class _FakeClient(object): """Replicates a tiny subset of memcached client interface.""" def __init__(self, *args, **kwargs): # Ignores the passed in args self.cache = {} def get(self, key): """Retrieve the value for a key or None. This expunges expired keys during each get. """ now = timeutils.utcnow_ts() for k in list(self.cache): (timeout, _value) = self.cache[k] if timeout and now >= timeout: del self.cache[k] return self.cache.get(key, (0, None))[1] def set(self, key, value, time=0, min_compress_len=0): """Set the value for a key.""" timeout = 0 if time != 0: timeout = timeutils.utcnow_ts() + time self.cache[key] = (timeout, value) return True def add(self, key, value, time=0, min_compress_len=0): """Set the value for a key if it doesn't exist.""" if self.get(key) is not None: return False return self.set(key, value, time, min_compress_len) def incr(self, key, delta=1): """Increment the value for a key.""" value = self.get(key) if value is None: return None new_value = int(value) + delta self.cache[key] = (self.cache[key][0], str(new_value)) return new_value def delete(self, key, time=0): """Delete the value associated with a key.""" if key in self.cache: del self.cache[key] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_exceptions.py0000664000175000017500000000153500000000000026461 0ustar00zuulzuul00000000000000# 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 keystonemiddleware import exceptions ConfigurationError = exceptions.ConfigurationError class InvalidToken(exceptions.KeystoneMiddlewareException): pass class ServiceError(exceptions.KeystoneMiddlewareException): pass class RevocationListError(exceptions.KeystoneMiddlewareException): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_identity.py0000664000175000017500000001633300000000000026133 0ustar00zuulzuul00000000000000# 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 urllib.parse from keystoneauth1 import discover from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import plugin from keystoneclient.v3 import client as v3_client from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.i18n import _ ACCESS_RULES_SUPPORT = '1' class _RequestStrategy(object): AUTH_VERSION = None def __init__(self, adap, include_service_catalog=None, requested_auth_interface=None): self._include_service_catalog = include_service_catalog self._requested_auth_interface = requested_auth_interface def verify_token(self, user_token, allow_expired=False): pass class _V3RequestStrategy(_RequestStrategy): AUTH_VERSION = (3, 0) def __init__(self, adap, **kwargs): super(_V3RequestStrategy, self).__init__(adap, **kwargs) client_args = {'session': adap} if self._requested_auth_interface: client_args['interface'] = self._requested_auth_interface self._client = v3_client.Client(**client_args) def verify_token(self, token, allow_expired=False): auth_ref = self._client.tokens.validate( token, include_catalog=self._include_service_catalog, allow_expired=allow_expired, access_rules_support=ACCESS_RULES_SUPPORT) if not auth_ref: msg = _('Failed to fetch token data from identity server') raise ksm_exceptions.InvalidToken(msg) return {'token': auth_ref} _REQUEST_STRATEGIES = [_V3RequestStrategy] class IdentityServer(object): """Base class for operations on the Identity API server. The auth_token middleware needs to communicate with the Identity API server to validate tokens. This class encapsulates the data and methods to perform the operations. """ def __init__(self, log, adap, include_service_catalog=None, requested_auth_version=None, requested_auth_interface=None): self._LOG = log self._adapter = adap self._include_service_catalog = include_service_catalog self._requested_auth_version = requested_auth_version self._requested_auth_interface = requested_auth_interface # Built on-demand with self._request_strategy. self._request_strategy_obj = None @property def www_authenticate_uri(self): www_authenticate_uri = self._adapter.get_endpoint( interface=plugin.AUTH_INTERFACE) # NOTE(jamielennox): This weird stripping of the prefix hack is # only relevant to the legacy case. We urljoin '/' to get just the # base URI as this is the original behaviour. if isinstance(self._adapter.auth, _auth.AuthTokenPlugin): www_authenticate_uri = urllib.parse.urljoin( www_authenticate_uri, '/').rstrip('/') return www_authenticate_uri @property def auth_version(self): return self._request_strategy.AUTH_VERSION @property def _request_strategy(self): if not self._request_strategy_obj: strategy_class = self._get_strategy_class() self._adapter.version = strategy_class.AUTH_VERSION self._request_strategy_obj = strategy_class( self._adapter, include_service_catalog=self._include_service_catalog, requested_auth_interface=self._requested_auth_interface) return self._request_strategy_obj def _get_strategy_class(self): if self._requested_auth_version: if not discover.version_match(_V3RequestStrategy.AUTH_VERSION, self._requested_auth_interface): self._LOG.info('A version other than v3 was requested: %s', self._requested_auth_interface) # Return v3, even if the request is unknown return _V3RequestStrategy # Specific version was not requested then we fall through to # discovering available versions from the server for klass in _REQUEST_STRATEGIES: if self._adapter.get_endpoint(version=klass.AUTH_VERSION): self._LOG.debug('Auth Token confirmed use of %s apis', klass.AUTH_VERSION) return klass versions = ['v%d.%d' % s.AUTH_VERSION for s in _REQUEST_STRATEGIES] self._LOG.error('No attempted versions [%s] supported by server', ', '.join(versions)) msg = _('No compatible apis supported by server') raise ksm_exceptions.ServiceError(msg) def verify_token(self, user_token, retry=True, allow_expired=False): """Authenticate user token with identity server. :param user_token: user's token id :param retry: flag that forces the middleware to retry user authentication when an indeterminate response is received. Optional. :param allow_expired: Allow retrieving an expired token. :returns: access info received from identity server on success :rtype: :py:class:`keystoneauth1.access.AccessInfo` :raises exc.InvalidToken: if token is rejected :raises exc.ServiceError: if unable to authenticate token """ try: auth_ref = self._request_strategy.verify_token( user_token, allow_expired=allow_expired) except ksa_exceptions.NotFound as e: self._LOG.info('Authorization failed for token') self._LOG.info('Identity response: %s', e.response.text) raise ksm_exceptions.InvalidToken(_('Token authorization failed')) except ksa_exceptions.Unauthorized as e: self._LOG.info('Identity server rejected authorization') self._LOG.warning('Identity response: %s', e.response.text) if retry: self._LOG.info('Retrying validation') return self.verify_token(user_token, False) msg = _('Identity server rejected authorization necessary to ' 'fetch token data') raise ksm_exceptions.ServiceError(msg) except ksa_exceptions.HttpError as e: self._LOG.error( 'Bad response code while validating token: %s %s', e.http_status, e.message) if hasattr(e.response, 'text'): self._LOG.warning('Identity response: %s', e.response.text) msg = _('Failed to fetch token data from identity server') raise ksm_exceptions.ServiceError(msg) else: return auth_ref def invalidate(self): return self._adapter.invalidate() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_memcache_crypt.py0000664000175000017500000001536600000000000027272 0ustar00zuulzuul00000000000000# Copyright 2010-2013 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. """ Utilities for memcache encryption and integrity check. Data should be serialized before entering these functions. Encryption has a dependency on the cryptography module. If cryptography is not available, CryptoUnavailableError will be raised. This module will not be called unless signing or encryption is enabled in the config. It will always validate signatures, and will decrypt data if encryption is enabled. It is not valid to mix protection modes. """ import base64 import functools import hashlib import hmac import math import os from keystonemiddleware.i18n import _ try: from cryptography.hazmat import backends as crypto_backends from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives import padding except ImportError: ciphers = None HASH_FUNCTION = hashlib.sha384 DIGEST_LENGTH = HASH_FUNCTION().digest_size DIGEST_SPLIT = DIGEST_LENGTH // 3 DIGEST_LENGTH_B64 = 4 * int(math.ceil(DIGEST_LENGTH / 3.0)) class InvalidMacError(Exception): """raise when unable to verify MACed data. This usually indicates that data had been expectedly modified in memcache. """ pass class DecryptError(Exception): """raise when unable to decrypt encrypted data.""" pass class CryptoUnavailableError(Exception): """raise when Python Crypto module is not available.""" pass def assert_crypto_availability(f): """Ensure cryptography module is available.""" @functools.wraps(f) def wrapper(*args, **kwds): if ciphers is None: raise CryptoUnavailableError() return f(*args, **kwds) return wrapper def derive_keys(token, secret, strategy): """Derive keys for MAC and ENCRYPTION from the user-provided secret. The resulting keys should be passed to the protect and unprotect functions. As suggested by NIST Special Publication 800-108, this uses the first 128 bits from the sha384 KDF for the obscured cache key value, the second 128 bits for the message authentication key and the remaining 128 bits for the encryption key. This approach is faster than computing a separate hmac as the KDF for each desired key. """ if not isinstance(secret, bytes): secret = secret.encode() if not isinstance(token, bytes): token = token.encode() if not isinstance(strategy, bytes): strategy = strategy.encode() digest = hmac.new(secret, token + strategy, HASH_FUNCTION).digest() return {'CACHE_KEY': digest[:DIGEST_SPLIT], 'MAC': digest[DIGEST_SPLIT: 2 * DIGEST_SPLIT], 'ENCRYPTION': digest[2 * DIGEST_SPLIT:], 'strategy': strategy} def sign_data(key, data): """Sign the data using the defined function and the derived key.""" if not isinstance(key, bytes): key = key.encode() if not isinstance(data, bytes): data = data.encode() mac = hmac.new(key, data, HASH_FUNCTION).digest() return base64.b64encode(mac) @assert_crypto_availability def encrypt_data(key, data): """Encrypt the data with the given secret key. Padding is n bytes of the value n, where 1 <= n <= blocksize. """ iv = os.urandom(16) cipher = ciphers.Cipher( algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()) # AES algorithm uses block size of 16 bytes = 128 bits, defined in # algorithms.AES.block_size. Previously, we manually padded this using # bytes((padding,)) * padding. Using ``cryptography``, we will # analogously use hazmat.primitives.padding to pad it to # the 128-bit block size. padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = cipher.encryptor() return iv + encryptor.update(padded_data) + encryptor.finalize() def decrypt_data(key, data): """Decrypt the data with the given secret key.""" iv = data[:16] cipher = ciphers.Cipher( algorithms.AES(key), modes.CBC(iv), backend=crypto_backends.default_backend()) try: decryptor = cipher.decryptor() result = decryptor.update(data[16:]) + decryptor.finalize() except Exception: raise DecryptError(_('Encrypted data appears to be corrupted.')) # Strip the last n padding bytes where n is the last value in # the plaintext unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() return unpadder.update(result) + unpadder.finalize() def protect_data(keys, data): """Serialize data given a dict of keys. Given keys and serialized data, returns an appropriately protected string suitable for storage in the cache. """ if keys['strategy'] == b'ENCRYPT': data = encrypt_data(keys['ENCRYPTION'], data) encoded_data = base64.b64encode(data) signature = sign_data(keys['MAC'], encoded_data) return signature + encoded_data def unprotect_data(keys, signed_data): """De-serialize data given a dict of keys. Given keys and cached string data, verifies the signature, decrypts if necessary, and returns the original serialized data. """ # cache backends return None when no data is found. We don't mind # that this particular special value is unsigned. if signed_data is None: return None # First we calculate the signature provided_mac = signed_data[:DIGEST_LENGTH_B64] calculated_mac = sign_data( keys['MAC'], signed_data[DIGEST_LENGTH_B64:]) # Then verify that it matches the provided value if not hmac.compare_digest(provided_mac, calculated_mac): raise InvalidMacError(_('Invalid MAC; data appears to be corrupted.')) data = base64.b64decode(signed_data[DIGEST_LENGTH_B64:]) # then if necessary decrypt the data if keys['strategy'] == b'ENCRYPT': data = decrypt_data(keys['ENCRYPTION'], data) return data def get_cache_key(keys): """Return a cache key. Given keys generated by derive_keys(), returns a base64 encoded value suitable for use as a cache key in memcached. """ return base64.b64encode(keys['CACHE_KEY']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_opts.py0000664000175000017500000002772400000000000025275 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from keystoneauth1 import loading from oslo_config import cfg from keystonemiddleware.auth_token import _base # NOTE(jamielennox): A number of options below are deprecated however are left # in the list and only mentioned as deprecated in the help string. This is # because we have to provide the same deprecation functionality for arguments # passed in via the conf in __init__ (from paste) and there is no way to test # that the default value was set or not in CONF. # Also if we were to remove the options from the CONF list (as typical CONF # deprecation works) then other projects will not be able to override the # options via CONF. _OPTS = [ cfg.StrOpt('www_authenticate_uri', # FIXME(dolph): should be default='http://127.0.0.1:5000/v2.0/', # or (depending on client support) an unversioned, publicly # accessible identity endpoint (see bug 1207517). Further, we # can eliminate this configuration option in favor of pulling # the endpoint from the service catalog that the service user # receives (there should be an identity endpoint listed there). # This wasn't an option originally when many auth_token # deployments were configured with the "ADMIN" token and # endpoint combination. deprecated_name='auth_uri', help='Complete "public" Identity API endpoint. This endpoint' ' should not be an "admin" endpoint, as it should be accessible' ' by all end users. Unauthenticated clients are redirected to' ' this endpoint to authenticate. Although this endpoint should' ' ideally be unversioned, client support in the wild varies.' ' If you\'re using a versioned v2 endpoint here, then this' ' should *not* be the same endpoint the service user utilizes' ' for validating tokens, because normal end users may not be' ' able to reach that endpoint.'), cfg.StrOpt('auth_uri', deprecated_for_removal=True, deprecated_reason='The auth_uri option is deprecated in favor' ' of www_authenticate_uri and will be removed in the S ' ' release.', deprecated_since='Queens', help='Complete "public" Identity API endpoint. This endpoint' ' should not be an "admin" endpoint, as it should be accessible' ' by all end users. Unauthenticated clients are redirected to' ' this endpoint to authenticate. Although this endpoint should' ' ideally be unversioned, client support in the wild varies.' ' If you\'re using a versioned v2 endpoint here, then this' ' should *not* be the same endpoint the service user utilizes' ' for validating tokens, because normal end users may not be' ' able to reach that endpoint. This option is deprecated in' ' favor of www_authenticate_uri and will be removed in the S' ' release.'), cfg.StrOpt('auth_version', help='API version of the Identity API endpoint.'), cfg.StrOpt('interface', default='internal', help='Interface to use for the Identity API endpoint. Valid' ' values are "public", "internal" (default) or "admin".'), cfg.BoolOpt('delay_auth_decision', default=False, help='Do not handle authorization requests within the' ' middleware, but delegate the authorization decision to' ' downstream WSGI components.'), cfg.IntOpt('http_connect_timeout', help='Request timeout value for communicating with Identity' ' API server.'), cfg.IntOpt('http_request_max_retries', default=3, help='How many times are we trying to reconnect when' ' communicating with Identity API Server.'), cfg.StrOpt('cache', help='Request environment key where the Swift cache object is' ' stored. When auth_token middleware is deployed with a Swift' ' cache, use this option to have the middleware share a caching' ' backend with swift. Otherwise, use the ``memcached_servers``' ' option instead.'), cfg.StrOpt('certfile', help='Required if identity server requires client certificate'), cfg.StrOpt('keyfile', help='Required if identity server requires client certificate'), cfg.StrOpt('cafile', help='A PEM encoded Certificate Authority to use when ' 'verifying HTTPs connections. Defaults to system CAs.'), cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'), cfg.StrOpt('region_name', help='The region in which the identity server can be found.'), cfg.ListOpt('memcached_servers', deprecated_name='memcache_servers', help='Optionally specify a list of memcached server(s) to' ' use for caching. If left undefined, tokens will instead be' ' cached in-process.'), cfg.IntOpt('token_cache_time', default=300, help='In order to prevent excessive effort spent validating' ' tokens, the middleware caches previously-seen tokens for a' ' configurable duration (in seconds). Set to -1 to disable' ' caching completely.'), cfg.StrOpt('memcache_security_strategy', default='None', choices=('None', 'MAC', 'ENCRYPT'), ignore_case=True, help='(Optional) If defined, indicate whether token data' ' should be authenticated or authenticated and encrypted.' ' If MAC, token data is authenticated (with HMAC) in the cache.' ' If ENCRYPT, token data is encrypted and authenticated in the' ' cache. If the value is not one of these options or empty,' ' auth_token will raise an exception on initialization.'), cfg.StrOpt('memcache_secret_key', secret=True, help='(Optional, mandatory if memcache_security_strategy is' ' defined) This string is used for key derivation.'), cfg.IntOpt('memcache_pool_dead_retry', default=5 * 60, help='(Optional) Number of seconds memcached server is' ' considered dead before it is tried again.'), cfg.IntOpt('memcache_pool_maxsize', default=10, help='(Optional) Maximum total number of open connections to' ' every memcached server.'), cfg.IntOpt('memcache_pool_socket_timeout', default=3, help='(Optional) Socket timeout in seconds for communicating ' 'with a memcached server.'), cfg.IntOpt('memcache_pool_unused_timeout', default=60, help='(Optional) Number of seconds a connection to memcached' ' is held unused in the pool before it is closed.'), cfg.IntOpt('memcache_pool_conn_get_timeout', default=10, help='(Optional) Number of seconds that an operation will wait ' 'to get a memcached client connection from the pool.'), cfg.BoolOpt('memcache_use_advanced_pool', default=True, help='(Optional) Use the advanced (eventlet safe) memcached ' 'client pool.'), cfg.BoolOpt('include_service_catalog', default=True, help='(Optional) Indicate whether to set the X-Service-Catalog' ' header. If False, middleware will not ask for service' ' catalog on token validation and will not set the' ' X-Service-Catalog header.'), cfg.StrOpt('enforce_token_bind', default='permissive', help='Used to control the use and type of token binding. Can' ' be set to: "disabled" to not check token binding.' ' "permissive" (default) to validate binding information if the' ' bind type is of a form known to the server and ignore it if' ' not. "strict" like "permissive" but if the bind type is' ' unknown the token will be rejected. "required" any form of' ' token binding is needed to be allowed. Finally the name of a' ' binding method that must be present in tokens.'), cfg.ListOpt('service_token_roles', default=['service'], help='A choice of roles that must be present in a service' ' token. Service tokens are allowed to request that an expired' ' token can be used and so this check should tightly control' ' that only actual services should be sending this token.' ' Roles here are applied as an ANY check so any role in this' ' list must be present. For backwards compatibility reasons' ' this currently only affects the allow_expired check.'), cfg.BoolOpt('service_token_roles_required', default=False, help='For backwards compatibility reasons we must let valid' ' service tokens pass that don\'t pass the service_token_roles' ' check as valid. Setting this true will become the default' ' in a future release and should be enabled if possible.'), cfg.StrOpt('service_type', help='The name or type of the service as it appears in the' ' service catalog. This is used to validate tokens that have' ' restricted access rules.'), cfg.BoolOpt('memcache_sasl_enabled', default=False, help='Enable the SASL(Simple Authentication and Security' ' Layer) if the SASL_enable is true, else disable.'), cfg.StrOpt('memcache_username', default='', help='the user name for the SASL'), cfg.StrOpt('memcache_password', default='', help='the username password for SASL'), ] CONF = cfg.CONF CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP) loading.register_auth_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP) auth_token_opts = [ (_base.AUTHTOKEN_GROUP, _OPTS + loading.get_auth_common_conf_options()), ] __all__ = ( 'list_opts', ) def list_opts(): """Return a list of oslo_config options available in auth_token middleware. The returned list includes the non-deprecated oslo_config options which may be registered at runtime by the project. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this middleware. Deprecated Options should not show up here so as to not be included in sample configuration. 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 discoverable via the entry point 'keystonemiddleware.auth_token' under the 'oslo.config.opts' namespace. :returns: a list of (group_name, opts) tuples """ auth_token_opts = (_OPTS + loading.get_auth_common_conf_options()) return [(_base.AUTHTOKEN_GROUP, copy.deepcopy(auth_token_opts))] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_request.py0000664000175000017500000002127100000000000025767 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools from oslo_serialization import jsonutils import webob def _normalize_catalog(catalog): """Convert a catalog to a compatible format.""" services = [] for v3_service in catalog: # first copy over the entries we allow for the service service = {'type': v3_service['type']} try: service['name'] = v3_service['name'] except KeyError: # nosec # v3 service doesn't have a name, move on. pass # now convert the endpoints. Because in v3 we specify region per # URL not per group we have to collect all the entries of the same # region together before adding it to the new service. regions = {} for v3_endpoint in v3_service.get('endpoints', []): region_name = v3_endpoint.get('region') try: region = regions[region_name] except KeyError: region = {'region': region_name} if region_name else {} regions[region_name] = region interface_name = v3_endpoint['interface'].lower() + 'URL' region[interface_name] = v3_endpoint['url'] service['endpoints'] = list(regions.values()) services.append(service) return services def _is_admin_project(auth_ref): """Return an appropriate header value for X-Is-Admin-Project. Headers must be strings so we can't simply pass a boolean value through so return a True or False string to signal the admin project. """ return 'True' if auth_ref.is_admin_project else 'False' def _get_system_scope(auth_ref): """Return the scope information of a system scoped token.""" if auth_ref.system_scoped: if auth_ref.system.get('all'): return 'all' # NOTE(jamielennox): this should probably be moved into its own file, but at # the moment there's no real logic here so just keep it locally. class _AuthTokenResponse(webob.Response): default_content_type = None # prevents webob assigning a content type class _AuthTokenRequest(webob.Request): ResponseClass = _AuthTokenResponse _HEADER_TEMPLATE = { 'X%s-Domain-Id': 'domain_id', 'X%s-Domain-Name': 'domain_name', 'X%s-Project-Id': 'project_id', 'X%s-Project-Name': 'project_name', 'X%s-Project-Domain-Id': 'project_domain_id', 'X%s-Project-Domain-Name': 'project_domain_name', 'X%s-User-Id': 'user_id', 'X%s-User-Name': 'username', 'X%s-User-Domain-Id': 'user_domain_id', 'X%s-User-Domain-Name': 'user_domain_name', } _ROLES_TEMPLATE = 'X%s-Roles' _USER_HEADER_PREFIX = '' _SERVICE_HEADER_PREFIX = '-Service' _USER_STATUS_HEADER = 'X-Identity-Status' _SERVICE_STATUS_HEADER = 'X-Service-Identity-Status' _ADMIN_PROJECT_HEADER = 'X-Is-Admin-Project' _SYSTEM_SCOPE_HEADER = 'OpenStack-System-Scope' _SERVICE_CATALOG_HEADER = 'X-Service-Catalog' _TOKEN_AUTH = 'keystone.token_auth' # nosec _TOKEN_INFO = 'keystone.token_info' # nosec _CONFIRMED = 'Confirmed' _INVALID = 'Invalid' # header names that have been deprecated in favour of something else. _DEPRECATED_HEADER_MAP = { 'X-Role': 'X-Roles', 'X-User': 'X-User-Name', 'X-Tenant-Id': 'X-Project-Id', 'X-Tenant-Name': 'X-Project-Name', 'X-Tenant': 'X-Project-Name', } def _confirmed(cls, value): return cls._CONFIRMED if value else cls._INVALID @property def user_token_valid(self): """User token is marked as valid. :returns: True if the X-Identity-Status header is set to Confirmed. :rtype: bool """ return self.headers[self._USER_STATUS_HEADER] == self._CONFIRMED @user_token_valid.setter def user_token_valid(self, value): self.headers[self._USER_STATUS_HEADER] = self._confirmed(value) @property def user_token(self): return self.headers.get('X-Auth-Token', self.headers.get('X-Storage-Token')) @property def service_token_valid(self): """Service token is marked as valid. :returns: True if the X-Service-Identity-Status header is set to Confirmed. :rtype: bool """ return self.headers[self._SERVICE_STATUS_HEADER] == self._CONFIRMED @service_token_valid.setter def service_token_valid(self, value): self.headers[self._SERVICE_STATUS_HEADER] = self._confirmed(value) @property def service_token(self): return self.headers.get('X-Service-Token') def _set_auth_headers(self, auth_ref, prefix): names = ','.join(auth_ref.role_names) self.headers[self._ROLES_TEMPLATE % prefix] = names self.headers[self._SYSTEM_SCOPE_HEADER] = _get_system_scope(auth_ref) for header_tmplt, attr in self._HEADER_TEMPLATE.items(): self.headers[header_tmplt % prefix] = getattr(auth_ref, attr) def set_user_headers(self, auth_ref): """Convert token object into headers. Build headers that represent authenticated user - see main doc info at start of __init__ file for details of headers to be defined """ self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX) self.headers[self._ADMIN_PROJECT_HEADER] = _is_admin_project(auth_ref) for k, v in self._DEPRECATED_HEADER_MAP.items(): self.headers[k] = self.headers[v] def set_service_catalog_headers(self, auth_ref): """Convert service catalog from token object into headers. Build headers that represent the catalog - see main doc info at start of __init__ file for details of headers to be defined :param auth_ref: The token data :type auth_ref: keystoneauth.access.AccessInfo """ if not auth_ref.has_service_catalog(): self.headers.pop(self._SERVICE_CATALOG_HEADER, None) return catalog = auth_ref.service_catalog.catalog if auth_ref.version == 'v3': catalog = _normalize_catalog(catalog) c = jsonutils.dumps(catalog) self.headers[self._SERVICE_CATALOG_HEADER] = c def set_service_headers(self, auth_ref): """Convert token object into service headers. Build headers that represent authenticated user - see main doc info at start of __init__ file for details of headers to be defined """ self._set_auth_headers(auth_ref, self._SERVICE_HEADER_PREFIX) def _all_auth_headers(self): """All the authentication headers that can be set on the request.""" yield self._SERVICE_CATALOG_HEADER yield self._USER_STATUS_HEADER yield self._SERVICE_STATUS_HEADER yield self._ADMIN_PROJECT_HEADER for header in self._DEPRECATED_HEADER_MAP: yield header prefixes = (self._USER_HEADER_PREFIX, self._SERVICE_HEADER_PREFIX) for tmpl, prefix in itertools.product(self._HEADER_TEMPLATE, prefixes): yield tmpl % prefix for prefix in prefixes: yield self._ROLES_TEMPLATE % prefix def remove_auth_headers(self): """Remove headers so a user can't fake authentication.""" for header in self._all_auth_headers(): self.headers.pop(header, None) @property def auth_type(self): """The authentication type that was performed by the web server. The returned string value is always lower case. :returns: The AUTH_TYPE environ string or None if not present. :rtype: str or None """ try: auth_type = self.environ['AUTH_TYPE'] except KeyError: return None else: return auth_type.lower() @property def token_auth(self): """The auth plugin that will be associated with this request.""" return self.environ.get(self._TOKEN_AUTH) @token_auth.setter def token_auth(self, v): self.environ[self._TOKEN_AUTH] = v @property def token_info(self): """The raw token dictionary retrieved by the middleware.""" return self.environ.get(self._TOKEN_INFO) @token_info.setter def token_info(self, v): self.environ[self._TOKEN_INFO] = v ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/auth_token/_user_plugin.py0000664000175000017500000000557200000000000026641 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1.identity import base as base_identity def _log_format(auth_ref): roles = ','.join(auth_ref.role_names) return 'user_id %s, project_id %s, roles %s' % (auth_ref.user_id, auth_ref.project_id, roles) class UserAuthPlugin(base_identity.BaseIdentityPlugin): """The incoming authentication credentials. A plugin that represents the incoming user credentials. This can be consumed by applications. This object is not expected to be constructed directly by users. It is created and passed by auth_token middleware and then can be used as the authentication plugin when communicating via a session. """ def __init__(self, user_auth_ref, serv_auth_ref, session=None, auth=None): super(UserAuthPlugin, self).__init__(reauthenticate=False) self.user = user_auth_ref self.service = serv_auth_ref # NOTE(jamielennox): adding a service token requires the original # session and auth plugin from auth_token self._session = session self._auth = auth @property def has_user_token(self): """Did this authentication request contained a user auth token.""" return self.user is not None @property def has_service_token(self): """Did this authentication request contained a service token.""" return self.service is not None def get_auth_ref(self, session, **kwargs): # NOTE(jamielennox): We will always use the auth_ref that was # calculated by the middleware. reauthenticate=False in __init__ should # ensure that this function is only called on the first access. return self.user @property def _log_format(self): msg = [] if self.has_user_token: msg.append('user: %s' % _log_format(self.user)) if self.has_service_token: msg.append('service: %s' % _log_format(self.service)) return ' '.join(msg) def get_headers(self, session, **kwargs): headers = super(UserAuthPlugin, self).get_headers(session, **kwargs) if headers is not None and self._session: token = self._session.get_token(auth=self._auth) if token: headers['X-Service-Token'] = token return headers ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/ec2_token.py0000664000175000017500000001763000000000000023654 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Starting point for routing EC2 requests.""" import hashlib from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils import requests import webob.dec from keystonemiddleware.i18n import _ keystone_ec2_opts = [ cfg.StrOpt('url', default='http://localhost:5000/v3/ec2tokens', help='URL to get token from ec2 request.'), cfg.StrOpt('keyfile', help='Required if EC2 server requires client certificate.'), cfg.StrOpt('certfile', help='Client certificate key filename. Required if EC2 server ' 'requires client certificate.'), cfg.StrOpt('cafile', help='A PEM encoded certificate authority to use when ' 'verifying HTTPS connections. Defaults to the system ' 'CAs.'), cfg.BoolOpt('insecure', default=False, help='Disable SSL certificate verification.'), cfg.IntOpt('timeout', default=60, help='Timeout to obtain token.'), ] CONF = cfg.CONF CONF.register_opts(keystone_ec2_opts, group='keystone_ec2_token') PROTOCOL_NAME = 'EC2 Token Authentication' class EC2Token(object): """Authenticate an EC2 request with keystone and convert to token.""" def __init__(self, application, conf): super(EC2Token, self).__init__() self._application = application self._logger = logging.getLogger(conf.get('log_name', __name__)) self._logger.debug('Starting the %s component', PROTOCOL_NAME) def _ec2_error_response(self, code, message): """Helper to construct an EC2 compatible error message.""" self._logger.debug('EC2 error response: %(code)s: %(message)s', {'code': code, 'message': message}) resp = webob.Response() resp.status = 400 resp.headers['Content-Type'] = 'text/xml' error_msg = str('\n' '%s' '%s' % (code, message)) error_msg = error_msg.encode() resp.body = error_msg return resp def _get_signature(self, req): """Extract the signature from the request. This can be a get/post variable or for version 4 also in a header called 'Authorization'. - params['Signature'] == version 0,1,2,3 - params['X-Amz-Signature'] == version 4 - header 'Authorization' == version 4 """ sig = req.params.get('Signature') or req.params.get('X-Amz-Signature') if sig is None and 'Authorization' in req.headers: auth_str = req.headers['Authorization'] sig = auth_str.partition("Signature=")[2].split(',')[0] return sig def _get_access(self, req): """Extract the access key identifier. For version 0/1/2/3 this is passed as the AccessKeyId parameter, for version 4 it is either an X-Amz-Credential parameter or a Credential= field in the 'Authorization' header string. """ access = req.params.get('AWSAccessKeyId') if access is None: cred_param = req.params.get('X-Amz-Credential') if cred_param: access = cred_param.split("/")[0] if access is None and 'Authorization' in req.headers: auth_str = req.headers['Authorization'] cred_str = auth_str.partition("Credential=")[2].split(',')[0] access = cred_str.split("/")[0] return access @webob.dec.wsgify() def __call__(self, req): # NOTE(alevine): We need to calculate the hash here because # subsequent access to request modifies the req.body so the hash # calculation will yield invalid results. body_hash = hashlib.sha256(req.body).hexdigest() signature = self._get_signature(req) if not signature: msg = _("Signature not provided") return self._ec2_error_response("AuthFailure", msg) access = self._get_access(req) if not access: msg = _("Access key not provided") return self._ec2_error_response("AuthFailure", msg) if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers: auth_params = {} else: # Make a copy of args for authentication and signature verification auth_params = dict(req.params) # Not part of authentication args auth_params.pop('Signature', None) headers = req.headers # NOTE(andrey-mp): jsonutils dumps it as list of keys without # conversion instead real dict headers = {k: headers[k] for k in headers} cred_dict = { 'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': auth_params, 'headers': headers, 'body_hash': body_hash } if "ec2" in CONF.keystone_ec2_token.url: creds = {'ec2Credentials': cred_dict} else: creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}} creds_json = jsonutils.dumps(creds) headers = {'Content-Type': 'application/json'} verify = not CONF.keystone_ec2_token.insecure if verify and CONF.keystone_ec2_token.cafile: verify = CONF.keystone_ec2_token.cafile cert = None if (CONF.keystone_ec2_token.certfile and CONF.keystone_ec2_token.keyfile): cert = (CONF.keystone_ec2_certfile, CONF.keystone_ec2_token.keyfile) elif CONF.keystone_ec2_token.certfile: cert = CONF.keystone_ec2_token.certfile response = requests.post(CONF.keystone_ec2_token.url, data=creds_json, headers=headers, verify=verify, cert=cert, timeout=CONF.keystone_ec2_token.timeout) # NOTE(vish): We could save a call to keystone by # having keystone return token, tenant, # user, and roles from this call. status_code = response.status_code if status_code != 200: msg = _('Error response from keystone: %s') % response.reason self._logger.debug(msg) return self._ec2_error_response("AuthFailure", msg) try: token_id = response.headers['x-subject-token'] except (AttributeError, KeyError): msg = _("Failure parsing response from keystone") self._logger.exception(msg) return self._ec2_error_response("AuthFailure", msg) # Authenticated! req.headers['X-Auth-Token'] = token_id return self._application def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return EC2Token(app, conf) return auth_filter def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return EC2Token(None, conf) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0007513 keystonemiddleware-10.9.0/keystonemiddleware/echo/0000775000175000017500000000000000000000000022340 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/echo/__init__.py0000664000175000017500000000000000000000000024437 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/echo/__main__.py0000664000175000017500000000025000000000000024427 0ustar00zuulzuul00000000000000from keystonemiddleware.echo import service try: service.EchoService() except KeyboardInterrupt: # nosec # The user wants this application to exit. pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/echo/service.py0000664000175000017500000000327100000000000024355 0ustar00zuulzuul00000000000000# 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. """ Run the echo service. The echo service can be run on port 8000 by executing the following:: $ python -m keystonemiddleware.echo When the ``auth_token`` module authenticates a request, the echo service will respond with all the environment variables presented to it by this module. """ from wsgiref import simple_server from oslo_serialization import jsonutils from keystonemiddleware import auth_token def echo_app(environ, start_response): """A WSGI application that echoes the CGI environment back to the user.""" start_response('200 OK', [('Content-Type', 'application/json')]) environment = dict((k, v) for k, v in environ.items() if k.startswith('HTTP_X_')) yield jsonutils.dumps(environment) class EchoService(object): """Runs an instance of the echo app on init.""" def __init__(self): # hardcode any non-default configuration here conf = {'auth_protocol': 'http', 'admin_token': 'ADMIN'} app = auth_token.AuthProtocol(echo_app, conf) server = simple_server.make_server('', 8000, app) print('Serving on port 8000 (Ctrl+C to end)...') server.serve_forever() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/exceptions.py0000664000175000017500000000123400000000000024155 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class KeystoneMiddlewareException(Exception): pass class ConfigurationError(KeystoneMiddlewareException): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/external_oauth2_token.py0000664000175000017500000012626200000000000026311 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import copy import hashlib import os import ssl import time import uuid import jwt.utils import oslo_cache from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils import requests.auth import webob.dec import webob.exc from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import loading from keystoneauth1.loading import session as session_loading from keystonemiddleware._common import config from keystonemiddleware.auth_token import _cache from keystonemiddleware.exceptions import ConfigurationError from keystonemiddleware.exceptions import KeystoneMiddlewareException from keystonemiddleware.i18n import _ oslo_cache.configure(cfg.CONF) _EXT_AUTH_CONFIG_GROUP_NAME = 'ext_oauth2_auth' _EXTERNAL_AUTH2_OPTS = [ cfg.StrOpt('certfile', help='Required if identity server requires client ' 'certificate.'), cfg.StrOpt('keyfile', help='Required if identity server requires client ' 'private key.'), cfg.StrOpt('cafile', help='A PEM encoded Certificate Authority to use when ' 'verifying HTTPs connections. Defaults to system CAs.'), cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'), cfg.IntOpt('http_connect_timeout', help='Request timeout value for communicating with Identity ' 'API server.'), cfg.StrOpt('introspect_endpoint', help='The endpoint for introspect API, it is used to verify ' 'that the OAuth 2.0 access token is valid.'), cfg.StrOpt('audience', help='The Audience should be the URL of the Authorization ' 'Server\'s Token Endpoint. The Authorization Server will ' 'verify that it is an intended audience for the token.'), cfg.StrOpt('auth_method', default='client_secret_basic', choices=('client_secret_basic', 'client_secret_post', 'tls_client_auth', 'private_key_jwt', 'client_secret_jwt'), help='The auth_method must use the authentication method ' 'specified by the Authorization Server. The system ' 'supports 5 authentication methods such as ' 'tls_client_auth, client_secret_basic, ' 'client_secret_post, client_secret_jwt, private_key_jwt.'), cfg.StrOpt('client_id', help='The OAuth 2.0 Client Identifier valid at the ' 'Authorization Server.'), cfg.StrOpt('client_secret', help='The OAuth 2.0 client secret. When the auth_method is ' 'client_secret_basic, client_secret_post, or ' 'client_secret_jwt, the value is used, and otherwise the ' 'value is ignored.'), cfg.BoolOpt('thumbprint_verify', default=False, help='If the access token generated by the Authorization ' 'Server is bound to the OAuth 2.0 certificate ' 'thumbprint, the value can be set to true, and then the ' 'keystone middleware will verify the thumbprint.'), cfg.StrOpt('jwt_key_file', help='The jwt_key_file must use the certificate key file which ' 'has been registered with the Authorization Server. ' 'When the auth_method is private_key_jwt, the value is ' 'used, and otherwise the value is ignored.'), cfg.StrOpt('jwt_algorithm', help='The jwt_algorithm must use the algorithm specified by ' 'the Authorization Server. When the auth_method is ' 'client_secret_jwt, this value is often set to HS256, ' 'when the auth_method is private_key_jwt, the value is ' 'often set to RS256, and otherwise the value is ignored.'), cfg.IntOpt('jwt_bearer_time_out', default=3600, help='This value is used to calculate the expiration time. If ' 'after the expiration time, the access token can not be ' 'accepted. When the auth_method is client_secret_jwt or ' 'private_key_jwt, the value is used, and otherwise the ' 'value is ignored.'), cfg.StrOpt('mapping_project_id', help='Specifies the method for obtaining the project ID that ' 'currently needs to be accessed. '), cfg.StrOpt('mapping_project_name', help='Specifies the method for obtaining the project name that ' 'currently needs to be accessed.'), cfg.StrOpt('mapping_project_domain_id', help='Specifies the method for obtaining the project domain ID ' 'that currently needs to be accessed.'), cfg.StrOpt('mapping_project_domain_name', help='Specifies the method for obtaining the project domain ' 'name that currently needs to be accessed.'), cfg.StrOpt('mapping_user_id', default='client_id', help='Specifies the method for obtaining the user ID.'), cfg.StrOpt('mapping_user_name', default='username', help='Specifies the method for obtaining the user name.'), cfg.StrOpt('mapping_user_domain_id', help='Specifies the method for obtaining the domain ID which ' 'the user belongs.'), cfg.StrOpt('mapping_user_domain_name', help='Specifies the method for obtaining the domain name which ' 'the user belongs.'), cfg.StrOpt('mapping_roles', help='Specifies the method for obtaining the list of roles in ' 'a project or domain owned by the user.'), cfg.StrOpt('mapping_system_scope', help='Specifies the method for obtaining the scope information ' 'indicating whether a token is system-scoped.'), cfg.StrOpt('mapping_expires_at', help='Specifies the method for obtaining the token expiration ' 'time.'), cfg.ListOpt('memcached_servers', deprecated_name='memcache_servers', help='Optionally specify a list of memcached server(s) to ' 'use for caching. If left undefined, tokens will ' 'instead be cached in-process.'), cfg.IntOpt('token_cache_time', default=300, help='In order to prevent excessive effort spent validating ' 'tokens, the middleware caches previously-seen tokens ' 'for a configurable duration (in seconds). Set to -1 to ' 'disable caching completely.'), cfg.StrOpt('memcache_security_strategy', default='None', choices=('None', 'MAC', 'ENCRYPT'), ignore_case=True, help='(Optional) If defined, indicate whether token data ' 'should be authenticated or authenticated and encrypted. ' 'If MAC, token data is authenticated (with HMAC) in the ' 'cache. If ENCRYPT, token data is encrypted and ' 'authenticated in the cache. If the value is not one of ' 'these options or empty, auth_token will raise an ' 'exception on initialization.'), cfg.StrOpt('memcache_secret_key', secret=True, help='(Optional, mandatory if memcache_security_strategy is ' 'defined) This string is used for key derivation.'), cfg.IntOpt('memcache_pool_dead_retry', default=5 * 60, help='(Optional) Number of seconds memcached server is ' 'considered dead before it is tried again.'), cfg.IntOpt('memcache_pool_maxsize', default=10, help='(Optional) Maximum total number of open connections to ' 'every memcached server.'), cfg.IntOpt('memcache_pool_socket_timeout', default=3, help='(Optional) Socket timeout in seconds for communicating ' 'with a memcached server.'), cfg.IntOpt('memcache_pool_unused_timeout', default=60, help='(Optional) Number of seconds a connection to memcached ' 'is held unused in the pool before it is closed.'), cfg.IntOpt('memcache_pool_conn_get_timeout', default=10, help='(Optional) Number of seconds that an operation will wait ' 'to get a memcached client connection from the pool.'), cfg.BoolOpt('memcache_use_advanced_pool', default=True, help='(Optional) Use the advanced (eventlet safe) memcached ' 'client pool.') ] cfg.CONF.register_opts(_EXTERNAL_AUTH2_OPTS, group=_EXT_AUTH_CONFIG_GROUP_NAME) class InvalidToken(KeystoneMiddlewareException): """Raise an InvalidToken Error. When can not get necessary information from the token, this error will be thrown. """ class ForbiddenToken(KeystoneMiddlewareException): """Raise a ForbiddenToken Error. When can not get necessary information from the token, this error will be thrown. """ class ServiceError(KeystoneMiddlewareException): """Raise a ServiceError. When can not verify any tokens, this error will be thrown. """ class AbstractAuthClient(object, metaclass=abc.ABCMeta): """Abstract http client using to access the OAuth2.0 Server.""" def __init__(self, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): self.session = session self.introspect_endpoint = introspect_endpoint self.audience = audience self.client_id = client_id self.get_config_option = func_get_config_option self.logger = logger @abc.abstractmethod def introspect(self, access_token): """Access the introspect API.""" pass class ClientSecretBasicAuthClient(AbstractAuthClient): """Http client with the auth method 'client_secret_basic'.""" def __init__(self, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): super(ClientSecretBasicAuthClient, self).__init__( session, introspect_endpoint, audience, client_id, func_get_config_option, logger) self.client_secret = self.get_config_option( 'client_secret', is_required=True) def introspect(self, access_token): """Access the introspect API. Access the Introspect API to verify the access token by the auth method 'client_secret_basic'. """ req_data = {'token': access_token, 'token_type_hint': 'access_token'} auth = requests.auth.HTTPBasicAuth(self.client_id, self.client_secret) http_response = self.session.request( self.introspect_endpoint, 'POST', authenticated=False, data=req_data, requests_auth=auth) return http_response class ClientSecretPostAuthClient(AbstractAuthClient): """Http client with the auth method 'client_secret_post'.""" def __init__(self, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): super(ClientSecretPostAuthClient, self).__init__( session, introspect_endpoint, audience, client_id, func_get_config_option, logger) self.client_secret = self.get_config_option( 'client_secret', is_required=True) def introspect(self, access_token): """Access the introspect API. Access the Introspect API to verify the access token by the auth method 'client_secret_post'. """ req_data = { 'client_id': self.client_id, 'client_secret': self.client_secret, 'token': access_token, 'token_type_hint': 'access_token' } http_response = self.session.request( self.introspect_endpoint, 'POST', authenticated=False, data=req_data) return http_response class TlsClientAuthClient(AbstractAuthClient): """Http client with the auth method 'tls_client_auth'.""" def introspect(self, access_token): """Access the introspect API. Access the Introspect API to verify the access token by the auth method 'tls_client_auth'. """ req_data = { 'client_id': self.client_id, 'token': access_token, 'token_type_hint': 'access_token' } http_response = self.session.request( self.introspect_endpoint, 'POST', authenticated=False, data=req_data) return http_response class PrivateKeyJwtAuthClient(AbstractAuthClient): """Http client with the auth method 'private_key_jwt'.""" def __init__(self, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): super(PrivateKeyJwtAuthClient, self).__init__( session, introspect_endpoint, audience, client_id, func_get_config_option, logger) self.jwt_key_file = self.get_config_option( 'jwt_key_file', is_required=True) self.jwt_bearer_time_out = self.get_config_option( 'jwt_bearer_time_out', is_required=True) self.jwt_algorithm = self.get_config_option( 'jwt_algorithm', is_required=True) self.logger = logger def introspect(self, access_token): """Access the introspect API. Access the Introspect API to verify the access token by the auth method 'private_key_jwt'. """ if not os.path.isfile(self.jwt_key_file): self.logger.critical('Configuration error. JWT key file is ' 'not a file. path: %s' % self.jwt_key_file) raise ConfigurationError(_('Configuration error. ' 'JWT key file is not a file.')) try: with open(self.jwt_key_file, 'r') as jwt_file: jwt_key = jwt_file.read() except Exception as e: self.logger.critical('Configuration error. Failed to read ' 'the JWT key file. %s', e) raise ConfigurationError(_('Configuration error. ' 'Failed to read the JWT key file.')) if not jwt_key: self.logger.critical('Configuration error. The JWT key file ' 'content is empty. path: %s' % self.jwt_key_file) raise ConfigurationError(_('Configuration error. The JWT key file ' 'content is empty.')) iat = round(time.time()) try: client_assertion = jwt.encode( payload={ 'jti': str(uuid.uuid4()), 'iat': str(iat), 'exp': str(iat + self.jwt_bearer_time_out), 'iss': self.client_id, 'sub': self.client_id, 'aud': self.audience}, headers={ 'typ': 'JWT', 'alg': self.jwt_algorithm}, key=jwt_key, algorithm=self.jwt_algorithm) except Exception as e: self.logger.critical('Configuration error. JWT encoding with ' 'the specified JWT key file and algorithm ' 'failed. path: %s, algorithm: %s, error: %s' % (self.jwt_key_file, self.jwt_algorithm, e)) raise ConfigurationError(_('Configuration error. JWT encoding ' 'with the specified JWT key file ' 'and algorithm failed.')) req_data = { 'client_id': self.client_id, 'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 'client_assertion': client_assertion, 'token': access_token, 'token_type_hint': 'access_token' } http_response = self.session.request( self.introspect_endpoint, 'POST', authenticated=False, data=req_data) return http_response class ClientSecretJwtAuthClient(AbstractAuthClient): """Http client with the auth method 'client_secret_jwt'.""" def __init__(self, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): super(ClientSecretJwtAuthClient, self).__init__( session, introspect_endpoint, audience, client_id, func_get_config_option, logger) self.client_secret = self.get_config_option( 'client_secret', is_required=True) self.jwt_bearer_time_out = self.get_config_option( 'jwt_bearer_time_out', is_required=True) self.jwt_algorithm = self.get_config_option( 'jwt_algorithm', is_required=True) def introspect(self, access_token): """Access the introspect API. Access the Introspect API to verify the access token by the auth method 'client_secret_jwt'. """ ita = round(time.time()) try: client_assertion = jwt.encode( payload={ 'jti': str(uuid.uuid4()), 'iat': str(ita), 'exp': str(ita + self.jwt_bearer_time_out), 'iss': self.client_id, 'sub': self.client_id, 'aud': self.audience}, headers={ 'typ': 'JWT', 'alg': self.jwt_algorithm}, key=self.client_secret, algorithm=self.jwt_algorithm) except Exception as e: self.logger.critical('Configuration error. JWT encoding with ' 'the specified client_secret and algorithm ' 'failed. algorithm: %s, error: %s' % (self.jwt_algorithm, e)) raise ConfigurationError(_('Configuration error. JWT encoding ' 'with the specified client_secret ' 'and algorithm failed.')) req_data = { 'client_id': self.client_id, 'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 'client_assertion': client_assertion, 'token': access_token, 'token_type_hint': 'access_token' } http_response = self.session.request( self.introspect_endpoint, 'POST', authenticated=False, data=req_data) return http_response _ALL_AUTH_CLIENTS = { 'client_secret_basic': ClientSecretBasicAuthClient, 'client_secret_post': ClientSecretPostAuthClient, 'tls_client_auth': TlsClientAuthClient, 'private_key_jwt': PrivateKeyJwtAuthClient, 'client_secret_jwt': ClientSecretJwtAuthClient } def _get_http_client(auth_method, session, introspect_endpoint, audience, client_id, func_get_config_option, logger): """Get an auth HTTP Client to access the OAuth2.0 Server.""" if auth_method in _ALL_AUTH_CLIENTS: return _ALL_AUTH_CLIENTS.get(auth_method)( session, introspect_endpoint, audience, client_id, func_get_config_option, logger) logger.critical('The value is incorrect for option ' 'auth_method in group [%s]' % _EXT_AUTH_CONFIG_GROUP_NAME) raise ConfigurationError(_('The configuration parameter for ' 'key "auth_method" in group [%s] ' 'is incorrect.') % _EXT_AUTH_CONFIG_GROUP_NAME) class ExternalAuth2Protocol(object): """Middleware that handles External Server OAuth2.0 authentication.""" def __init__(self, application, conf): super(ExternalAuth2Protocol, self).__init__() self._application = application self._log = logging.getLogger(conf.get('log_name', __name__)) self._log.info('Starting Keystone external_oauth2_token middleware') config_opts = [ (_EXT_AUTH_CONFIG_GROUP_NAME, _EXTERNAL_AUTH2_OPTS + loading.get_auth_common_conf_options()) ] all_opts = [(g, copy.deepcopy(o)) for g, o in config_opts] self._conf = config.Config('external_oauth2_token', _EXT_AUTH_CONFIG_GROUP_NAME, all_opts, conf) self._token_cache = self._token_cache_factory() self._session = self._create_session() self._audience = self._get_config_option('audience', is_required=True) self._introspect_endpoint = self._get_config_option( 'introspect_endpoint', is_required=True) self._auth_method = self._get_config_option( 'auth_method', is_required=True) self._client_id = self._get_config_option( 'client_id', is_required=True) self._http_client = _get_http_client( self._auth_method, self._session, self._introspect_endpoint, self._audience, self._client_id, self._get_config_option, self._log) def _token_cache_factory(self): security_strategy = self._conf.get('memcache_security_strategy') cache_kwargs = dict( cache_time=int(self._conf.get('token_cache_time')), memcached_servers=self._conf.get('memcached_servers'), use_advanced_pool=self._conf.get( 'memcache_use_advanced_pool'), dead_retry=self._conf.get('memcache_pool_dead_retry'), maxsize=self._conf.get('memcache_pool_maxsize'), unused_timeout=self._conf.get( 'memcache_pool_unused_timeout'), conn_get_timeout=self._conf.get( 'memcache_pool_conn_get_timeout'), socket_timeout=self._conf.get( 'memcache_pool_socket_timeout'), ) if security_strategy.lower() != 'none': secret_key = self._conf.get('memcache_secret_key') return _cache.SecureTokenCache(self._log, security_strategy, secret_key, **cache_kwargs) return _cache.TokenCache(self._log, **cache_kwargs) @webob.dec.wsgify() def __call__(self, req): """Handle incoming request.""" self.process_request(req) response = req.get_response(self._application) return self.process_response(response) def process_request(self, request): """Process request. :param request: Incoming request :type request: _request.AuthTokenRequest """ access_token = None if (request.authorization and request.authorization.authtype == 'Bearer'): access_token = request.authorization.params try: if not access_token: self._log.info('Unable to obtain the access token.') raise InvalidToken(_('Unable to obtain the access token.')) self._token_cache.initialize(request.environ) token_data = self._fetch_token(access_token) if (self._get_config_option('thumbprint_verify', is_required=False)): self._confirm_certificate_thumbprint( request, token_data.get('origin_token_metadata')) self._set_request_env(request, token_data) except InvalidToken as error: self._log.info('Rejecting request. ' 'Need a valid OAuth 2.0 access token. ' 'error: %s', error) message = _('The request you have made is denied, ' 'because the token is invalid.') body = {'error': { 'code': 401, 'title': 'Unauthorized', 'message': message, }} raise webob.exc.HTTPUnauthorized( body=jsonutils.dumps(body), headers=self._reject_headers, charset='UTF-8', content_type='application/json') except ForbiddenToken as error: self._log.warning('Rejecting request. ' 'The necessary information is required.' 'error: %s', error) message = _('The request you have made is denied, ' 'because the necessary information ' 'could not be parsed.') body = {'error': { 'code': 403, 'title': 'Forbidden', 'message': message, }} raise webob.exc.HTTPForbidden( body=jsonutils.dumps(body), charset='UTF-8', content_type='application/json') except ConfigurationError as error: self._log.critical('Rejecting request. ' 'The configuration parameters are incorrect. ' 'error: %s', error) message = _('The request you have made is denied, ' 'because the configuration parameters are incorrect ' 'and the token can not be verified.') body = {'error': { 'code': 500, 'title': 'Internal Server Error', 'message': message, }} raise webob.exc.HTTPServerError( body=jsonutils.dumps(body), charset='UTF-8', content_type='application/json') except ServiceError as error: self._log.warning('Rejecting request. An exception occurred and ' 'the OAuth 2.0 access token can not be ' 'verified. error: %s', error) message = _('The request you have made is denied, ' 'because an exception occurred while accessing ' 'the external authentication server ' 'for token validation.') body = {'error': { 'code': 500, 'title': 'Internal Server Error', 'message': message, }} raise webob.exc.HTTPServerError( body=jsonutils.dumps(body), charset='UTF-8', content_type='application/json') def process_response(self, response): """Process Response. Add ``WWW-Authenticate`` headers to requests that failed with ``401 Unauthenticated`` so users know where to authenticate for future requests. """ if response.status_int == 401: response.headers.extend(self._reject_headers) return response def _create_session(self, **kwargs): """Create session for HTTP access.""" kwargs.setdefault('cert', self._get_config_option( 'certfile', is_required=False)) kwargs.setdefault('key', self._get_config_option( 'keyfile', is_required=False)) kwargs.setdefault('cacert', self._get_config_option( 'cafile', is_required=False)) kwargs.setdefault('insecure', self._get_config_option( 'insecure', is_required=False)) kwargs.setdefault('timeout', self._get_config_option( 'http_connect_timeout', is_required=False)) kwargs.setdefault('user_agent', self._conf.user_agent) return session_loading.Session().load_from_options(**kwargs) def _get_config_option(self, key, is_required): """Read the value from config file by the config key.""" value = self._conf.get(key) if not value: if is_required: self._log.critical('The value is required for option %s ' 'in group [%s]' % ( key, _EXT_AUTH_CONFIG_GROUP_NAME)) raise ConfigurationError( _('Configuration error. The parameter ' 'is not set for "%s" in group [%s].') % ( key, _EXT_AUTH_CONFIG_GROUP_NAME)) else: return None else: return value @property def _reject_headers(self): """Generate WWW-Authenticate Header. When response status is 401, this method will be called to add the 'WWW-Authenticate' header to the response. """ header_val = 'Authorization OAuth 2.0 uri="%s"' % self._audience return [('WWW-Authenticate', header_val)] def _fetch_token(self, access_token): """Use access_token to get the valid token meta_data. Verify the access token through accessing the external authorization server. """ try: cached = self._token_cache.get(access_token) if cached: self._log.debug('The cached token: %s' % cached) if (not isinstance(cached, dict) or 'origin_token_metadata' not in cached): self._log.warning('The cached data is invalid. %s' % cached) raise InvalidToken(_('The token is invalid.')) origin_token_metadata = cached.get('origin_token_metadata') if not origin_token_metadata.get('active'): self._log.warning('The cached data is invalid. %s' % cached) raise InvalidToken(_('The token is invalid.')) expire_at = self._read_data_from_token( origin_token_metadata, 'mapping_expires_at', is_required=False, value_type=int) if expire_at: if int(expire_at) < int(time.time()): cached['origin_token_metadata']['active'] = False self._token_cache.set(access_token, cached) self._log.warning( 'The cached data is invalid. %s' % cached) raise InvalidToken(_('The token is invalid.')) return cached http_response = self._http_client.introspect(access_token) if http_response.status_code != 200: self._log.critical('The introspect API returns an ' 'incorrect response. ' 'response_status: %s, response_text: %s' % (http_response.status_code, http_response.text)) raise ServiceError(_('The token cannot be verified ' 'for validity.')) origin_token_metadata = http_response.json() self._log.debug('The introspect API response: %s' % origin_token_metadata) if not origin_token_metadata.get('active'): self._token_cache.set( access_token, {'origin_token_metadata': origin_token_metadata}) self._log.info('The token is invalid. response: %s' % origin_token_metadata) raise InvalidToken(_('The token is invalid.')) token_data = self._parse_necessary_info(origin_token_metadata) self._token_cache.set(access_token, token_data) return token_data except (ConfigurationError, ForbiddenToken, ServiceError, InvalidToken): raise except (ksa_exceptions.ConnectFailure, ksa_exceptions.DiscoveryFailure, ksa_exceptions.RequestTimeout) as error: self._log.critical('Unable to validate token: %s', error) raise ServiceError( _('The Introspect API service is temporarily unavailable.')) except Exception as error: self._log.critical('Unable to validate token: %s', error) raise ServiceError(_('An exception occurred during the token ' 'verification process.')) def _read_data_from_token(self, token_metadata, config_key, is_required=False, value_type=None): """Read value from token metadata. Read the necessary information from the token metadata with the config key. """ if not value_type: value_type = str meta_key = self._get_config_option(config_key, is_required=is_required) if not meta_key: return None if meta_key.find('.') >= 0: meta_value = None for temp_key in meta_key.split('.'): if not temp_key: self._log.critical('Configuration error. ' 'config_key: %s , meta_key: %s ' % (config_key, meta_key)) raise ConfigurationError( _('Failed to parse the necessary information ' 'for the field "%s".') % meta_key) if not meta_value: meta_value = token_metadata.get(temp_key) else: if not isinstance(meta_value, dict): self._log.warning( 'Failed to parse the necessary information. ' 'The meta_value is not of type dict.' 'config_key: %s , meta_key: %s, value: %s' % (config_key, meta_key, meta_value)) raise ForbiddenToken( _('Failed to parse the necessary information ' 'for the field "%s".') % meta_key) meta_value = meta_value.get(temp_key) else: meta_value = token_metadata.get(meta_key) if not meta_value: if is_required: self._log.warning( 'Failed to parse the necessary information. ' 'The meta value is required.' 'config_key: %s , meta_key: %s, value: %s, need_type: %s' % (config_key, meta_key, meta_value, value_type)) raise ForbiddenToken(_('Failed to parse the necessary ' 'information for the field "%s".') % meta_key) else: meta_value = None else: if not isinstance(meta_value, value_type): self._log.warning( 'Failed to parse the necessary information. ' 'The meta value is of an incorrect type.' 'config_key: %s , meta_key: %s, value: %s, need_type: %s' % (config_key, meta_key, meta_value, value_type)) raise ForbiddenToken(_('Failed to parse the necessary ' 'information for the field "%s".') % meta_key) return meta_value def _parse_necessary_info(self, token_metadata): """Parse the necessary information from the token metadata.""" token_data = dict() token_data['origin_token_metadata'] = token_metadata roles = self._read_data_from_token(token_metadata, 'mapping_roles', is_required=True) is_admin = 'false' if 'admin' in roles.lower().split(','): is_admin = 'true' token_data['roles'] = roles token_data['is_admin'] = is_admin system_scope = self._read_data_from_token( token_metadata, 'mapping_system_scope', is_required=False, value_type=bool) if system_scope: token_data['system_scope'] = 'all' else: project_id = self._read_data_from_token( token_metadata, 'mapping_project_id', is_required=False) if project_id: token_data['project_id'] = project_id token_data['project_name'] = self._read_data_from_token( token_metadata, 'mapping_project_name', is_required=True) token_data['project_domain_id'] = self._read_data_from_token( token_metadata, 'mapping_project_domain_id', is_required=True) token_data['project_domain_name'] = self._read_data_from_token( token_metadata, 'mapping_project_domain_name', is_required=True) else: token_data['domain_id'] = self._read_data_from_token( token_metadata, 'mapping_project_domain_id', is_required=True) token_data['domain_name'] = self._read_data_from_token( token_metadata, 'mapping_project_domain_name', is_required=True) token_data['user_id'] = self._read_data_from_token( token_metadata, 'mapping_user_id', is_required=True) token_data['user_name'] = self._read_data_from_token( token_metadata, 'mapping_user_name', is_required=True) token_data['user_domain_id'] = self._read_data_from_token( token_metadata, 'mapping_user_domain_id', is_required=True) token_data['user_domain_name'] = self._read_data_from_token( token_metadata, 'mapping_user_domain_name', is_required=True) return token_data def _get_client_certificate(self, request): """Get the client certificate from request environ or socket.""" try: pem_client_cert = request.environ.get('SSL_CLIENT_CERT') if pem_client_cert: peer_cert = ssl.PEM_cert_to_DER_cert(pem_client_cert) else: wsgi_input = request.environ.get('wsgi.input') if not wsgi_input: self._log.warn('Unable to obtain the client certificate. ' 'The object for wsgi_input is none.') raise InvalidToken(_('Unable to obtain the client ' 'certificate.')) socket = wsgi_input.get_socket() if not socket: self._log.warn('Unable to obtain the client certificate. ' 'The object for socket is none.') raise InvalidToken(_('Unable to obtain the client ' 'certificate.')) peer_cert = socket.getpeercert(binary_form=True) if not peer_cert: self._log.warn('Unable to obtain the client certificate. ' 'The object for peer_cert is none.') raise InvalidToken(_('Unable to obtain the client ' 'certificate.')) return peer_cert except InvalidToken: raise except Exception as error: self._log.warn('Unable to obtain the client certificate. %s' % error) raise InvalidToken(_('Unable to obtain the client certificate.')) def _confirm_certificate_thumbprint(self, request, origin_token_metadata): """Check if the thumbprint in the token is valid.""" peer_cert = self._get_client_certificate(request) try: thumb_sha256 = hashlib.sha256(peer_cert).digest() cert_thumb = jwt.utils.base64url_encode(thumb_sha256).decode( 'ascii') except Exception as error: self._log.warn('An Exception occurred. %s' % error) raise InvalidToken(_('Can not generate the thumbprint.')) token_thumb = origin_token_metadata.get('cnf', {}).get('x5t#S256') if cert_thumb != token_thumb: self._log.warn('The two thumbprints do not match. ' 'token_thumbprint: %s, certificate_thumbprint %s' % (token_thumb, cert_thumb)) raise InvalidToken(_('The two thumbprints do not match.')) def _set_request_env(self, request, token_data): """Set request.environ with the necessary information.""" request.environ['external.token_info'] = token_data request.environ['HTTP_X_IDENTITY_STATUS'] = 'Confirmed' request.environ['HTTP_X_ROLES'] = token_data.get('roles') request.environ['HTTP_X_ROLE'] = token_data.get('roles') request.environ['HTTP_X_USER_ID'] = token_data.get('user_id') request.environ['HTTP_X_USER_NAME'] = token_data.get('user_name') request.environ['HTTP_X_USER_DOMAIN_ID'] = token_data.get( 'user_domain_id') request.environ['HTTP_X_USER_DOMAIN_NAME'] = token_data.get( 'user_domain_name') if token_data.get('is_admin') == 'true': request.environ['HTTP_X_IS_ADMIN_PROJECT'] = token_data.get( 'is_admin') request.environ['HTTP_X_USER'] = token_data.get('user_name') if token_data.get('system_scope'): request.environ['HTTP_OPENSTACK_SYSTEM_SCOPE'] = token_data.get( 'system_scope' ) elif token_data.get('project_id'): request.environ['HTTP_X_PROJECT_ID'] = token_data.get('project_id') request.environ['HTTP_X_PROJECT_NAME'] = token_data.get( 'project_name') request.environ['HTTP_X_PROJECT_DOMAIN_ID'] = token_data.get( 'project_domain_id') request.environ['HTTP_X_PROJECT_DOMAIN_NAME'] = token_data.get( 'project_domain_name') request.environ['HTTP_X_TENANT_ID'] = token_data.get('project_id') request.environ['HTTP_X_TENANT_NAME'] = token_data.get( 'project_name') request.environ['HTTP_X_TENANT'] = token_data.get('project_id') else: request.environ['HTTP_X_DOMAIN_ID'] = token_data.get('domain_id') request.environ['HTTP_X_DOMAIN_NAME'] = token_data.get( 'domain_name') self._log.debug('The access token data is %s.' % jsonutils.dumps( token_data)) def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return ExternalAuth2Protocol(app, conf) return auth_filter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/fixture.py0000664000175000017500000000722100000000000023464 0ustar00zuulzuul00000000000000# 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 fixtures from keystoneauth1 import fixture as client_fixtures from oslo_log import log as logging from oslo_utils import timeutils from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _exceptions _LOG = logging.getLogger(__name__) class AuthTokenFixture(fixtures.Fixture): """Overrides what keystonemiddleware will return to the app behind it.""" def setUp(self): super(AuthTokenFixture, self).setUp() # Ensure that the initialized token data is cleaned up self._token_data = {} self.addCleanup(self._token_data.clear) _LOG.info('Using Testing AuthTokenFixture...') self.useFixture(fixtures.MockPatchObject( auth_token.AuthProtocol, 'fetch_token', self.fetch_token)) @property def tokens(self): return self._token_data.keys() def add_token_data(self, token_id=None, expires=None, user_id=None, user_name=None, user_domain_id=None, user_domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, role_list=None, is_v2=False): """Add token data to the auth_token fixture.""" if not token_id: token_id = uuid.uuid4().hex if not role_list: role_list = [] if is_v2: token = client_fixtures.V2Token( token_id=token_id, expires=expires, tenant_id=project_id, tenant_name=project_name, user_id=user_id, user_name=user_name) else: token = client_fixtures.V3Token( expires=expires, user_id=user_id, user_name=user_name, user_domain_id=user_domain_id, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, user_domain_name=user_domain_name, project_domain_name=project_domain_name) for role in role_list: token.add_role(name=role) self.add_token(token, token_id=token_id) def add_token(self, token_data, token_id=None): """Add an existing token to the middleware. :param token_data: token data to add to the fixture :type token_data: dict :param token_id: the token ID to add this token as :type token_id: str :returns: The token_id that the token was added as. :rtype: str """ if not token_id: token_id = uuid.uuid4().hex self._token_data[token_id] = token_data return token_id def fetch_token(self, token, **kwargs): """Low level replacement of fetch_token for AuthProtocol.""" token_data = self._token_data.get(token, {}) if token_data: self._assert_token_not_expired(token_data.expires) return token_data raise _exceptions.InvalidToken() def _assert_token_not_expired(self, token_expires): if timeutils.utcnow() > timeutils.normalize_time(token_expires): raise _exceptions.InvalidToken() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/i18n.py0000664000175000017500000000154200000000000022555 0ustar00zuulzuul00000000000000# 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. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n as i18n _translators = i18n.TranslatorFactory(domain='keystonemiddleware') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9807508 keystonemiddleware-10.9.0/keystonemiddleware/locale/0000775000175000017500000000000000000000000022661 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9807508 keystonemiddleware-10.9.0/keystonemiddleware/locale/en_GB/0000775000175000017500000000000000000000000023633 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0007513 keystonemiddleware-10.9.0/keystonemiddleware/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025420 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/locale/en_GB/LC_MESSAGES/keystonemiddleware.po0000664000175000017500000001210500000000000031656 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2024. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2024-06-28 17:34+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2024-07-18 10:19+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "Access key not provided" msgstr "Access key not provided" msgid "An exception occurred during the token verification process." msgstr "An exception occurred during the token verification process." msgid "Can not generate the thumbprint." msgstr "Can not generate the thumbprint." msgid "Configuration error. Failed to read the JWT key file." msgstr "Configuration error. Failed to read the JWT key file." msgid "" "Configuration error. JWT encoding with the specified JWT key file and " "algorithm failed." msgstr "" "Configuration error. JWT encoding with the specified JWT key file and " "algorithm failed." msgid "" "Configuration error. JWT encoding with the specified client_secret and " "algorithm failed." msgstr "" "Configuration error. JWT encoding with the specified client_secret and " "algorithm failed." msgid "Configuration error. JWT key file is not a file." msgstr "Configuration error. JWT key file is not a file." msgid "Configuration error. The JWT key file content is empty." msgstr "Configuration error. The JWT key file content is empty." #, python-format msgid "Configuration error. The parameter is not set for \"%s\" in group [%s]." msgstr "" "Configuration error. The parameter is not set for \"%s\" in group [%s]." msgid "Encrypted data appears to be corrupted." msgstr "Encrypted data appears to be corrupted." #, python-format msgid "Error response from keystone: %s" msgstr "Error response from Keystone: %s" msgid "Failed to fetch token data from identity server" msgstr "Failed to fetch token data from identity server" #, python-format msgid "Failed to parse the necessary information for the field \"%s\"." msgstr "Failed to parse the necessary information for the field \"%s\"." msgid "Failure parsing response from keystone" msgstr "Failure parsing response from Keystone" msgid "Identity server rejected authorization necessary to fetch token data" msgstr "Identity server rejected authorisation necessary to fetch token data" msgid "Invalid MAC; data appears to be corrupted." msgstr "Invalid MAC; data appears to be corrupted." msgid "Invalid version asked for in auth_token plugin" msgstr "Invalid version asked for in auth_token plugin" msgid "No compatible apis supported by server" msgstr "No compatible APIs supported by server" msgid "Signature not provided" msgstr "Signature not provided" msgid "The Introspect API service is temporarily unavailable." msgstr "The Introspect API service is temporarily unavailable." #, python-format msgid "" "The configuration parameter for key \"auth_method\" in group [%s] is " "incorrect." msgstr "" "The configuration parameter for key \"auth_method\" in group [%s] is " "incorrect." msgid "" "The request you have made is denied, because an exception occurred while " "accessing the external authentication server for token validation." msgstr "" "The request you have made is denied, because an exception occurred while " "accessing the external authentication server for token validation." msgid "" "The request you have made is denied, because the configuration parameters " "are incorrect and the token can not be verified." msgstr "" "The request you have made is denied, because the configuration parameters " "are incorrect and the token can not be verified." msgid "" "The request you have made is denied, because the necessary information could " "not be parsed." msgstr "" "The request you have made is denied, because the necessary information could " "not be parsed." msgid "The request you have made is denied, because the token is invalid." msgstr "The request you have made is denied, because the token is invalid." msgid "The request you have made requires authentication." msgstr "The request you have made requires authentication." msgid "The token cannot be verified for validity." msgstr "The token cannot be verified for validity." msgid "The token is invalid." msgstr "The token is invalid." msgid "The two thumbprints do not match." msgstr "The two thumbprints do not match." msgid "Token authorization failed" msgstr "Token authorisation failed" msgid "Unable to determine service tenancy." msgstr "Unable to determine service tenancy." msgid "Unable to obtain the access token." msgstr "Unable to obtain the access token." msgid "Unable to obtain the client certificate." msgstr "Unable to obtain the client certificate." msgid "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" msgstr "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9807508 keystonemiddleware-10.9.0/keystonemiddleware/locale/ko_KR/0000775000175000017500000000000000000000000023666 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0007513 keystonemiddleware-10.9.0/keystonemiddleware/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000000000000000025453 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/locale/ko_KR/LC_MESSAGES/keystonemiddleware.po0000664000175000017500000000416500000000000031720 0ustar00zuulzuul00000000000000# Ian Y. Choi , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2019-12-21 02:49+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-01-23 03:52+0000\n" "Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "Access key not provided" msgstr "Access key가 없습니다" msgid "Encrypted data appears to be corrupted." msgstr "암호화된 데이터가 손상된 것으로 보입니다." #, python-format msgid "Error response from keystone: %s" msgstr "keystone 오류 응답: %s" msgid "Failed to fetch token data from identity server" msgstr "인증 서버로부터 토큰 데이터를 가져올 수 없습니다" msgid "Failure parsing response from keystone" msgstr "keystone 응답 파싱 실패" msgid "Identity server rejected authorization necessary to fetch token data" msgstr "토큰 데이터를 가져오기 위해 필수인 인증을 인증 서버에서 거절" msgid "Invalid MAC; data appears to be corrupted." msgstr "잘못된 MAC; 데이터가 손상된 것으로 보입니다." msgid "Invalid version asked for in auth_token plugin" msgstr "auth_token 플러그인에서 잘못된 버전 요청" msgid "No compatible apis supported by server" msgstr "서버에서 지원하는 호환 api가 없습니다" msgid "Signature not provided" msgstr "Signature가 없습니다" msgid "The request you have made requires authentication." msgstr "제공한 요청은 인증을 필요로 합니다." msgid "Token authorization failed" msgstr "Token 인증 실패" msgid "Unable to determine service tenancy." msgstr "서비스 tenancy를 결정할 수 없습니다." msgid "" "memcache_secret_key must be defined when a memcache_security_strategy is " "defined" msgstr "" "memcache_security_strategy 가 정의되어 있을 때 memcache_secret_key 를 정의해" "야 합니다" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/oauth2_mtls_token.py0000664000175000017500000001372500000000000025445 0ustar00zuulzuul00000000000000# Copyright 2022 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 base64 import hashlib import ssl import webob from oslo_log import log as logging from oslo_serialization import jsonutils from keystonemiddleware.auth_token import _user_plugin from keystonemiddleware.auth_token import AuthProtocol from keystonemiddleware import exceptions from keystonemiddleware.i18n import _ class OAuth2mTlsProtocol(AuthProtocol): """Middleware that handles OAuth2.0 mutual-TLS client authentication.""" def __init__(self, app, conf): log = logging.getLogger(conf.get('log_name', __name__)) log.info('Starting Keystone oauth2_mls_token middleware') super(OAuth2mTlsProtocol, self).__init__(app, conf) def _confirm_certificate_thumbprint(self, token_thumb, peer_cert): """Check if the thumbprint in the token is valid. :rtype: if the thumbprint is valid """ try: cert_pem = ssl.DER_cert_to_PEM_cert(peer_cert) thumb_sha256 = hashlib.sha256(cert_pem.encode('ascii')).digest() cert_thumb = base64.urlsafe_b64encode(thumb_sha256).decode('ascii') if cert_thumb == token_thumb: is_valid = True else: self.log.info('The two thumbprints do not match.') is_valid = False except Exception as error: self.log.exception(error) is_valid = False return is_valid def _is_valid_access_token(self, request): """Check the OAuth2.0 certificate-bound access token. :param request: Incoming request :rtype: if the access token is valid """ try: wsgi_input = request.environ.get("wsgi.input") if not wsgi_input: self.log.warn('Unable to obtain the client certificate.') return False sock = wsgi_input.get_socket() if not sock: self.log.warn('Unable to obtain the client certificate.') return False peer_cert = sock.getpeercert(binary_form=True) if not peer_cert: self.log.warn('Unable to obtain the client certificate.') return False except Exception as error: self.log.warn('Unable to obtain the client certificate. %s' % str(error)) return False access_token = None if (request.authorization and request.authorization.authtype == 'Bearer'): access_token = request.authorization.params if not access_token: self.log.info('Unable to obtain the token.') return False try: token_data, user_auth_ref = self._do_fetch_token( access_token, allow_expired=False) self._validate_token(user_auth_ref, allow_expired=False) token = token_data.get('token') oauth2_cred = token.get('oauth2_credential') if not oauth2_cred: self.log.info( 'Invalid OAuth2.0 certificate-bound access token: ' 'The token is not an OAuth2.0 credential access token.') return False token_thumb = oauth2_cred.get("x5t#S256") if self._confirm_certificate_thumbprint(token_thumb, peer_cert): self._confirm_token_bind(user_auth_ref, request) request.token_info = token_data request.token_auth = _user_plugin.UserAuthPlugin( user_auth_ref, None) return True else: self.log.info( 'Invalid OAuth2.0 certificate-bound access token: ' 'the access token dose not match the client certificate.') return False except exceptions.KeystoneMiddlewareException as err: self.log.info('Invalid OAuth2.0 certificate-bound access token: %s' % str(err)) return False def process_request(self, request): """Process request. :param request: Incoming request :type request: _request.AuthTokenRequest """ request.remove_auth_headers() self._token_cache.initialize(request.environ) if (not self._is_valid_access_token(request) or "keystone.token_info" not in request.environ or "token" not in request.environ["keystone.token_info"]): self.log.info('Rejecting request') message = _('The request you have made requires authentication.') body = {'error': { 'code': 401, 'title': 'Unauthorized', 'message': message, }} raise webob.exc.HTTPUnauthorized( body=jsonutils.dumps(body), headers=self._reject_auth_headers, charset='UTF-8', content_type='application/json') request.set_user_headers(request.token_auth.user) request.set_service_catalog_headers(request.token_auth.user) request.token_auth._auth = self._auth request.token_auth._session = self._session self.log.debug('Accepting request and inited all env fields.') def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return OAuth2mTlsProtocol(app, conf) return auth_filter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/oauth2_token.py0000664000175000017500000000743400000000000024406 0ustar00zuulzuul00000000000000# Copyright 2022 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 webob from oslo_log import log as logging from oslo_serialization import jsonutils from keystonemiddleware.auth_token import _user_plugin from keystonemiddleware.auth_token import AuthProtocol from keystonemiddleware import exceptions from keystonemiddleware.i18n import _ _LOG = logging.getLogger(__name__) class OAuth2Protocol(AuthProtocol): """Middleware that handles OAuth2.0 client credentials authentication.""" def __init__(self, app, conf): log = logging.getLogger(conf.get('log_name', __name__)) log.info('Starting Keystone oauth2_token middleware') super(OAuth2Protocol, self).__init__(app, conf) def _is_valid_access_token(self, request): """Check if the request contains an OAuth2.0 access token. :param request: Incoming request :type request: _request.AuthTokenRequest """ access_token = None if (request.authorization and request.authorization.authtype == 'Bearer'): access_token = request.authorization.params if access_token: try: token_data, user_auth_ref = self._do_fetch_token( access_token, allow_expired=False) self._validate_token(user_auth_ref, allow_expired=False) token = token_data['token'] self.validate_allowed_request(request, token) self._confirm_token_bind(user_auth_ref, request) request.token_info = token_data request.token_auth = _user_plugin.UserAuthPlugin( user_auth_ref, None) return True except exceptions.KeystoneMiddlewareException as err: _LOG.info('Invalid OAuth2.0 access token: %s' % str(err)) return False def process_request(self, request): """Process request. :param request: Incoming request :type request: _request.AuthTokenRequest """ request.remove_auth_headers() self._token_cache.initialize(request.environ) if (not self._is_valid_access_token(request) or "keystone.token_info" not in request.environ or "token" not in request.environ["keystone.token_info"]): _LOG.info('Rejecting request') message = _('The request you have made requires authentication.') body = {'error': { 'code': 401, 'title': 'Unauthorized', 'message': message, }} raise webob.exc.HTTPUnauthorized( body=jsonutils.dumps(body), headers=self._reject_auth_headers, charset='UTF-8', content_type='application/json') request.set_user_headers(request.token_auth.user) request.set_service_catalog_headers(request.token_auth.user) request.token_auth._auth = self._auth request.token_auth._session = self._session def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return OAuth2Protocol(app, conf) return auth_filter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/opts.py0000664000175000017500000000134400000000000022763 0ustar00zuulzuul00000000000000# Copyright (c) 2014 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. __all__ = ( 'list_auth_token_opts', ) from keystonemiddleware import auth_token def list_auth_token_opts(): return auth_token.list_opts() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/s3_token.py0000664000175000017500000002262500000000000023530 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011,2012 Akira YOSHIYAMA # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This source code is based ./auth_token.py and ./ec2_token.py. # See them for their copyright. """ S3 Token Middleware. This WSGI component: * Gets a request from the swift3 middleware with an S3 Authorization access key. * Validates s3 token in Keystone. * Transforms the account name to AUTH_%(tenant_name). """ import webob from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import strutils import requests s3_opts = [ cfg.IntOpt('timeout', default=60, help='Timeout to obtain token.'), ] CONF = cfg.CONF CONF.register_opts(s3_opts, group='s3_token') PROTOCOL_NAME = 'S3 Token Authentication' class ServiceError(Exception): pass class S3Token(object): """Middleware that handles S3 authentication.""" def __init__(self, app, conf): """Common initialization code.""" self._app = app self._logger = logging.getLogger(conf.get('log_name', __name__)) self._logger.debug('Starting the %s component', PROTOCOL_NAME) self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_') # where to find the auth service (we use this to validate tokens) self._request_uri = conf.get('www_authenticate_uri') auth_uri = conf.get('auth_uri') if not self._request_uri and auth_uri: self._logger.warning( "Use of the auth_uri option was deprecated " "in the Queens release in favor of www_authenticate_uri. This " "option will be removed in the S release.") self._request_uri = auth_uri if not self._request_uri: self._logger.warning( "Use of the auth_host, auth_port, and auth_protocol " "configuration options was deprecated in the Newton release " "in favor of www_authenticate_uri. These options will be " "removed in the S release.") auth_host = conf.get('auth_host') auth_port = int(conf.get('auth_port', 35357)) auth_protocol = conf.get('auth_protocol', 'https') self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host, auth_port) # SSL insecure = strutils.bool_from_string(conf.get('insecure', False)) cert_file = conf.get('certfile') key_file = conf.get('keyfile') if insecure: self._verify = False elif cert_file and key_file: self._verify = (cert_file, key_file) elif cert_file: self._verify = cert_file else: self._verify = None def _deny_request(self, code): error_table = { 'AccessDenied': (401, 'Access denied'), 'InvalidURI': (400, 'Could not parse the specified URI'), } resp = webob.Response(content_type='text/xml') resp.status = error_table[code][0] error_msg = ('\r\n' '\r\n %s\r\n ' '%s\r\n\r\n' % (code, error_table[code][1])) error_msg = error_msg.encode() resp.body = error_msg return resp def _json_request(self, creds_json): headers = {'Content-Type': 'application/json'} try: response = requests.post('%s/v3/s3tokens' % self._request_uri, headers=headers, data=creds_json, verify=self._verify, timeout=CONF.s3_token.timeout) except requests.exceptions.RequestException as e: self._logger.info('HTTP connection exception: %s', e) resp = self._deny_request('InvalidURI') raise ServiceError(resp) if response.status_code < 200 or response.status_code >= 300: self._logger.debug('Keystone reply error: status=%s reason=%s', response.status_code, response.reason) resp = self._deny_request('AccessDenied') raise ServiceError(resp) return response def __call__(self, environ, start_response): """Handle incoming request. authenticate and send downstream.""" req = webob.Request(environ) self._logger.debug('Calling S3Token middleware.') try: parts = strutils.split_path(req.path, 1, 4, True) version, account, container, obj = parts except ValueError: msg = 'Not a path query, skipping.' self._logger.debug(msg) return self._app(environ, start_response) # Read request signature and access id. if 'Authorization' not in req.headers: msg = 'No Authorization header. skipping.' self._logger.debug(msg) return self._app(environ, start_response) token = req.headers.get('X-Auth-Token', req.headers.get('X-Storage-Token')) if not token: msg = 'You did not specify an auth or a storage token. skipping.' self._logger.debug(msg) return self._app(environ, start_response) auth_header = req.headers['Authorization'] try: access, signature = auth_header.split(' ')[-1].rsplit(':', 1) except ValueError: msg = 'You have an invalid Authorization header: %s' self._logger.debug(msg, auth_header) return self._deny_request('InvalidURI')(environ, start_response) # NOTE(chmou): This is to handle the special case with nova # when we have the option s3_affix_tenant. We will force it to # connect to another account than the one # authenticated. Before people start getting worried about # security, I should point that we are connecting with # username/token specified by the user but instead of # connecting to its own account we will force it to go to an # another account. In a normal scenario if that user don't # have the reseller right it will just fail but since the # reseller account can connect to every account it is allowed # by the swift_auth middleware. force_tenant = None if ':' in access: access, force_tenant = access.split(':') # Authenticate request. creds = {'credentials': {'access': access, 'token': token, 'signature': signature}} creds_json = jsonutils.dumps(creds) self._logger.debug('Connecting to Keystone sending this JSON: %s', creds_json) # NOTE(vish): We could save a call to keystone by having # keystone return token, tenant, user, and roles # from this call. # # NOTE(chmou): We still have the same problem we would need to # change token_auth to detect if we already # identified and not doing a second query and just # pass it through to swiftauth in this case. try: resp = self._json_request(creds_json) except ServiceError as e: resp = e.args[0] msg = 'Received error, exiting middleware with error: %s' self._logger.debug(msg, resp.status_code) return resp(environ, start_response) self._logger.debug('Keystone Reply: Status: %d, Output: %s', resp.status_code, resp.content) try: identity_info = resp.json() token_id = str(identity_info['access']['token']['id']) tenant = identity_info['access']['token']['tenant'] except (ValueError, KeyError): error = 'Error on keystone reply: %d %s' self._logger.debug(error, resp.status_code, resp.content) return self._deny_request('InvalidURI')(environ, start_response) req.headers['X-Auth-Token'] = token_id tenant_to_connect = force_tenant or tenant['id'] self._logger.debug('Connecting with tenant: %s', tenant_to_connect) new_tenant_name = '%s%s' % (self._reseller_prefix, tenant_to_connect) environ['PATH_INFO'] = environ['PATH_INFO'].replace(account, new_tenant_name) return self._app(environ, start_response) def filter_factory(global_conf, **local_conf): """Return a WSGI filter app for use with paste.deploy.""" conf = global_conf.copy() conf.update(local_conf) def auth_filter(app): return S3Token(app, conf) return auth_filter ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0007513 keystonemiddleware-10.9.0/keystonemiddleware/tests/0000775000175000017500000000000000000000000022564 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/__init__.py0000664000175000017500000000000000000000000024663 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0047514 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/0000775000175000017500000000000000000000000023543 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/__init__.py0000664000175000017500000000000000000000000025642 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0047514 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/0000775000175000017500000000000000000000000024651 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/__init__.py0000664000175000017500000000000000000000000026750 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/base.py0000664000175000017500000000575100000000000026145 0ustar00zuulzuul00000000000000# 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 cfg_fixture from oslo_messaging import conffixture as msg_fixture from oslotest import createfile import webob.dec from keystonemiddleware import audit from keystonemiddleware.tests.unit import utils audit_map_content = """ [custom_actions] reboot = start/reboot os-migrations/get = read [path_keywords] action = None os-hosts = host os-migrations = None reboot = None servers = server [service_endpoints] compute = service/compute """ class BaseAuditMiddlewareTest(utils.MiddlewareTestCase): PROJECT_NAME = 'keystonemiddleware' def setUp(self): super(BaseAuditMiddlewareTest, self).setUp() self.audit_map_file_fixture = self.useFixture( createfile.CreateFileWithContent('audit', audit_map_content)) self.cfg = self.useFixture(cfg_fixture.Config()) self.msg = self.useFixture(msg_fixture.ConfFixture(self.cfg.conf)) self.cfg.conf([], project=self.PROJECT_NAME) def create_middleware(self, cb, **kwargs): @webob.dec.wsgify def _do_cb(req): return cb(req) kwargs.setdefault('audit_map_file', self.audit_map) kwargs.setdefault('service_name', 'pycadf') return audit.AuditMiddleware(_do_cb, **kwargs) @property def audit_map(self): return self.audit_map_file_fixture.path @staticmethod def get_environ_header(req_type=None): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774", "id": "resource_id"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed'} if req_type: env_headers['REQUEST_METHOD'] = req_type return env_headers ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/test_audit_api.py0000664000175000017500000004732700000000000030236 0ustar00zuulzuul00000000000000# 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 pycadf import cadftaxonomy as taxonomy import webob from keystonemiddleware import audit from keystonemiddleware.tests.unit.audit import base class AuditApiLogicTest(base.BaseAuditMiddlewareTest): def get_payload(self, method, url, audit_map=None, body=None, environ=None): audit_map = audit_map or self.audit_map environ = environ or self.get_environ_header() req = webob.Request.blank(url, body=body, method=method, environ=environ, remote_addr='192.168.0.1') middleware = audit.OpenStackAuditApi(audit_map) return middleware._create_event(req).as_dict() def test_get_list(self): path = '/v2/' + str(uuid.uuid4()) + '/servers' url = 'http://admin_host:8774' + path payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['typeURI'], 'http://schemas.dmtf.org/cloud/audit/1.0/event') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['eventType'], 'activity') self.assertEqual(payload['target']['name'], 'nova') self.assertEqual(payload['target']['id'], 'resource_id') self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(len(payload['target']['addresses']), 3) self.assertEqual(payload['target']['addresses'][0]['name'], 'admin') self.assertEqual(payload['target']['addresses'][0]['url'], 'http://admin_host:8774') self.assertEqual(payload['initiator']['id'], 'user_id') self.assertEqual(payload['initiator']['name'], 'user_name') self.assertEqual(payload['initiator']['project_id'], 'tenant_id') self.assertEqual(payload['initiator']['host']['address'], '192.168.0.1') self.assertEqual(payload['initiator']['typeURI'], 'service/security/account/user') self.assertNotEqual(payload['initiator']['credential']['token'], 'token') self.assertEqual(payload['initiator']['credential']['identity_status'], 'Confirmed') self.assertNotIn('reason', payload) self.assertNotIn('reporterchain', payload) self.assertEqual(payload['observer']['id'], 'target') self.assertEqual(path, payload['requestPath']) def test_get_read(self): url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/server') self.assertEqual(payload['action'], 'read') self.assertEqual(payload['outcome'], 'pending') def test_get_unknown_endpoint(self): url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['target']['name'], 'unknown') self.assertEqual(payload['target']['id'], 'unknown') self.assertEqual(payload['target']['typeURI'], 'unknown') def test_get_unknown_endpoint_default_set(self): with open(self.audit_map, "w") as f: f.write("[DEFAULT]\n") f.write("target_endpoint_type = compute\n") f.write("[path_keywords]\n") f.write("servers = server\n\n") f.write("[service_endpoints]\n") f.write("compute = service/compute") url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url) self.assertEqual(payload['action'], 'read/list') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['target']['name'], 'nova') self.assertEqual(payload['target']['id'], 'resource_id') self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') def test_put(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('PUT', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'update') self.assertEqual(payload['outcome'], 'pending') def test_delete(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('DELETE', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'delete') self.assertEqual(payload['outcome'], 'pending') def test_head(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('HEAD', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'read') self.assertEqual(payload['outcome'], 'pending') def test_post_update(self): url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/server') self.assertEqual(payload['action'], 'update') self.assertEqual(payload['outcome'], 'pending') def test_post_create(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers') self.assertEqual(payload['action'], 'create') self.assertEqual(payload['outcome'], 'pending') def test_post_action(self): url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex body = b'{"createImage" : {"name" : "new-image","metadata": ' \ b'{"ImageType": "Gold","ImageVersion": "2.0"}}}' payload = self.get_payload('POST', url, body=body) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/action') self.assertEqual(payload['action'], 'update/createImage') self.assertEqual(payload['outcome'], 'pending') def test_post_empty_body_action(self): url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/servers/action') self.assertEqual(payload['action'], 'create') self.assertEqual(payload['outcome'], 'pending') def test_custom_action(self): url = 'http://admin_host:8774/v2/%s/os-hosts/%s/reboot' % ( uuid.uuid4().hex, uuid.uuid4().hex) payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-hosts/host/reboot') self.assertEqual(payload['action'], 'start/reboot') self.assertEqual(payload['outcome'], 'pending') def test_custom_action_complex(self): url = 'http://admin_host:8774/v2/%s/os-migrations' % uuid.uuid4().hex payload = self.get_payload('GET', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-migrations') self.assertEqual(payload['action'], 'read') payload = self.get_payload('POST', url) self.assertEqual(payload['target']['typeURI'], 'service/compute/os-migrations') self.assertEqual(payload['action'], 'create') def test_response_mod_msg(self): url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' req = webob.Request.blank(url, environ=self.get_environ_header('GET'), remote_addr='192.168.0.1') req.environ['audit.context'] = {} middleware = self.create_simple_middleware() middleware._process_request(req) payload = req.environ['cadf_event'].as_dict() middleware._process_response(req, webob.Response()) payload2 = req.environ['cadf_event'].as_dict() self.assertEqual(payload['id'], payload2['id']) self.assertEqual(payload['tags'], payload2['tags']) self.assertEqual(payload2['outcome'], 'success') self.assertEqual(payload2['reason']['reasonType'], 'HTTP') self.assertEqual(payload2['reason']['reasonCode'], '200') self.assertEqual(len(payload2['reporterchain']), 1) self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], 'target') def test_missing_catalog_endpoint_id(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['target']['id'], 'nova') def test_endpoint_missing_internal_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "publicURL": "http://public_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][1]['url']), "unknown") def test_endpoint_missing_public_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host:8774", "region": "RegionOne", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][2]['url']), "unknown") def test_endpoint_missing_admin_url(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"region": "RegionOne", "publicURL": "http://public_host:8774", "internalURL": "http://internal_host:8774"}], "type": "compute", "name": "nova"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual((payload['target']['addresses'][0]['url']), "unknown") def test_service_with_no_endpoints(self): env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [], "type": "foo", "name": "bar"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers' payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['target']['name'], "unknown") def test_endpoint_no_service_port(self): with open(self.audit_map, "w") as f: f.write("[DEFAULT]\n") f.write("target_endpoint_type = load-balancer\n") f.write("[path_keywords]\n") f.write("loadbalancers = loadbalancer\n\n") f.write("[service_endpoints]\n") f.write("load-balancer = service/load-balancer") env_headers = {'HTTP_X_SERVICE_CATALOG': '''[{"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host/compute", "region": "RegionOne", "publicURL": "http://public_host/compute"}], "type": "compute", "name": "nova"}, {"endpoints_links": [], "endpoints": [{"adminURL": "http://admin_host/load-balancer", "region": "RegionOne", "publicURL": "http://public_host/load-balancer"}], "type": "load-balancer", "name": "octavia"}]''', 'HTTP_X_USER_ID': 'user_id', 'HTTP_X_USER_NAME': 'user_name', 'HTTP_X_AUTH_TOKEN': 'token', 'HTTP_X_PROJECT_ID': 'tenant_id', 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'REQUEST_METHOD': 'GET'} url = ('http://admin_host/load-balancer/v2/loadbalancers/' + str(uuid.uuid4())) payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['target']['id'], 'octavia') def test_no_auth_token(self): # Test cases where API requests such as Swift list public containers # which does not require an auth token. In these cases, CADF event # should have the defaults (i.e taxonomy.UNKNOWN) instead of raising # an exception. env_headers = {'HTTP_X_IDENTITY_STATUS': 'Invalid', 'REQUEST_METHOD': 'GET'} path = '/v1/' + str(uuid.uuid4()) url = 'https://23.253.72.207' + path payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['action'], 'read') self.assertEqual(payload['typeURI'], 'http://schemas.dmtf.org/cloud/audit/1.0/event') self.assertEqual(payload['outcome'], 'pending') self.assertEqual(payload['eventType'], 'activity') self.assertEqual(payload['target']['name'], taxonomy.UNKNOWN) self.assertEqual(payload['target']['id'], taxonomy.UNKNOWN) self.assertEqual(payload['target']['typeURI'], taxonomy.UNKNOWN) self.assertNotIn('addresses', payload['target']) self.assertEqual(payload['initiator']['id'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['name'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['project_id'], taxonomy.UNKNOWN) self.assertEqual(payload['initiator']['host']['address'], '192.168.0.1') self.assertEqual(payload['initiator']['typeURI'], 'service/security/account/user') self.assertNotEqual(payload['initiator']['credential']['token'], None) self.assertEqual(payload['initiator']['credential']['identity_status'], 'Invalid') self.assertNotIn('reason', payload) self.assertNotIn('reporterchain', payload) self.assertEqual(payload['observer']['id'], 'target') self.assertEqual(path, payload['requestPath']) def test_request_and_global_request_id(self): path = '/v1/' + str(uuid.uuid4()) url = 'https://23.253.72.207' + path request_id = 'req-%s' % uuid.uuid4() global_request_id = 'req-%s' % uuid.uuid4() env_headers = self.get_environ_header('GET') env_headers['openstack.request_id'] = request_id env_headers['openstack.global_request_id'] = global_request_id payload = self.get_payload('GET', url, environ=env_headers) self.assertEqual(payload['initiator']['request_id'], request_id) self.assertEqual(payload['initiator']['global_request_id'], global_request_id) payload = self.get_payload('GET', url) self.assertNotIn('request_id', payload['initiator']) self.assertNotIn('global_request_id', payload['initiator']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/test_audit_middleware.py0000664000175000017500000002137400000000000031574 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import uuid import fixtures import webob from keystonemiddleware.tests.unit.audit import base class AuditMiddlewareTest(base.BaseAuditMiddlewareTest): def setUp(self): self.notifier = mock.MagicMock() p = 'keystonemiddleware.audit._notifier.create_notifier' f = fixtures.MockPatch(p, return_value=self.notifier) self.notifier_fixture = self.useFixture(f) super(AuditMiddlewareTest, self).setUp() def test_api_request(self): self.create_simple_app().get('/foo/bar', extra_environ=self.get_environ_header()) # Check first notification with only 'request' call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reason', call_args[2]) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('success', call_args[2]['outcome']) self.assertIn('reason', call_args[2]) self.assertIn('reporterchain', call_args[2]) def test_api_request_failure(self): class CustomException(Exception): pass def cb(req): raise CustomException('It happens!') try: self.create_app(cb).get('/foo/bar', extra_environ=self.get_environ_header()) self.fail('Application exception has not been re-raised') except CustomException: pass # Check first notification with only 'request' call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('unknown', call_args[2]['outcome']) self.assertIn('reporterchain', call_args[2]) def test_process_request_fail(self): req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.environ['audit.context'] = {} self.create_simple_middleware()._process_request(req) self.assertTrue(self.notifier.notify.called) def test_process_response_fail(self): req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.environ['audit.context'] = {} middleware = self.create_simple_middleware() middleware._process_response(req, webob.response.Response()) self.assertTrue(self.notifier.notify.called) def test_ignore_req_opt(self): app = self.create_simple_app(ignore_req_list='get, PUT') # Check GET/PUT request does not send notification app.get('/skip/foo', extra_environ=self.get_environ_header()) app.put('/skip/foo', extra_environ=self.get_environ_header()) self.assertFalse(self.notifier.notify.called) # Check non-GET/PUT request does send notification app.post('/accept/foo', extra_environ=self.get_environ_header()) self.assertEqual(2, self.notifier.notify.call_count) call_args = self.notifier.notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath']) call_args = self.notifier.notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath']) def test_cadf_event_context_scoped(self): self.create_simple_app().get('/foo/bar', extra_environ=self.get_environ_header()) self.assertEqual(2, self.notifier.notify.call_count) first, second = [a[0] for a in self.notifier.notify.call_args_list] # the Context is the first argument. Let's verify it. self.assertIsInstance(first[0], dict) # ensure exact same context is used between request and response self.assertIs(first[0], second[0]) def test_cadf_event_scoped_to_request(self): app = self.create_simple_app() resp = app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertIsNotNone(resp.request.environ.get('cadf_event')) # ensure exact same event is used between request and response self.assertEqual(self.notifier.calls[0].payload['id'], self.notifier.calls[1].payload['id']) def test_cadf_event_scoped_to_request_on_error(self): middleware = self.create_simple_middleware() req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req.environ['audit.context'] = {} self.notifier.notify.side_effect = Exception('error') middleware(req) self.assertTrue(self.notifier.notify.called) req2 = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) req2.environ['audit.context'] = {} self.notifier.reset_mock() middleware._process_response(req2, webob.response.Response()) self.assertTrue(self.notifier.notify.called) # ensure event is not the same across requests self.assertNotEqual(req.environ['cadf_event'].id, self.notifier.notify.call_args_list[0][0][2]['id']) def test_project_name_from_oslo_config(self): self.assertEqual(self.PROJECT_NAME, self.create_simple_middleware()._conf.project) def test_project_name_from_local_config(self): project_name = uuid.uuid4().hex middleware = self.create_simple_middleware(project=project_name) self.assertEqual(project_name, middleware._conf.project) def test_no_response(self): middleware = self.create_simple_middleware() url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' req = webob.Request.blank(url, environ=self.get_environ_header('GET'), remote_addr='192.168.0.1') req.environ['audit.context'] = {} middleware._process_request(req) payload = req.environ['cadf_event'].as_dict() middleware._process_response(req, None) payload2 = req.environ['cadf_event'].as_dict() self.assertEqual(payload['id'], payload2['id']) self.assertEqual(payload['tags'], payload2['tags']) self.assertEqual(payload2['outcome'], 'unknown') self.assertNotIn('reason', payload2) self.assertEqual(len(payload2['reporterchain']), 1) self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], 'target') def test_missing_req(self): req = webob.Request.blank('http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers', environ=self.get_environ_header('GET')) req.environ['audit.context'] = {} self.assertNotIn('cadf_event', req.environ) self.create_simple_middleware()._process_response(req, webob.Response()) self.assertIn('cadf_event', req.environ) payload = req.environ['cadf_event'].as_dict() self.assertEqual(payload['outcome'], 'success') self.assertEqual(payload['reason']['reasonType'], 'HTTP') self.assertEqual(payload['reason']['reasonCode'], '200') self.assertEqual(payload['observer']['id'], 'target') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/test_audit_oslo_messaging.py0000664000175000017500000001064500000000000032467 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from keystonemiddleware import audit from keystonemiddleware.tests.unit.audit import base class AuditNotifierConfigTest(base.BaseAuditMiddlewareTest): def test_conf_middleware_log_and_default_as_messaging(self): self.cfg.config(driver='log', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', side_effect=Exception('error')) as driver: app.get('/foo/bar', extra_environ=self.get_environ_header()) # audit middleware conf has 'log' make sure that driver is invoked # and not the one specified in DEFAULT section self.assertTrue(driver.called) def test_conf_middleware_log_and_oslo_msg_as_messaging(self): self.cfg.config(driver=['messaging'], group='oslo_messaging_notifications') self.cfg.config(driver='log', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', side_effect=Exception('error')) as driver: app.get('/foo/bar', extra_environ=self.get_environ_header()) # audit middleware conf has 'log' make sure that driver is invoked # and not the one specified in oslo_messaging_notifications section self.assertTrue(driver.called) def test_conf_middleware_messaging_and_oslo_msg_as_log(self): self.cfg.config(driver=['log'], group='oslo_messaging_notifications') self.cfg.config(driver='messaging', group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' '.notify', side_effect=Exception('error')) as driver: # audit middleware has 'messaging' make sure that driver is invoked # and not the one specified in oslo_messaging_notifications section app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertTrue(driver.called) def test_with_no_middleware_notification_conf(self): self.cfg.config(driver=['messaging'], group='oslo_messaging_notifications') self.cfg.config(driver=None, group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' '.notify', side_effect=Exception('error')) as driver: # audit middleware section is not set. So driver needs to be # invoked from oslo_messaging_notifications section. app.get('/foo/bar', extra_environ=self.get_environ_header()) self.assertTrue(driver.called) @mock.patch('oslo_messaging.get_notification_transport') def test_conf_middleware_messaging_and_transport_set(self, m): transport_url = 'rabbit://me:passwd@host:5672/virtual_host' self.cfg.config(driver='messaging', transport_url=transport_url, group='audit_middleware_notifications') self.create_simple_middleware() self.assertTrue(m.called) # make sure first call kwarg 'url' is same as provided transport_url self.assertEqual(transport_url, m.call_args_list[0][1]['url']) def test_do_not_use_oslo_messaging(self): self.cfg.config(use_oslo_messaging=False, group='audit_middleware_notifications') audit_middleware = self.create_simple_middleware() # make sure it is using a local notifier instead of oslo_messaging self.assertIsInstance(audit_middleware._notifier, audit._notifier._LogNotifier) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/audit/test_logging_notifier.py0000664000175000017500000000316700000000000031616 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import fixtures from keystonemiddleware.tests.unit.audit import base class TestLoggingNotifier(base.BaseAuditMiddlewareTest): def setUp(self): p = 'keystonemiddleware.audit._notifier.oslo_messaging' f = fixtures.MockPatch(p, None) self.messaging_fixture = self.useFixture(f) super(TestLoggingNotifier, self).setUp() def test_api_request_no_messaging(self): self.cfg.config(use_oslo_messaging=False, group='audit_middleware_notifications') app = self.create_simple_app() with mock.patch('keystonemiddleware.audit._LOG.info') as log: app.get('/foo/bar', extra_environ=self.get_environ_header()) # Check first notification with only 'request' call_args = log.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]['event_type']) # Check second notification with request + response call_args = log.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]['event_type']) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0087514 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/0000775000175000017500000000000000000000000025704 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/__init__.py0000664000175000017500000000000000000000000030003 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/base.py0000664000175000017500000000422000000000000027166 0ustar00zuulzuul00000000000000# 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 http.client as http_client import fixtures from oslo_config import cfg from oslo_config import fixture as cfg_fixture from oslo_log import log as logging from requests_mock.contrib import fixture as rm_fixture import webob.dec from keystonemiddleware import auth_token from keystonemiddleware.tests.unit import utils class BaseAuthTokenTestCase(utils.MiddlewareTestCase): def setUp(self): super(BaseAuthTokenTestCase, self).setUp() self.requests_mock = self.useFixture(rm_fixture.Fixture()) self.logger = fixtures.FakeLogger(level=logging.DEBUG) self.cfg = self.useFixture(cfg_fixture.Config(conf=cfg.ConfigOpts())) self.cfg.conf(args=[]) def create_middleware(self, cb, conf=None, use_global_conf=False): @webob.dec.wsgify def _do_cb(req): return cb(req) if use_global_conf: opts = conf or {} else: opts = { 'oslo_config_config': self.cfg.conf, } opts.update(conf or {}) return auth_token.AuthProtocol(_do_cb, opts) def call(self, middleware, method='GET', path='/', headers=None, expected_status=http_client.OK, expected_body_string=None): req = webob.Request.blank(path) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) self.assertEqual(expected_status, resp.status_int) if expected_body_string: self.assertIn(expected_body_string, str(resp.body)) resp.request = req return resp ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_auth.py0000664000175000017500000000757000000000000030267 0ustar00zuulzuul00000000000000# 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 io import uuid from keystoneauth1 import fixture from keystoneauth1 import plugin as ksa_plugin from keystoneauth1 import session from oslo_log import log as logging from requests_mock.contrib import fixture as rm_fixture from keystonemiddleware.auth_token import _auth from keystonemiddleware.tests.unit import utils class DefaultAuthPluginTests(utils.BaseTestCase): def new_plugin(self, auth_host=None, auth_port=None, auth_protocol=None, auth_admin_prefix=None, admin_user=None, admin_password=None, admin_tenant_name=None, admin_token=None, identity_uri=None, log=None): if not log: log = self.logger return _auth.AuthTokenPlugin( auth_host=auth_host, auth_port=auth_port, auth_protocol=auth_protocol, auth_admin_prefix=auth_admin_prefix, admin_user=admin_user, admin_password=admin_password, admin_tenant_name=admin_tenant_name, admin_token=admin_token, identity_uri=identity_uri, log=log) def setUp(self): super(DefaultAuthPluginTests, self).setUp() self.stream = io.StringIO() self.logger = logging.getLogger(__name__) self.session = session.Session() self.requests_mock = self.useFixture(rm_fixture.Fixture()) def test_auth_uri_from_fragments(self): auth_protocol = 'http' auth_host = 'testhost' auth_port = 8888 auth_admin_prefix = 'admin' expected = '%s://%s:%d/admin' % (auth_protocol, auth_host, auth_port) plugin = self.new_plugin(auth_host=auth_host, auth_protocol=auth_protocol, auth_port=auth_port, auth_admin_prefix=auth_admin_prefix) endpoint = plugin.get_endpoint(self.session, interface=ksa_plugin.AUTH_INTERFACE) self.assertEqual(expected, endpoint) def test_identity_uri_overrides_fragments(self): identity_uri = 'http://testhost:8888/admin' plugin = self.new_plugin(identity_uri=identity_uri, auth_host='anotherhost', auth_port=9999, auth_protocol='ftp') endpoint = plugin.get_endpoint(self.session, interface=ksa_plugin.AUTH_INTERFACE) self.assertEqual(identity_uri, endpoint) def test_with_admin_token(self): token = uuid.uuid4().hex plugin = self.new_plugin(identity_uri='http://testhost:8888/admin', admin_token=token) self.assertEqual(token, plugin.get_token(self.session)) def test_with_user_pass(self): base_uri = 'http://testhost:8888/admin' token = fixture.V2Token() admin_tenant_name = uuid.uuid4().hex self.requests_mock.post(base_uri + '/v2.0/tokens', json=token) plugin = self.new_plugin(identity_uri=base_uri, admin_user=uuid.uuid4().hex, admin_password=uuid.uuid4().hex, admin_tenant_name=admin_tenant_name) self.assertEqual(token.token_id, plugin.get_token(self.session)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py0000664000175000017500000024265100000000000033665 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import os import time from unittest import mock import uuid import fixtures from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import fixture from keystoneauth1 import loading from keystoneauth1 import session import oslo_cache from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import timeutils import pbr.version import testresources from testtools import matchers import webob import webob.dec from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _base from keystonemiddleware.auth_token import _cache from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit import client_fixtures EXPECTED_V2_DEFAULT_ENV_RESPONSE = { 'HTTP_X_IDENTITY_STATUS': 'Confirmed', 'HTTP_X_TENANT_ID': 'tenant_id1', 'HTTP_X_TENANT_NAME': 'tenant_name1', 'HTTP_X_USER_ID': 'user_id1', 'HTTP_X_USER_NAME': 'user_name1', 'HTTP_X_ROLES': 'role1,role2', 'HTTP_X_IS_ADMIN_PROJECT': 'True', } EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE = { 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Confirmed', 'HTTP_X_SERVICE_PROJECT_ID': 'service_project_id1', 'HTTP_X_SERVICE_PROJECT_NAME': 'service_project_name1', 'HTTP_X_SERVICE_USER_ID': 'service_user_id1', 'HTTP_X_SERVICE_USER_NAME': 'service_user_name1', 'HTTP_X_SERVICE_ROLES': 'service,service_role2', } EXPECTED_V3_DEFAULT_ENV_ADDITIONS = { 'HTTP_X_PROJECT_DOMAIN_ID': 'domain_id1', 'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1', 'HTTP_X_USER_DOMAIN_ID': 'domain_id1', 'HTTP_X_USER_DOMAIN_NAME': 'domain_name1', 'HTTP_X_IS_ADMIN_PROJECT': 'True' } EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS = { 'HTTP_X_SERVICE_PROJECT_DOMAIN_ID': 'service_domain_id1', 'HTTP_X_SERVICE_PROJECT_DOMAIN_NAME': 'service_domain_name1', 'HTTP_X_SERVICE_USER_DOMAIN_ID': 'service_domain_id1', 'HTTP_X_SERVICE_USER_DOMAIN_NAME': 'service_domain_name1' } BASE_HOST = 'https://keystone.example.com:1234' BASE_URI = '%s/testadmin' % BASE_HOST FAKE_ADMIN_TOKEN_ID = 'admin_token2' FAKE_ADMIN_TOKEN = jsonutils.dumps( {'access': {'token': {'id': FAKE_ADMIN_TOKEN_ID, 'expires': '%i-10-03T16:58:01Z' % (1 + time.gmtime().tm_year)}}}) VERSION_LIST_v3 = fixture.DiscoveryList(href=BASE_URI) VERSION_LIST_v2 = fixture.DiscoveryList(v3=False, href=BASE_URI) ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2' TIMEOUT_TOKEN = '4ed1c5e53beee59458adcf8261a8cae2' ENDPOINT_NOT_FOUND_TOKEN = 'edf9fa62-5afd-4d64-89ac-f99b209bd995' def strtime(at=None): at = at or timeutils.utcnow() return at.strftime(timeutils.PERFECT_TIME_FORMAT) class TimezoneFixture(fixtures.Fixture): @staticmethod def supported(): # tzset is only supported on Unix. return hasattr(time, 'tzset') def __init__(self, new_tz): super(TimezoneFixture, self).__init__() self.tz = new_tz self.old_tz = os.environ.get('TZ') def setUp(self): super(TimezoneFixture, self).setUp() if not self.supported(): raise NotImplementedError('timezone override is not supported.') os.environ['TZ'] = self.tz time.tzset() self.addCleanup(self.cleanup) def cleanup(self): if self.old_tz is not None: os.environ['TZ'] = self.old_tz elif 'TZ' in os.environ: del os.environ['TZ'] time.tzset() class TimeFixture(fixtures.Fixture): def __init__(self, new_time, normalize=True): super(TimeFixture, self).__init__() if isinstance(new_time, str): new_time = timeutils.parse_isotime(new_time) if normalize: new_time = timeutils.normalize_time(new_time) self.new_time = new_time def setUp(self): super(TimeFixture, self).setUp() timeutils.set_time_override(self.new_time) self.addCleanup(timeutils.clear_time_override) class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" SUCCESS = b'SUCCESS' FORBIDDEN = b'FORBIDDEN' expected_env = {} def __init__(self, expected_env=None, need_service_token=False): self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) if expected_env: self.expected_env.update(expected_env) self.need_service_token = need_service_token @webob.dec.wsgify def __call__(self, req): for k, v in self.expected_env.items(): assert req.environ[k] == v, '%s != %s' % (req.environ[k], v) resp = webob.Response() if (req.environ.get('HTTP_X_IDENTITY_STATUS') == 'Invalid' and req.environ['HTTP_X_SERVICE_IDENTITY_STATUS'] == 'Invalid'): # Simulate delayed auth forbidding access with arbitrary status # code to differentiate checking this code path resp.status = 419 resp.body = FakeApp.FORBIDDEN elif req.environ.get('HTTP_X_SERVICE_IDENTITY_STATUS') == 'Invalid': # Simulate delayed auth forbidding access with arbitrary status # code to differentiate checking this code path resp.status = 420 resp.body = FakeApp.FORBIDDEN elif req.environ['HTTP_X_IDENTITY_STATUS'] == 'Invalid': # Simulate delayed auth forbidding access resp.status = 403 resp.body = FakeApp.FORBIDDEN elif (self.need_service_token is True and req.environ.get('HTTP_X_SERVICE_TOKEN') is None): # Simulate requiring composite auth # Arbitrary value to allow checking this code path resp.status = 418 resp.body = FakeApp.FORBIDDEN else: resp.body = FakeApp.SUCCESS return resp class v3FakeApp(FakeApp): """This represents a v3 WSGI app protected by the auth_token middleware.""" def __init__(self, expected_env=None, need_service_token=False): # with v3 additions, these are for the DEFAULT TOKEN v3_default_env_additions = dict(EXPECTED_V3_DEFAULT_ENV_ADDITIONS) if expected_env: v3_default_env_additions.update(expected_env) super(v3FakeApp, self).__init__(expected_env=v3_default_env_additions, need_service_token=need_service_token) class CompositeBase(object): """Base composite auth object with common service token environment.""" def __init__(self, expected_env=None): comp_expected_env = dict(EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) if expected_env: comp_expected_env.update(expected_env) super(CompositeBase, self).__init__( expected_env=comp_expected_env, need_service_token=True) class CompositeFakeApp(CompositeBase, FakeApp): """A fake v2 WSGI app protected by composite auth_token middleware.""" def __init__(self, expected_env): super(CompositeFakeApp, self).__init__(expected_env=expected_env) class v3CompositeFakeApp(CompositeBase, v3FakeApp): """A fake v3 WSGI app protected by composite auth_token middleware.""" def __init__(self, expected_env=None): # with v3 additions, these are for the DEFAULT SERVICE TOKEN v3_default_service_env_additions = dict( EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS) if expected_env: v3_default_service_env_additions.update(expected_env) super(v3CompositeFakeApp, self).__init__( v3_default_service_env_additions) class FakeOsloCache(_cache._FakeClient): """A fake oslo_cache object. The memcache and oslo_cache interfaces are almost the same except we need to return NO_VALUE when not found. """ def get(self, key): return super(FakeOsloCache, self).get(key) or oslo_cache.NO_VALUE class BaseAuthTokenMiddlewareTest(base.BaseAuthTokenTestCase): """Base test class for auth_token middleware. All the tests allow for running with auth_token configured for receiving v2 or v3 tokens, with the choice being made by passing configuration data into setUp(). The base class will, by default, run all the tests expecting v2 token formats. Child classes can override this to specify, for instance, v3 format. """ def setUp(self, expected_env=None, auth_version=None, fake_app=None): super(BaseAuthTokenMiddlewareTest, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) # the default oslo_cache is null cache, always use an in-mem cache self.useFixture(fixtures.MockPatchObject(auth_token.AuthProtocol, '_create_oslo_cache', return_value=FakeOsloCache())) self.expected_env = expected_env or dict() self.fake_app = fake_app or FakeApp self.middleware = None self.conf = { 'identity_uri': 'https://keystone.example.com:1234/testadmin/', 'auth_version': auth_version, 'www_authenticate_uri': 'https://keystone.example.com:1234', 'admin_user': uuid.uuid4().hex, } self.auth_version = auth_version self.response_status = None self.response_headers = None def call_middleware(self, **kwargs): return self.call(self.middleware, **kwargs) def set_middleware(self, expected_env=None, conf=None): """Configure the class ready to call the auth_token middleware. Set up the various fake items needed to run the middleware. Individual tests that need to further refine these can call this function to override the class defaults. """ if conf: self.conf.update(conf) if expected_env: self.expected_env.update(expected_env) self.middleware = auth_token.AuthProtocol( self.fake_app(self.expected_env), self.conf) def update_expected_env(self, expected_env={}): self.middleware._app.expected_env.update(expected_env) def purge_token_expected_env(self): for key in self.token_expected_env.keys(): del self.middleware._app.expected_env[key] def purge_service_token_expected_env(self): for key in self.service_token_expected_env.keys(): del self.middleware._app.expected_env[key] def assertLastPath(self, path): if path: self.assertEqual(BASE_URI + path, self.requests_mock.last_request.url) else: self.assertIsNone(self.requests_mock.last_request) class CachePoolTest(BaseAuthTokenMiddlewareTest): def test_use_cache_from_env(self): # If `swift.cache` is set in the environment and `cache` is set in the # config then the env cache is used. env = {'swift.cache': 'CACHE_TEST'} conf = { 'cache': 'swift.cache' } self.set_middleware(conf=conf) self.middleware._token_cache.initialize(env) with self.middleware._token_cache._cache_pool.reserve() as cache: self.assertEqual(cache, 'CACHE_TEST') def test_not_use_cache_from_env(self): # If `swift.cache` is set in the environment but `cache` isn't set # initialize the config then the env cache isn't used. self.set_middleware() env = {'swift.cache': 'CACHE_TEST'} self.middleware._token_cache.initialize(env) with self.middleware._token_cache._cache_pool.reserve() as cache: self.assertNotEqual(cache, 'CACHE_TEST') def test_multiple_context_managers_share_single_client(self): self.set_middleware() token_cache = self.middleware._token_cache env = {} token_cache.initialize(env) caches = [] with token_cache._cache_pool.reserve() as cache: caches.append(cache) with token_cache._cache_pool.reserve() as cache: caches.append(cache) self.assertIs(caches[0], caches[1]) self.assertEqual(set(caches), set(token_cache._cache_pool)) def test_nested_context_managers_create_multiple_clients(self): self.set_middleware() env = {} self.middleware._token_cache.initialize(env) token_cache = self.middleware._token_cache with token_cache._cache_pool.reserve() as outer_cache: with token_cache._cache_pool.reserve() as inner_cache: self.assertNotEqual(outer_cache, inner_cache) self.assertEqual( set([inner_cache, outer_cache]), set(token_cache._cache_pool)) class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase): """General Token Behavior tests. These tests are not affected by the token format (see CommonAuthTokenMiddlewareTest). """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_fixed_cache_key_length(self): self.set_middleware() short_string = uuid.uuid4().hex long_string = 8 * uuid.uuid4().hex token_cache = self.middleware._token_cache hashed_short_string_key, context_ = token_cache._get_cache_key( short_string) hashed_long_string_key, context_ = token_cache._get_cache_key( long_string) # The hash keys should always match in length self.assertThat(hashed_short_string_key, matchers.HasLength(len(hashed_long_string_key))) def test_conf_values_type_convert(self): conf = { 'identity_uri': 'https://keystone.example.com:1234', 'include_service_catalog': '0', 'nonexsit_option': '0', } middleware = auth_token.AuthProtocol(self.fake_app, conf) self.assertFalse(middleware._include_service_catalog) self.assertEqual('0', middleware._conf.get('nonexsit_option')) def test_deprecated_conf_values(self): servers = 'localhost:11211' conf = { 'memcache_servers': servers } middleware = auth_token.AuthProtocol(self.fake_app, conf) self.assertEqual([servers], middleware._conf.get('memcached_servers')) def test_conf_values_type_convert_with_wrong_key(self): conf = { 'wrong_key': '123' } log = 'The option "wrong_key" is not known to keystonemiddleware' auth_token.AuthProtocol(self.fake_app, conf) self.assertThat(self.logger.output, matchers.Contains(log)) def test_conf_values_type_convert_with_wrong_value(self): conf = { 'include_service_catalog': '123', } self.assertRaises(ksm_exceptions.ConfigurationError, auth_token.AuthProtocol, self.fake_app, conf) def test_auth_region_name(self): token = fixture.V3Token() auth_url = 'http://keystone-auth.example.com:5000' east_url = 'http://keystone-east.example.com:5000' west_url = 'http://keystone-west.example.com:5000' auth_versions = fixture.DiscoveryList(href=auth_url) east_versions = fixture.DiscoveryList(href=east_url) west_versions = fixture.DiscoveryList(href=west_url) s = token.add_service('identity') s.add_endpoint(interface='internal', url=east_url, region='east') s.add_endpoint(interface='internal', url=west_url, region='west') self.requests_mock.get(auth_url, json=auth_versions) self.requests_mock.get(east_url, json=east_versions) self.requests_mock.get(west_url, json=west_versions) self.requests_mock.post( '%s/v3/auth/tokens' % auth_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=token) east_mock = self.requests_mock.get( '%s/v3/auth/tokens' % east_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=fixture.V3Token()) west_mock = self.requests_mock.get( '%s/v3/auth/tokens' % west_url, headers={'X-Subject-Token': uuid.uuid4().hex}, json=fixture.V3Token()) loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) opts = loading.get_auth_plugin_conf_options('v3password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_url=auth_url + '/v3', auth_type='v3password', username='user', password='pass', user_domain_id=uuid.uuid4().hex, group=_base.AUTHTOKEN_GROUP) self.assertEqual(0, east_mock.call_count) self.assertEqual(0, west_mock.call_count) east_app = self.create_simple_middleware(conf=dict(region_name='east')) self.call(east_app, headers={'X-Auth-Token': uuid.uuid4().hex}) self.assertEqual(1, east_mock.call_count) self.assertEqual(0, west_mock.call_count) west_app = self.create_simple_middleware(conf=dict(region_name='west')) self.call(west_app, headers={'X-Auth-Token': uuid.uuid4().hex}) self.assertEqual(1, east_mock.call_count) self.assertEqual(1, west_mock.call_count) class CommonAuthTokenMiddlewareTest(object): """These tests are run once using v2 tokens and again using v3 tokens.""" def test_init_does_not_call_http(self): self.create_simple_middleware(conf={}) self.assertLastPath(None) def test_auth_with_no_token_does_not_call_http(self): middleware = self.create_simple_middleware() self.call(middleware, expected_status=401) self.assertLastPath(None) def test_init_by_ipv6Addr_auth_host(self): del self.conf['identity_uri'] conf = { 'auth_host': '2001:2013:1:f101::1', 'auth_port': '1234', 'auth_protocol': 'http', 'www_authenticate_uri': None, 'auth_version': 'v3.0', } middleware = self.create_simple_middleware(conf=conf) self.assertEqual('http://[2001:2013:1:f101::1]:1234', middleware._www_authenticate_uri) def assert_valid_request_200(self, token, with_catalog=True): resp = self.call_middleware(headers={'X-Auth-Token': token}) if with_catalog: self.assertTrue(resp.request.headers.get('X-Service-Catalog')) else: self.assertNotIn('X-Service-Catalog', resp.request.headers) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', resp.request.environ) return resp.request def test_valid_uuid_request(self): for _ in range(2): # Do it twice because first result was cached. token = self.token_dict['uuid_token_default'] self.assert_valid_request_200(token) self.assert_valid_last_url(token) def test_valid_uuid_request_with_auth_fragments(self): del self.conf['identity_uri'] self.conf['auth_protocol'] = 'https' self.conf['auth_host'] = 'keystone.example.com' self.conf['auth_port'] = '1234' self.conf['auth_admin_prefix'] = '/testadmin' self.set_middleware() self.assert_valid_request_200(self.token_dict['uuid_token_default']) self.assert_valid_last_url(self.token_dict['uuid_token_default']) def test_request_invalid_uuid_token(self): # remember because we are testing the middleware we stub the connection # to the keystone server, but this is not what gets returned invalid_uri = "%s/v2.0/tokens/invalid-token" % BASE_URI self.requests_mock.get(invalid_uri, status_code=404) resp = self.call_middleware(headers={'X-Auth-Token': 'invalid-token'}, expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def test_request_no_token(self): resp = self.call_middleware(expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def test_request_no_token_http(self): resp = self.call_middleware(method='HEAD', expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def test_request_blank_token(self): resp = self.call_middleware(headers={'X-Auth-Token': ''}, expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def _get_cached_token(self, token): return self.middleware._token_cache.get(token) def test_memcache_set_invalid_uuid(self): invalid_uri = "%s/v3/tokens/invalid-token" % BASE_URI self.requests_mock.get(invalid_uri, status_code=404) token = 'invalid-token' self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401) def test_memcache_set_expired(self, extra_conf={}, extra_environ={}): token_cache_time = 10 conf = { 'token_cache_time': '%s' % token_cache_time, } conf.update(extra_conf) self.set_middleware(conf=conf) token = self.token_dict['uuid_token_default'] self.call_middleware(headers={'X-Auth-Token': token}) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = token req.environ.update(extra_environ) now = datetime.datetime.now(datetime.timezone.utc) self.useFixture(TimeFixture(now)) req.get_response(self.middleware) self.assertIsNotNone(self._get_cached_token(token)) timeutils.advance_time_seconds(token_cache_time) self.assertIsNone(self._get_cached_token(token)) def test_swift_memcache_set_expired(self): extra_conf = {'cache': 'swift.cache'} extra_environ = {'swift.cache': _cache._FakeClient()} self.test_memcache_set_expired(extra_conf, extra_environ) def test_http_error_not_cached_token(self): """Test to don't cache token as invalid on network errors. We use UUID tokens since they are the easiest one to reach get_http_connection. """ self.set_middleware(conf={'http_request_max_retries': '0'}) self.call_middleware(headers={'X-Auth-Token': ERROR_TOKEN}, expected_status=503) self.assertIsNone(self._get_cached_token(ERROR_TOKEN)) self.assert_valid_last_url(ERROR_TOKEN) def test_discovery_failure(self): def discovery_failure_response(request, context): raise ksa_exceptions.DiscoveryFailure( "Could not determine a suitable URL for the plugin") self.requests_mock.get(BASE_URI, text=discovery_failure_response) self.call_middleware(headers={'X-Auth-Token': 'token'}, expected_status=503) self.assertIsNone(self._get_cached_token('token')) self.assertEqual(BASE_URI, self.requests_mock.last_request.url) def test_http_request_max_retries(self): times_retry = 10 body_string = 'The Keystone service is temporarily unavailable.' conf = {'http_request_max_retries': '%s' % times_retry} self.set_middleware(conf=conf) with mock.patch('time.sleep') as mock_obj: self.call_middleware(headers={'X-Auth-Token': ERROR_TOKEN}, expected_status=503, expected_body_string=body_string) self.assertEqual(mock_obj.call_count, times_retry) def test_request_timeout(self): self.call_middleware(headers={'X-Auth-Token': TIMEOUT_TOKEN}, expected_status=503) self.assertIsNone(self._get_cached_token(TIMEOUT_TOKEN)) self.assert_valid_last_url(TIMEOUT_TOKEN) def test_nocatalog(self): conf = { 'include_service_catalog': 'False' } self.set_middleware(conf=conf) self.assert_valid_request_200(self.token_dict['uuid_token_default'], with_catalog=False) def assert_kerberos_bind(self, token, bind_level, use_kerberos=True, success=True): conf = { 'enforce_token_bind': bind_level, 'auth_version': self.auth_version, } self.set_middleware(conf=conf) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = token if use_kerberos: if use_kerberos is True: req.environ['REMOTE_USER'] = self.examples.KERBEROS_BIND else: req.environ['REMOTE_USER'] = use_kerberos req.environ['AUTH_TYPE'] = 'Negotiate' resp = req.get_response(self.middleware) if success: self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', req.environ) self.assert_valid_last_url(token) else: self.assertEqual(401, resp.status_int) msg = 'Keystone uri="https://keystone.example.com:1234"' self.assertEqual(msg, resp.headers['WWW-Authenticate']) def test_uuid_bind_token_disabled_with_kerb_user(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='disabled', use_kerberos=use_kerberos, success=True) def test_uuid_bind_token_disabled_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_permissive_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='permissive', use_kerberos=True, success=True) def test_uuid_bind_token_permissive_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='permissive', use_kerberos=False, success=False) def test_uuid_bind_token_permissive_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='permissive', use_kerberos=use_kerberos, success=True) def test_uuid_bind_token_permissive_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_strict_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='strict', use_kerberos=True, success=True) def test_uuid_bind_token_strict_with_kerbout_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='strict', use_kerberos=False, success=False) def test_uuid_bind_token_strict_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='strict', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_required_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='required', use_kerberos=True, success=True) def test_uuid_bind_token_required_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='required', use_kerberos=False, success=False) def test_uuid_bind_token_required_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='required', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_required_without_bind(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_default'], bind_level='required', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_with_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos=True, success=True) def test_uuid_bind_token_named_kerberos_without_kerb_user(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos=False, success=False) def test_uuid_bind_token_named_kerberos_with_unknown_bind(self): token = self.token_dict['uuid_token_unknown_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='kerberos', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_without_bind(self): for use_kerberos in [True, False]: self.assert_kerberos_bind(self.token_dict['uuid_token_default'], bind_level='kerberos', use_kerberos=use_kerberos, success=False) def test_uuid_bind_token_named_kerberos_with_incorrect_ticket(self): self.assert_kerberos_bind(self.token_dict['uuid_token_bind'], bind_level='kerberos', use_kerberos='ronald@MCDONALDS.COM', success=False) def test_uuid_bind_token_with_unknown_named_FOO(self): token = self.token_dict['uuid_token_bind'] for use_kerberos in [True, False]: self.assert_kerberos_bind(token, bind_level='FOO', use_kerberos=use_kerberos, success=False) def test_caching_token_on_verify(self): # When the token is cached it isn't cached again when it's verified. # The token cache has to be initialized with our cache instance. self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) # Mock cache.set since then the test can verify call_count. orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) token = self.token_dict['uuid_token_default'] self.call_middleware(headers={'X-Auth-Token': token}) self.assertThat(1, matchers.Equals(cache.set.call_count)) self.call_middleware(headers={'X-Auth-Token': token}) # Assert that the token wasn't cached again. self.assertThat(1, matchers.Equals(cache.set.call_count)) def test_auth_plugin(self): for service_url in (self.examples.UNVERSIONED_SERVICE_URL, self.examples.SERVICE_URL): self.requests_mock.get(service_url, json=VERSION_LIST_v3, status_code=300) token = self.token_dict['uuid_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] endpoint_filter = {'service_type': self.examples.SERVICE_TYPE, 'version': 3} url = token_auth.get_endpoint(session.Session(), **endpoint_filter) self.assertEqual('%s/v3' % BASE_URI, url) self.assertTrue(token_auth.has_user_token) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) def test_doesnt_auto_set_content_type(self): # webob will set content_type = 'text/html' by default if nothing is # provided. We don't want our middleware messing with the content type # of the underlying applications. text = uuid.uuid4().hex def _middleware(environ, start_response): start_response(200, []) return text def _start_response(status_code, headerlist, exc_info=None): self.assertIn('200', status_code) # will be '200 OK' self.assertEqual([], headerlist) m = auth_token.AuthProtocol(_middleware, self.conf) env = {'REQUEST_METHOD': 'GET', 'HTTP_X_AUTH_TOKEN': self.token_dict['uuid_token_default']} r = m(env, _start_response) self.assertEqual(text, r) def test_auth_plugin_service_token(self): url = 'http://test.url' text = uuid.uuid4().hex self.requests_mock.get(url, text=text) token = self.token_dict['uuid_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) s = session.Session(auth=resp.request.environ['keystone.token_auth']) resp = s.get(url) self.assertEqual(text, resp.text) self.assertEqual(200, resp.status_code) headers = self.requests_mock.last_request.headers self.assertEqual(FAKE_ADMIN_TOKEN_ID, headers['X-Service-Token']) def test_service_token_with_valid_service_role_not_required(self): self.conf['service_token_roles'] = ['service'] self.conf['service_token_roles_required'] = False self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_invalid_service_role_not_required(self): self.conf['service_token_roles'] = [uuid.uuid4().hex] self.conf['service_token_roles_required'] = False self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_valid_service_role_required(self): self.conf['service_token_roles'] = ['service'] self.conf['service_token_roles_required'] = True self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}) self.assertEqual('Confirmed', resp.request.headers['X-Service-Identity-Status']) def test_service_token_with_invalid_service_role_required(self): self.conf['service_token_roles'] = [uuid.uuid4().hex] self.conf['service_token_roles_required'] = True self.set_middleware(conf=self.conf) user_token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': user_token, 'X-Service-Token': service_token}, expected_status=401) self.assertEqual('Invalid', resp.request.headers['X-Service-Identity-Status']) def network_error_response(request, context): raise ksa_exceptions.ConnectFailure("Network connection refused.") def request_timeout_response(request, context): raise ksa_exceptions.RequestTimeout( "Request to https://host/token/path timed out") class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, CommonAuthTokenMiddlewareTest, testresources.ResourcedTestCase): """Test auth_token middleware with v3 tokens. Re-execute the AuthTokenMiddlewareTest class tests, but with the auth_token middleware configured to expect v3 tokens back from a keystone server. This is done by configuring the AuthTokenMiddlewareTest class via its Setup(), passing in v3 style data that will then be used by the tests themselves. """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v3AuthTokenMiddlewareTest, self).setUp( auth_version='v3.0', fake_app=v3FakeApp) self.token_dict = { 'uuid_token_default': self.examples.v3_UUID_TOKEN_DEFAULT, 'uuid_token_unscoped': self.examples.v3_UUID_TOKEN_UNSCOPED, 'uuid_token_bind': self.examples.v3_UUID_TOKEN_BIND, 'uuid_token_unknown_bind': self.examples.v3_UUID_TOKEN_UNKNOWN_BIND, 'uuid_service_token_default': self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) # TODO(jamielennox): auth_token middleware uses a v2 admin token # regardless of the auth_version that is set. self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.set_middleware() def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksa_exceptions.ConnectFailure(msg) elif token_id == TIMEOUT_TOKEN: request_timeout_response(request, context) elif token_id == ENDPOINT_NOT_FOUND_TOKEN: raise ksa_exceptions.EndpointNotFound() try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: response = "" context.status_code = 404 return response def assert_valid_last_url(self, token_id): self.assertLastPath('/v3/auth/tokens') def test_valid_unscoped_uuid_request(self): # Remove items that won't be in an unscoped token delta_expected_env = { 'HTTP_X_PROJECT_ID': None, 'HTTP_X_PROJECT_NAME': None, 'HTTP_X_PROJECT_DOMAIN_ID': None, 'HTTP_X_PROJECT_DOMAIN_NAME': None, 'HTTP_X_TENANT_ID': None, 'HTTP_X_TENANT_NAME': None, 'HTTP_X_ROLES': '', 'HTTP_X_TENANT': None, 'HTTP_X_ROLE': '', } self.set_middleware(expected_env=delta_expected_env) self.assert_valid_request_200(self.examples.v3_UUID_TOKEN_UNSCOPED, with_catalog=False) self.assertLastPath('/v3/auth/tokens') def test_valid_system_scoped_token_request(self): delta_expected_env = { 'HTTP_OPENSTACK_SYSTEM_SCOPE': 'all', 'HTTP_X_PROJECT_ID': None, 'HTTP_X_PROJECT_NAME': None, 'HTTP_X_PROJECT_DOMAIN_ID': None, 'HTTP_X_PROJECT_DOMAIN_NAME': None, 'HTTP_X_TENANT_ID': None, 'HTTP_X_TENANT_NAME': None, 'HTTP_X_TENANT': None } self.set_middleware(expected_env=delta_expected_env) self.assert_valid_request_200(self.examples.v3_SYSTEM_SCOPED_TOKEN) self.assertLastPath('/v3/auth/tokens') def test_domain_scoped_uuid_request(self): # Modify items compared to default token for a domain scope delta_expected_env = { 'HTTP_X_DOMAIN_ID': 'domain_id1', 'HTTP_X_DOMAIN_NAME': 'domain_name1', 'HTTP_X_PROJECT_ID': None, 'HTTP_X_PROJECT_NAME': None, 'HTTP_X_PROJECT_DOMAIN_ID': None, 'HTTP_X_PROJECT_DOMAIN_NAME': None, 'HTTP_X_TENANT_ID': None, 'HTTP_X_TENANT_NAME': None, 'HTTP_X_TENANT': None } self.set_middleware(expected_env=delta_expected_env) self.assert_valid_request_200( self.examples.v3_UUID_TOKEN_DOMAIN_SCOPED) self.assertLastPath('/v3/auth/tokens') def test_user_plugin_token_properties(self): token = self.examples.v3_UUID_TOKEN_DEFAULT token_data = self.examples.TOKEN_RESPONSES[token] service = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT service_data = self.examples.TOKEN_RESPONSES[service] resp = self.call_middleware(headers={'X-Service-Catalog': '[]', 'X-Auth-Token': token, 'X-Service-Token': service}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertTrue(token_auth.has_user_token) self.assertTrue(token_auth.has_service_token) self.assertEqual(token_data.user_id, token_auth.user.user_id) self.assertEqual(token_data.project_id, token_auth.user.project_id) self.assertEqual(token_data.user_domain_id, token_auth.user.user_domain_id) self.assertEqual(token_data.project_domain_id, token_auth.user.project_domain_id) self.assertThat(token_auth.user.role_names, matchers.HasLength(2)) self.assertIn('role1', token_auth.user.role_names) self.assertIn('role2', token_auth.user.role_names) self.assertIsNone(token_auth.user.trust_id) self.assertEqual(service_data.user_id, token_auth.service.user_id) self.assertEqual(service_data.project_id, token_auth.service.project_id) self.assertEqual(service_data.user_domain_id, token_auth.service.user_domain_id) self.assertEqual(service_data.project_domain_id, token_auth.service.project_domain_id) self.assertThat(token_auth.service.role_names, matchers.HasLength(2)) self.assertIn('service', token_auth.service.role_names) self.assertIn('service_role2', token_auth.service.role_names) self.assertIsNone(token_auth.service.trust_id) def test_expire_stored_in_cache(self): # tests the upgrade path from storing a tuple vs just the data in the # cache. Can be removed in the future. token = 'mytoken' data = 'this_data' self.set_middleware() self.middleware._token_cache.initialize({}) now = datetime.datetime.now(datetime.timezone.utc) delta = datetime.timedelta(hours=1) expires = strtime(at=(now + delta)) self.middleware._token_cache.set(token, (data, expires)) new_data = self.middleware.fetch_token(token) self.assertEqual(data, new_data) def test_endpoint_not_found_in_token(self): token = ENDPOINT_NOT_FOUND_TOKEN self.set_middleware() self.middleware._token_cache.initialize({}) with mock.patch.object(self.middleware._identity_server, 'invalidate', new=mock.Mock()): self.assertRaises(ksa_exceptions.EndpointNotFound, self.middleware.fetch_token, token) self.assertTrue(self.middleware._identity_server.invalidate.called) def test_not_is_admin_project(self): token = self.examples.v3_NOT_IS_ADMIN_PROJECT self.set_middleware(expected_env={'HTTP_X_IS_ADMIN_PROJECT': 'False'}) req = self.assert_valid_request_200(token) self.assertIs(False, req.environ['keystone.token_auth'].user.is_admin_project) def test_service_token_with_valid_service_role_not_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_valid_service_role_not_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertEqual(['1'], e) def test_service_token_with_invalid_service_role_not_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_invalid_service_role_not_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertIsNone(e) def test_service_token_with_valid_service_role_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_valid_service_role_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertEqual(['1'], e) def test_service_token_with_invalid_service_role_required(self): s = super(v3AuthTokenMiddlewareTest, self) s.test_service_token_with_invalid_service_role_required() e = self.requests_mock.request_history[3].qs.get('allow_expired') self.assertIsNone(e) def test_app_cred_token_without_access_rules(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_TOKEN token_data = self.examples.TOKEN_RESPONSES[token] resp = self.call_middleware(headers={'X-Auth-Token': token}) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) def test_app_cred_access_rules_token(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_ACCESS_RULES token_data = self.examples.TOKEN_RESPONSES[token] resp = self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v2.1/servers') token_auth = resp.request.environ['keystone.token_auth'] self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) self.assertEqual(token_data.application_credential_access_rules, token_auth.user.application_credential_access_rules) resp = self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2.1/servers/someuuid') token_auth = resp.request.environ['keystone.token_auth'] self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) self.assertEqual(token_data.application_credential_access_rules, token_auth.user.application_credential_access_rules) def test_app_cred_access_rules_service_request(self): self.set_middleware(conf={'service_type': 'image'}) token = self.examples.v3_APP_CRED_ACCESS_RULES headers = {'X-Auth-Token': token} self.call_middleware(headers=headers, expected_status=401, method='GET', path='/v2/images') service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT headers['X-Service-Token'] = service_token self.call_middleware(headers=headers, expected_status=200, method='GET', path='/v2/images') def test_app_cred_no_access_rules_token(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_EMPTY_ACCESS_RULES self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2.1/servers') service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT headers = { 'X-Auth-Token': token, 'X-Service-Token': service_token } self.call_middleware(headers=headers, expected_status=401, method='GET', path='/v2.1/servers') def test_app_cred_matching_rules(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_MATCHING_RULES self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v2.1/servers/foobar') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2.1/servers/foobar/barfoo') self.set_middleware(conf={'service_type': 'image'}) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v2/images/foobar') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2/images/foobar/barfoo') self.set_middleware(conf={'service_type': 'identity'}) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v3/projects/123/users/456/roles/member') self.set_middleware(conf={'service_type': 'block-storage'}) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v3/123/types/456') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v3/123/types') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2/123/types/456') self.set_middleware(conf={'service_type': 'object-store'}) self.call_middleware(headers={'X-Auth-Token': token}, expected_status=200, method='GET', path='/v1/1/2/3') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v1/1/2') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/v2/1/2') self.call_middleware(headers={'X-Auth-Token': token}, expected_status=401, method='GET', path='/info') class DelayedAuthTests(BaseAuthTokenMiddlewareTest): def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) if request.headers.get('X-Subject-Token') == ERROR_TOKEN: msg = 'Network connection refused.' raise ksa_exceptions.ConnectFailure(msg) # All others just fail context.status_code = 404 return '' def test_header_in_401(self): body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = {'delay_auth_decision': 'True', 'auth_version': 'v3', 'www_authenticate_uri': www_authenticate_uri} middleware = self.create_simple_middleware(status='401 Unauthorized', body=body, conf=conf) resp = self.call(middleware, expected_status=401) self.assertEqual(body.encode(), resp.body) self.assertEqual('Keystone uri="%s"' % www_authenticate_uri, resp.headers['WWW-Authenticate']) def test_delayed_auth_values(self): conf = {'www_authenticate_uri': 'http://local.test'} status = '401 Unauthorized' middleware = self.create_simple_middleware(status=status, conf=conf) self.assertFalse(middleware._delay_auth_decision) for v in ('True', '1', 'on', 'yes'): conf = {'delay_auth_decision': v, 'www_authenticate_uri': 'http://local.test'} middleware = self.create_simple_middleware(status=status, conf=conf) self.assertTrue(middleware._delay_auth_decision) for v in ('False', '0', 'no'): conf = {'delay_auth_decision': v, 'www_authenticate_uri': 'http://local.test'} middleware = self.create_simple_middleware(status=status, conf=conf) self.assertFalse(middleware._delay_auth_decision) def test_auth_plugin_with_no_tokens(self): body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = { 'delay_auth_decision': True, 'www_authenticate_uri': www_authenticate_uri } middleware = self.create_simple_middleware(body=body, conf=conf) resp = self.call(middleware) self.assertEqual(body.encode(), resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertFalse(token_auth.has_user_token) self.assertIsNone(token_auth.user) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) def test_auth_plugin_with_token(self): self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = { 'delay_auth_decision': 'True', 'www_authenticate_uri': www_authenticate_uri, 'auth_type': 'admin_token', 'endpoint': '%s/v3' % BASE_URI, 'token': FAKE_ADMIN_TOKEN_ID, } middleware = self.create_simple_middleware(body=body, conf=conf) resp = self.call(middleware, headers={ 'X-Auth-Token': 'non-keystone-token'}) self.assertEqual(body.encode(), resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertFalse(token_auth.has_user_token) self.assertIsNone(token_auth.user) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) def test_auth_plugin_with_token_keystone_down(self): self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': ERROR_TOKEN}) body = uuid.uuid4().hex www_authenticate_uri = 'http://local.test' conf = { 'delay_auth_decision': 'True', 'www_authenticate_uri': www_authenticate_uri, 'auth_type': 'admin_token', 'endpoint': '%s/v3' % BASE_URI, 'token': FAKE_ADMIN_TOKEN_ID, 'http_request_max_retries': '0' } middleware = self.create_simple_middleware(body=body, conf=conf) resp = self.call(middleware, headers={'X-Auth-Token': ERROR_TOKEN}) self.assertEqual(body.encode(), resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertFalse(token_auth.has_user_token) self.assertIsNone(token_auth.user) self.assertFalse(token_auth.has_service_token) self.assertIsNone(token_auth.service) class CommonCompositeAuthTests(object): """Test Composite authentication. Test the behaviour of adding a service-token. """ def test_composite_auth_ok(self): token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] fake_logger = fixtures.FakeLogger(level=logging.DEBUG) self.middleware.logger = self.useFixture(fake_logger) resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}) self.assertEqual(FakeApp.SUCCESS, resp.body) expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) expected_env.update(EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) # role list may get reordered, check for string pieces individually self.assertIn('Received request from user: ', fake_logger.output) self.assertIn('user_id %(HTTP_X_USER_ID)s, ' 'project_id %(HTTP_X_TENANT_ID)s, ' 'roles ' % expected_env, fake_logger.output) self.assertIn('service: user_id %(HTTP_X_SERVICE_USER_ID)s, ' 'project_id %(HTTP_X_SERVICE_PROJECT_ID)s, ' 'roles ' % expected_env, fake_logger.output) roles = ','.join([expected_env['HTTP_X_SERVICE_ROLES'], expected_env['HTTP_X_ROLES']]) for r in roles.split(','): self.assertIn(r, fake_logger.output) def test_composite_auth_invalid_service_token(self): token = self.token_dict['uuid_token_default'] service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_no_service_token(self): self.purge_service_token_expected_env() req = webob.Request.blank('/') req.headers['X-Auth-Token'] = self.token_dict['uuid_token_default'] # Ensure injection of service headers is not possible for key, value in self.service_token_expected_env.items(): header_key = key[len('HTTP_'):].replace('_', '-') req.headers[header_key] = value # Check arbitrary headers not removed req.headers['X-Foo'] = 'Bar' resp = req.get_response(self.middleware) for key in self.service_token_expected_env.keys(): header_key = key[len('HTTP_'):].replace('_', '-') self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, resp.status_int) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_invalid_user_token(self): token = 'invalid-token' service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_no_user_token(self): service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Service-Token': service_token}, expected_status=401) expected_body = b'The request you have made requires authentication.' self.assertThat(resp.body, matchers.Contains(expected_body)) def test_composite_auth_delay_ok(self): self.middleware._delay_auth_decision = True token = self.token_dict['uuid_token_default'] service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}) self.assertEqual(FakeApp.SUCCESS, resp.body) def test_composite_auth_delay_invalid_service_token(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() expected_env = { 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = self.token_dict['uuid_token_default'] service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=420) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_invalid_service_and_user_tokens(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = 'invalid-token' service_token = 'invalid-service-token' resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=419) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_no_service_token(self): self.middleware._delay_auth_decision = True self.purge_service_token_expected_env() req = webob.Request.blank('/') req.headers['X-Auth-Token'] = self.token_dict['uuid_token_default'] # Ensure injection of service headers is not possible for key, value in self.service_token_expected_env.items(): header_key = key[len('HTTP_'):].replace('_', '-') req.headers[header_key] = value # Check arbitrary headers not removed req.headers['X-Foo'] = 'Bar' resp = req.get_response(self.middleware) for key in self.service_token_expected_env.keys(): header_key = key[len('HTTP_'):].replace('_', '-') self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, resp.status_int) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_invalid_user_token(self): self.middleware._delay_auth_decision = True self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) token = 'invalid-token' service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Auth-Token': token, 'X-Service-Token': service_token}, expected_status=403) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def test_composite_auth_delay_no_user_token(self): self.middleware._delay_auth_decision = True self.purge_token_expected_env() expected_env = { 'HTTP_X_IDENTITY_STATUS': 'Invalid', } self.update_expected_env(expected_env) service_token = self.token_dict['uuid_service_token_default'] resp = self.call_middleware(headers={'X-Service-Token': service_token}, expected_status=403) self.assertEqual(FakeApp.FORBIDDEN, resp.body) def assert_kerberos_composite_bind(self, user_token, service_token, bind_level): conf = { 'enforce_token_bind': bind_level, 'auth_version': self.auth_version, } self.set_middleware(conf=conf) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = user_token req.headers['X-Service-Token'] = service_token req.environ['REMOTE_USER'] = self.examples.SERVICE_KERBEROS_BIND req.environ['AUTH_TYPE'] = 'Negotiate' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_int) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertIn('keystone.token_info', req.environ) def test_composite_auth_with_bind(self): token = self.token_dict['uuid_token_bind'] service_token = self.token_dict['uuid_service_token_bind'] self.assert_kerberos_composite_bind(token, service_token, bind_level='required') class v3CompositeAuthTests(BaseAuthTokenMiddlewareTest, CommonCompositeAuthTests, testresources.ResourcedTestCase): """Test auth_token middleware with v3 token based composite auth. Execute the Composite auth class tests, but with the auth_token middleware configured to expect v3 tokens back from a keystone server. """ resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(v3CompositeAuthTests, self).setUp( auth_version='v3', fake_app=v3CompositeFakeApp) uuid_token_default = self.examples.v3_UUID_TOKEN_DEFAULT uuid_serv_token_default = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT uuid_token_bind = self.examples.v3_UUID_TOKEN_BIND uuid_service_token_bind = self.examples.v3_UUID_SERVICE_TOKEN_BIND self.token_dict = { 'uuid_token_default': uuid_token_default, 'uuid_service_token_default': uuid_serv_token_default, 'uuid_token_bind': uuid_token_bind, 'uuid_service_token_bind': uuid_service_token_bind, } self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) # TODO(jamielennox): auth_token middleware uses a v2 admin token # regardless of the auth_version that is set. self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.token_expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) self.token_expected_env.update(EXPECTED_V3_DEFAULT_ENV_ADDITIONS) self.service_token_expected_env = dict( EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE) self.service_token_expected_env.update( EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS) self.set_middleware() def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) status = 200 response = "" if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksa_exceptions.ConnectFailure(msg) elif token_id == TIMEOUT_TOKEN: request_timeout_response(request, context) try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: status = 404 context.status_code = status return response class OtherTests(BaseAuthTokenMiddlewareTest): def setUp(self): super(OtherTests, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger()) def test_unknown_server_versions(self): versions = fixture.DiscoveryList(v2=False, v3_id='v4', href=BASE_URI) self.set_middleware() self.requests_mock.get(BASE_URI, json=versions, status_code=300) self.call_middleware(headers={'X-Auth-Token': uuid.uuid4().hex}, expected_status=503) self.assertIn('versions [v3.0]', self.logger.output) def _assert_auth_version(self, conf_version, identity_server_version): self.set_middleware(conf={'auth_version': conf_version}) identity_server = self.middleware._create_identity_server() self.assertEqual(identity_server_version, identity_server.auth_version) def test_micro_version(self): self._assert_auth_version('v3', (3, 0)) self._assert_auth_version('v3.0', (3, 0)) self._assert_auth_version('v3.1', (3, 0)) self._assert_auth_version('v3.2', (3, 0)) self._assert_auth_version('v3.9', (3, 0)) self._assert_auth_version('v3.3.1', (3, 0)) self._assert_auth_version('v3.3.5', (3, 0)) def test_default_auth_version(self): # VERSION_LIST_v3 contains both v2 and v3 version elements self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self._assert_auth_version(None, (3, 0)) def test_unsupported_auth_version(self): # If the requested version isn't supported we will use v3 self._assert_auth_version('v1', (3, 0)) self._assert_auth_version('v10', (3, 0)) class AuthProtocolLoadingTests(BaseAuthTokenMiddlewareTest): AUTH_URL = 'http://auth.url/prefix' DISC_URL = 'http://disc.url/prefix' KEYSTONE_BASE_URL = 'http://keystone.url/prefix' CRUD_URL = 'http://crud.url/prefix' KEYSTONE_URL = KEYSTONE_BASE_URL + '/v3' def setUp(self): super(AuthProtocolLoadingTests, self).setUp() self.project_id = uuid.uuid4().hex # first touch is to discover the available versions at the auth_url self.requests_mock.get(self.AUTH_URL, json=fixture.DiscoveryList(href=self.DISC_URL), status_code=300) # then we do discovery on the URL from the service catalog. In practice # this is mostly the same URL as before but test the full range. self.requests_mock.get(self.KEYSTONE_BASE_URL + '/', json=fixture.DiscoveryList(href=self.CRUD_URL), status_code=300) def good_request(self, app): # admin_token is the token that the service will get back from auth admin_token_id = uuid.uuid4().hex admin_token = fixture.V3Token(project_id=self.project_id) s = admin_token.add_service('identity', name='keystone') s.add_standard_endpoints(internal=self.KEYSTONE_URL) self.requests_mock.post('%s/v3/auth/tokens' % self.AUTH_URL, json=admin_token, headers={'X-Subject-Token': admin_token_id}) # user_token is the data from the user's inputted token user_token_id = uuid.uuid4().hex user_token = fixture.V3Token() user_token.set_project_scope() request_headers = {'X-Subject-Token': user_token_id, 'X-Auth-Token': admin_token_id} self.requests_mock.get('%s/v3/auth/tokens' % self.KEYSTONE_BASE_URL, request_headers=request_headers, json=user_token, headers={'X-Subject-Token': uuid.uuid4().hex}) resp = self.call(app, headers={'X-Auth-Token': user_token_id}) return resp def test_loading_password_plugin(self): # the password options aren't set on config until loading time, but we # need them set so we can override the values for testing, so force it opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) project_id = uuid.uuid4().hex # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_type='password', username='testuser', password='testpass', auth_url=self.AUTH_URL, project_id=project_id, user_domain_id='userdomainid', group=_base.AUTHTOKEN_GROUP) body = uuid.uuid4().hex app = self.create_simple_middleware(body=body) resp = self.good_request(app) self.assertEqual(body.encode(), resp.body) @staticmethod def get_plugin(app): return app._identity_server._adapter.auth def test_invalid_plugin_fails_to_initialize(self): loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type=uuid.uuid4().hex, group=_base.AUTHTOKEN_GROUP) self.assertRaises( ksa_exceptions.NoMatchingPlugin, self.create_simple_middleware) def test_plugin_loading_mixed_opts(self): # some options via override and some via conf opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) username = 'testuser' password = 'testpass' # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_type='password', auth_url='http://keystone.test:5000', password=password, project_id=self.project_id, user_domain_id='userdomainid', group=_base.AUTHTOKEN_GROUP) conf = {'username': username, 'auth_url': self.AUTH_URL} body = uuid.uuid4().hex app = self.create_simple_middleware(body=body, conf=conf) resp = self.good_request(app) self.assertEqual(body.encode(), resp.body) plugin = self.get_plugin(app) self.assertEqual(self.AUTH_URL, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(password, plugin._password) self.assertEqual(self.project_id, plugin._project_id) def test_plugin_loading_with_auth_section(self): # some options via override and some via conf section = 'testsection' username = 'testuser' password = 'testpass' loading.register_auth_conf_options(self.cfg.conf, group=section) opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=section) # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_section=section, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type='password', auth_url=self.AUTH_URL, password=password, project_id=self.project_id, user_domain_id='userdomainid', group=section) conf = {'username': username} body = uuid.uuid4().hex app = self.create_simple_middleware(body=body, conf=conf) resp = self.good_request(app) self.assertEqual(body.encode(), resp.body) plugin = self.get_plugin(app) self.assertEqual(self.AUTH_URL, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(password, plugin._password) self.assertEqual(self.project_id, plugin._project_id) class TestAuthPluginUserAgentGeneration(BaseAuthTokenMiddlewareTest): def setUp(self): super(TestAuthPluginUserAgentGeneration, self).setUp() self.auth_url = uuid.uuid4().hex self.project_id = uuid.uuid4().hex self.username = uuid.uuid4().hex self.password = uuid.uuid4().hex self.section = uuid.uuid4().hex self.user_domain_id = uuid.uuid4().hex loading.register_auth_conf_options(self.cfg.conf, group=self.section) opts = loading.get_auth_plugin_conf_options('password') self.cfg.register_opts(opts, group=self.section) # Register the authentication options loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) # configure the authentication options self.cfg.config(auth_section=self.section, group=_base.AUTHTOKEN_GROUP) self.cfg.config(auth_type='password', password=self.password, project_id=self.project_id, user_domain_id=self.user_domain_id, group=self.section) def test_no_project_configured(self): ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url} app = self._create_app(conf, '', ksm_version) self._assert_user_agent(app, '', ksm_version) def test_project_in_configuration(self): project = uuid.uuid4().hex project_version = uuid.uuid4().hex ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url, 'project': project} app = self._create_app(conf, project_version, ksm_version) project_with_version = '{0}/{1} '.format(project, project_version) self._assert_user_agent(app, project_with_version, ksm_version) def test_project_not_installed_results_in_unknown_version(self): project = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url, 'project': project} v = pbr.version.VersionInfo('keystonemiddleware').version_string() app = self.create_simple_middleware(conf=conf, use_global_conf=True) project_with_version = '{0}/{1} '.format(project, 'unknown') self._assert_user_agent(app, project_with_version, v) def test_project_in_oslo_configuration(self): project = uuid.uuid4().hex project_version = uuid.uuid4().hex ksm_version = uuid.uuid4().hex conf = {'username': self.username, 'auth_url': self.auth_url} with mock.patch.object(self.cfg.conf, 'project', new=project, create=True): app = self._create_app(conf, project_version, ksm_version) project = '{0}/{1} '.format(project, project_version) self._assert_user_agent(app, project, ksm_version) def _create_app(self, conf, project_version, ksm_version): fake_im = mock.Mock() fake_im.return_value = project_version fake_version_info = mock.Mock() fake_version_info.version_string.return_value = ksm_version fake_pbr_version = mock.Mock() fake_pbr_version.VersionInfo.return_value = fake_version_info body = uuid.uuid4().hex at_im = 'keystonemiddleware._common.config.importlib.metadata.version' at_pbr = 'keystonemiddleware._common.config.pbr.version' with mock.patch(at_im, new=fake_im): with mock.patch(at_pbr, new=fake_pbr_version): return self.create_simple_middleware(body=body, conf=conf) def _assert_user_agent(self, app, project, ksm_version): sess = app._identity_server._adapter.session expected_ua = ('{0}keystonemiddleware.auth_token/{1}' .format(project, ksm_version)) self.assertThat(sess.user_agent, matchers.StartsWith(expected_ua)) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_base_middleware.py0000664000175000017500000001735200000000000032434 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime from unittest import mock import uuid from keystoneauth1 import fixture import testtools import webob from keystonemiddleware import auth_token from keystonemiddleware.auth_token import _request class FakeApp(object): @webob.dec.wsgify def __call__(self, req): return webob.Response() class FetchingMiddleware(auth_token.BaseAuthProtocol): def __init__(self, app, token_dict={}, **kwargs): super(FetchingMiddleware, self).__init__(app, **kwargs) self.token_dict = token_dict def fetch_token(self, token, **kwargs): try: return self.token_dict[token] except KeyError: raise auth_token.InvalidToken() class BaseAuthProtocolTests(testtools.TestCase): @mock.patch.multiple(auth_token.BaseAuthProtocol, process_request=mock.DEFAULT, process_response=mock.DEFAULT) def test_process_flow(self, process_request, process_response): m = auth_token.BaseAuthProtocol(FakeApp()) process_request.return_value = None process_response.side_effect = lambda x: x req = webob.Request.blank('/', method='GET') resp = req.get_response(m) self.assertEqual(200, resp.status_code) self.assertEqual(1, process_request.call_count) self.assertIsInstance(process_request.call_args[0][0], _request._AuthTokenRequest) self.assertEqual(1, process_response.call_count) self.assertIsInstance(process_response.call_args[0][0], webob.Response) @classmethod def call(cls, middleware, method='GET', path='/', headers=None): req = webob.Request.blank(path) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) resp.request = req return resp def test_good_v3_user_token(self): t = fixture.V3Token() t.set_project_scope() role = t.add_role() token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual(token_id, req.headers['X-Auth-Token'].strip()) self.assertEqual('Confirmed', req.headers['X-Identity-Status']) self.assertNotIn('X-Service-Token', req.headers) p = req.environ['keystone.token_auth'] self.assertTrue(p.has_user_token) self.assertFalse(p.has_service_token) self.assertEqual(t.project_id, p.user.project_id) self.assertEqual(t.project_domain_id, p.user.project_domain_id) self.assertEqual(t.user_id, p.user.user_id) self.assertEqual(t.user_domain_id, p.user.user_domain_id) self.assertIn(role['name'], p.user.role_names) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict) self.call(m, headers={'X-Auth-Token': token_id}) # also try with whitespace in the token self.call(m, headers={'X-Auth-Token': token_id + ' '}) self.call(m, headers={'X-Auth-Token': token_id + '\r'}) def test_invalid_user_token(self): token_id = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Identity-Status']) self.assertEqual(token_id, req.headers['X-Auth-Token']) return webob.Response() m = FetchingMiddleware(_do_cb) self.call(m, headers={'X-Auth-Token': token_id}) def test_expired_user_token(self): t = fixture.V3Token() t.set_project_scope() t.expires = ( datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=10)) token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Identity-Status']) self.assertEqual(token_id, req.headers['X-Auth-Token']) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict=token_dict) self.call(m, headers={'X-Auth-Token': token_id}) def test_good_v3_service_token(self): t = fixture.V3Token() t.set_project_scope() role = t.add_role() token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual(token_id, req.headers['X-Service-Token'].strip()) self.assertEqual('Confirmed', req.headers['X-Service-Identity-Status']) self.assertNotIn('X-Auth-Token', req.headers) p = req.environ['keystone.token_auth'] self.assertFalse(p.has_user_token) self.assertTrue(p.has_service_token) self.assertEqual(t.project_id, p.service.project_id) self.assertEqual(t.project_domain_id, p.service.project_domain_id) self.assertEqual(t.user_id, p.service.user_id) self.assertEqual(t.user_domain_id, p.service.user_domain_id) self.assertIn(role['name'], p.service.role_names) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict) self.call(m, headers={'X-Service-Token': token_id}) # also try with whitespace in the token self.call(m, headers={'X-Service-Token': token_id + ' '}) self.call(m, headers={'X-Service-Token': token_id + '\r'}) def test_invalid_service_token(self): token_id = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Service-Identity-Status']) self.assertEqual(token_id, req.headers['X-Service-Token']) return webob.Response() m = FetchingMiddleware(_do_cb) self.call(m, headers={'X-Service-Token': token_id}) def test_expired_service_token(self): t = fixture.V3Token() t.set_project_scope() t.expires = ( datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=10)) token_id = uuid.uuid4().hex token_dict = {token_id: t} @webob.dec.wsgify def _do_cb(req): self.assertEqual('Invalid', req.headers['X-Service-Identity-Status']) self.assertEqual(token_id, req.headers['X-Service-Token']) return webob.Response() m = FetchingMiddleware(_do_cb, token_dict=token_dict) self.call(m, headers={'X-Service-Token': token_id}) def test_base_doesnt_block_project_id(self): # X-Project-Id and X-Domain-Id must pass for tokenless/x509 auth project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex body = uuid.uuid4().hex @webob.dec.wsgify def _do_cb(req): self.assertEqual(project_id, req.headers['X-Project-Id']) self.assertEqual(domain_id, req.headers['X-Domain-Id']) return webob.Response(body, 200) m = FetchingMiddleware(_do_cb) resp = self.call(m, headers={'X-Project-Id': project_id, 'X-Domain-Id': domain_id}) self.assertEqual(body, resp.text) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_cache.py0000664000175000017500000001613300000000000030364 0ustar00zuulzuul00000000000000# 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 fixtures from unittest import mock from keystonemiddleware.auth_token import _cache from keystonemiddleware.auth_token import _exceptions as exc from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit import utils MEMCACHED_SERVERS = ['localhost:11211'] MEMCACHED_AVAILABLE = None class TestCacheSetup(base.BaseAuthTokenTestCase): def test_assert_valid_memcache_protection_config(self): # test missing memcache_secret_key conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'Encrypt' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) # test invalue memcache_security_strategy conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'whatever' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) # test missing memcache_secret_key conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mac' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'Encrypt', 'memcache_secret_key': '' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mAc', 'memcache_secret_key': '' } self.assertRaises(exc.ConfigurationError, self.create_simple_middleware, conf=conf) class NoMemcacheAuthToken(base.BaseAuthTokenTestCase): """These tests will not have the memcache module available.""" def setUp(self): super(NoMemcacheAuthToken, self).setUp() self.useFixture(utils.DisableModuleFixture('memcache')) def test_nomemcache(self): conf = { 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': '1234', 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'www_authenticate_uri': 'https://keystone.example.com:1234', } self.create_simple_middleware(conf=conf) class TestLiveMemcache(base.BaseAuthTokenTestCase): def setUp(self): super(TestLiveMemcache, self).setUp() global MEMCACHED_AVAILABLE if MEMCACHED_AVAILABLE is None: try: import memcache c = memcache.Client(MEMCACHED_SERVERS) c.set('ping', 'pong', time=1) MEMCACHED_AVAILABLE = c.get('ping') == 'pong' except ImportError: MEMCACHED_AVAILABLE = False if not MEMCACHED_AVAILABLE: self.skipTest('memcached not available') def test_encrypt_cache_data(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'encrypt', 'memcache_secret_key': 'mysecret' } token = uuid.uuid4().hex.encode() data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) @mock.patch("keystonemiddleware.auth_token._memcache_crypt.unprotect_data") def test_corrupted_cache_data(self, mocked_decrypt_data): mocked_decrypt_data.side_effect = Exception("corrupted") conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'encrypt', 'memcache_secret_key': 'mysecret' } token = uuid.uuid4().hex.encode() data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertIsNone(token_cache.get(token)) def test_sign_cache_data(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_security_strategy': 'mac', 'memcache_secret_key': 'mysecret' } token = uuid.uuid4().hex.encode() data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) def test_no_memcache_protection(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_secret_key': 'mysecret' } token = uuid.uuid4().hex.encode() data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) def test_memcache_pool(self): conf = { 'memcached_servers': ','.join(MEMCACHED_SERVERS), 'memcache_use_advanced_pool': True } token = uuid.uuid4().hex.encode() data = uuid.uuid4().hex token_cache = self.create_simple_middleware(conf=conf)._token_cache token_cache.initialize({}) token_cache.set(token, data) self.assertEqual(token_cache.get(token), data) class TestMemcachePoolAbstraction(utils.TestCase): def setUp(self): super(TestMemcachePoolAbstraction, self).setUp() self.useFixture(fixtures.MockPatch( 'oslo_cache._memcache_pool._MemcacheClient')) def test_abstraction_layer_reserve_places_connection_back_in_pool(self): cache_pool = _cache._MemcacheClientPool( memcache_servers=[], arguments={}, maxsize=1, unused_timeout=10) conn = None with cache_pool.reserve() as client: self.assertEqual(cache_pool._pool._acquired, 1) conn = client self.assertEqual(cache_pool._pool._acquired, 0) with cache_pool.reserve() as client: # Make sure the connection we got before is in-fact the one we # get again. self.assertEqual(conn, client) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_config.py0000664000175000017500000001200700000000000030562 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import uuid from oslo_config import cfg from oslotest import createfile from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _opts from keystonemiddleware.tests.unit.auth_token import base def conf_get(app, *args, **kwargs): return app._conf.get(*args, **kwargs) class TestAuthPluginLocalOsloConfig(base.BaseAuthTokenTestCase): def setUp(self): super(TestAuthPluginLocalOsloConfig, self).setUp() self.project = uuid.uuid4().hex # NOTE(cdent): The options below are selected from those # which are statically registered by auth_token middleware # in the 'keystone_authtoken' group. Additional options, from # plugins, are registered dynamically so must not be used here. self.oslo_options = { 'www_authenticate_uri': uuid.uuid4().hex, 'identity_uri': uuid.uuid4().hex, } self.local_oslo_config = cfg.ConfigOpts() self.local_oslo_config.register_group( cfg.OptGroup(name='keystone_authtoken')) self.local_oslo_config.register_opts(_opts._OPTS, group='keystone_authtoken') self.local_oslo_config.register_opts(_auth.OPTS, group='keystone_authtoken') for option, value in self.oslo_options.items(): self.local_oslo_config.set_override(option, value, 'keystone_authtoken') self.local_oslo_config(args=[], project=self.project) self.file_options = { 'auth_type': 'password', 'www_authenticate_uri': uuid.uuid4().hex, 'password': uuid.uuid4().hex, } content = ("[DEFAULT]\n" "test_opt=15\n" "[keystone_authtoken]\n" "auth_type=%(auth_type)s\n" "www_authenticate_uri=%(www_authenticate_uri)s\n" "auth_url=%(www_authenticate_uri)s\n" "password=%(password)s\n" % self.file_options) self.conf_file_fixture = self.useFixture( createfile.CreateFileWithContent(self.project, content)) def _create_app(self, conf, project_version=None): if not project_version: project_version = uuid.uuid4().hex fake_im = mock.Mock() fake_im.return_value = project_version body = uuid.uuid4().hex at_im = 'keystonemiddleware._common.config.importlib.metadata.version' with mock.patch(at_im, new=fake_im): # use_global_conf is poorly named. What it means is # don't use the config created in test setUp. return self.create_simple_middleware(body=body, conf=conf, use_global_conf=True) def test_project_in_local_oslo_configuration(self): conf = {'oslo_config_project': self.project, 'oslo_config_file': self.conf_file_fixture.path} app = self._create_app(conf) for option in self.file_options: self.assertEqual(self.file_options[option], conf_get(app, option), option) def test_passed_oslo_configuration(self): conf = {'oslo_config_config': self.local_oslo_config} app = self._create_app(conf) for option in self.oslo_options: self.assertEqual(self.oslo_options[option], conf_get(app, option)) def test_passed_oslo_configuration_with_deprecated_ones(self): deprecated_opt = cfg.IntOpt('test_opt', deprecated_for_removal=True) cfg.CONF.register_opt(deprecated_opt) cfg.CONF(args=[], default_config_files=[self.conf_file_fixture.path]) conf = {'oslo_config_config': cfg.CONF} # success to init AuthProtocol self._create_app(conf) def test_passed_oslo_configuration_wins(self): """oslo_config_config has precedence over oslo_config_project.""" conf = {'oslo_config_project': self.project, 'oslo_config_config': self.local_oslo_config, 'oslo_config_file': self.conf_file_fixture.path} app = self._create_app(conf) for option in self.oslo_options: self.assertEqual(self.oslo_options[option], conf_get(app, option)) self.assertNotEqual(self.file_options['www_authenticate_uri'], conf_get(app, 'www_authenticate_uri')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_connection_pool.py0000664000175000017500000001112000000000000032500 0ustar00zuulzuul00000000000000# 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 queue import time from unittest import mock from oslo_cache import _memcache_pool import testtools from testtools import matchers from keystonemiddleware.tests.unit import utils class _TestConnectionPool(_memcache_pool.ConnectionPool): destroyed_value = 'destroyed' def _create_connection(self): return mock.MagicMock() def _destroy_connection(self, conn): conn(self.destroyed_value) class TestConnectionPool(utils.TestCase): def setUp(self): super(TestConnectionPool, self).setUp() self.unused_timeout = 10 self.maxsize = 2 self.connection_pool = _TestConnectionPool( maxsize=self.maxsize, unused_timeout=self.unused_timeout) def test_get_context_manager(self): self.assertThat(self.connection_pool.queue, matchers.HasLength(0)) with self.connection_pool.acquire() as conn: self.assertEqual(1, self.connection_pool._acquired) self.assertEqual(0, self.connection_pool._acquired) self.assertThat(self.connection_pool.queue, matchers.HasLength(1)) self.assertEqual(conn, self.connection_pool.queue[0].connection) def test_cleanup_pool(self): self.test_get_context_manager() newtime = time.time() + self.unused_timeout * 2 non_expired_connection = _memcache_pool._PoolItem( ttl=(newtime * 2), connection=mock.MagicMock()) self.connection_pool.queue.append(non_expired_connection) self.assertThat(self.connection_pool.queue, matchers.HasLength(2)) with mock.patch.object(time, 'time', return_value=newtime): conn = self.connection_pool.queue[0].connection with self.connection_pool.acquire(): pass conn.assert_has_calls( [mock.call(self.connection_pool.destroyed_value)]) self.assertThat(self.connection_pool.queue, matchers.HasLength(1)) self.assertEqual(0, non_expired_connection.connection.call_count) def test_acquire_conn_exception_returns_acquired_count(self): class TestException(Exception): pass with mock.patch.object(_TestConnectionPool, '_create_connection', side_effect=TestException): with testtools.ExpectedException(TestException): with self.connection_pool.acquire(): pass self.assertThat(self.connection_pool.queue, matchers.HasLength(0)) self.assertEqual(0, self.connection_pool._acquired) def test_connection_pool_limits_maximum_connections(self): # NOTE(morganfainberg): To ensure we don't lockup tests until the # job limit, explicitly call .get_nowait() and .put_nowait() in this # case. conn1 = self.connection_pool.get_nowait() conn2 = self.connection_pool.get_nowait() # Use a nowait version to raise an Empty exception indicating we would # not get another connection until one is placed back into the queue. self.assertRaises(queue.Empty, self.connection_pool.get_nowait) # Place the connections back into the pool. self.connection_pool.put_nowait(conn1) self.connection_pool.put_nowait(conn2) # Make sure we can get a connection out of the pool again. self.connection_pool.get_nowait() def test_connection_pool_maximum_connection_get_timeout(self): connection_pool = _TestConnectionPool( maxsize=1, unused_timeout=self.unused_timeout, conn_get_timeout=0) def _acquire_connection(): with connection_pool.acquire(): pass # Make sure we've consumed the only available connection from the pool conn = connection_pool.get_nowait() self.assertRaises(_memcache_pool.exception.QueueEmpty, _acquire_connection) # Put the connection back and ensure we can acquire the connection # after it is available. connection_pool.put_nowait(conn) _acquire_connection() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_memcache_crypt.py0000664000175000017500000000646300000000000032311 0ustar00zuulzuul00000000000000# 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 struct from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt from keystonemiddleware.tests.unit import utils class MemcacheCryptPositiveTests(utils.BaseTestCase): def _setup_keys(self, strategy): return memcache_crypt.derive_keys('token', 'secret', strategy) def test_derive_keys(self): keys = self._setup_keys(b'strategy') self.assertEqual(len(keys['ENCRYPTION']), len(keys['CACHE_KEY'])) self.assertEqual(len(keys['CACHE_KEY']), len(keys['MAC'])) self.assertNotEqual(keys['ENCRYPTION'], keys['MAC']) self.assertIn('strategy', keys.keys()) def test_key_strategy_diff(self): k1 = self._setup_keys(b'MAC') k2 = self._setup_keys(b'ENCRYPT') self.assertNotEqual(k1, k2) def test_sign_data(self): keys = self._setup_keys(b'MAC') sig = memcache_crypt.sign_data(keys['MAC'], b'data') self.assertEqual(len(sig), memcache_crypt.DIGEST_LENGTH_B64) def test_encryption(self): int2byte = struct.Struct(">B").pack keys = self._setup_keys(b'ENCRYPT') # what you put in is what you get out for data in [b'data', b'1234567890123456', b'\x00\xFF' * 13 ] + [int2byte(x % 256) * x for x in range(768)]: crypt = memcache_crypt.encrypt_data(keys['ENCRYPTION'], data) decrypt = memcache_crypt.decrypt_data(keys['ENCRYPTION'], crypt) self.assertEqual(data, decrypt) self.assertRaises(memcache_crypt.DecryptError, memcache_crypt.decrypt_data, keys['ENCRYPTION'], crypt[:-1]) def test_protect_wrappers(self): data = b'My Pretty Little Data' for strategy in [b'MAC', b'ENCRYPT']: keys = self._setup_keys(strategy) protected = memcache_crypt.protect_data(keys, data) self.assertNotEqual(protected, data) if strategy == b'ENCRYPT': self.assertNotIn(data, protected) unprotected = memcache_crypt.unprotect_data(keys, protected) self.assertEqual(data, unprotected) self.assertRaises(memcache_crypt.InvalidMacError, memcache_crypt.unprotect_data, keys, protected[:-1]) self.assertIsNone(memcache_crypt.unprotect_data(keys, None)) def test_no_cryptography(self): aes = memcache_crypt.ciphers memcache_crypt.ciphers = None self.assertRaises(memcache_crypt.CryptoUnavailableError, memcache_crypt.encrypt_data, 'token', 'secret', 'data') memcache_crypt.ciphers = aes ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_request.py0000664000175000017500000002331200000000000031006 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools import uuid from keystoneauth1 import access from keystoneauth1 import fixture from keystonemiddleware.auth_token import _request from keystonemiddleware.tests.unit import utils class RequestObjectTests(utils.TestCase): def setUp(self): super(RequestObjectTests, self).setUp() self.request = _request._AuthTokenRequest.blank('/') def test_setting_user_token_valid(self): self.assertNotIn('X-Identity-Status', self.request.headers) self.request.user_token_valid = True self.assertEqual('Confirmed', self.request.headers['X-Identity-Status']) self.assertTrue(self.request.user_token_valid) self.request.user_token_valid = False self.assertEqual('Invalid', self.request.headers['X-Identity-Status']) self.assertFalse(self.request.user_token_valid) def test_setting_service_token_valid(self): self.assertNotIn('X-Service-Identity-Status', self.request.headers) self.request.service_token_valid = True self.assertEqual('Confirmed', self.request.headers['X-Service-Identity-Status']) self.assertTrue(self.request.service_token_valid) self.request.service_token_valid = False self.assertEqual('Invalid', self.request.headers['X-Service-Identity-Status']) self.assertFalse(self.request.service_token_valid) def test_removing_headers(self): GOOD = ('X-Auth-Token', 'unknownstring', uuid.uuid4().hex) BAD = ('X-Domain-Id', 'X-Domain-Name', 'X-Project-Id', 'X-Project-Name', 'X-Project-Domain-Id', 'X-Project-Domain-Name', 'X-User-Id', 'X-User-Name', 'X-User-Domain-Id', 'X-User-Domain-Name', 'X-Roles', 'X-Identity-Status', 'X-Service-Domain-Id', 'X-Service-Domain-Name', 'X-Service-Project-Id', 'X-Service-Project-Name', 'X-Service-Project-Domain-Id', 'X-Service-Project-Domain-Name', 'X-Service-User-Id', 'X-Service-User-Name', 'X-Service-User-Domain-Id', 'X-Service-User-Domain-Name', 'X-Service-Roles', 'X-Service-Identity-Status', 'X-Service-Catalog', 'X-Role', 'X-User', 'X-Tenant-Id', 'X-Tenant-Name', 'X-Tenant', ) header_vals = {} for header in itertools.chain(GOOD, BAD): v = uuid.uuid4().hex header_vals[header] = v self.request.headers[header] = v self.request.remove_auth_headers() for header in BAD: self.assertNotIn(header, self.request.headers) for header in GOOD: self.assertEqual(header_vals[header], self.request.headers[header]) def _test_v3_headers(self, token, prefix): self.assertEqual(token.domain_id, self.request.headers['X%s-Domain-Id' % prefix]) self.assertEqual(token.domain_name, self.request.headers['X%s-Domain-Name' % prefix]) self.assertEqual(token.project_id, self.request.headers['X%s-Project-Id' % prefix]) self.assertEqual(token.project_name, self.request.headers['X%s-Project-Name' % prefix]) self.assertEqual( token.project_domain_id, self.request.headers['X%s-Project-Domain-Id' % prefix]) self.assertEqual( token.project_domain_name, self.request.headers['X%s-Project-Domain-Name' % prefix]) self.assertEqual(token.user_id, self.request.headers['X%s-User-Id' % prefix]) self.assertEqual(token.user_name, self.request.headers['X%s-User-Name' % prefix]) self.assertEqual( token.user_domain_id, self.request.headers['X%s-User-Domain-Id' % prefix]) self.assertEqual( token.user_domain_name, self.request.headers['X%s-User-Domain-Name' % prefix]) def test_project_scoped_user_headers(self): token = fixture.V3Token() token.set_project_scope() token_id = uuid.uuid4().hex auth_ref = access.create(auth_token=token_id, body=token) self.request.set_user_headers(auth_ref) self._test_v3_headers(token, '') def test_project_scoped_service_headers(self): token = fixture.V3Token() token.set_project_scope() token_id = uuid.uuid4().hex auth_ref = access.create(auth_token=token_id, body=token) self.request.set_service_headers(auth_ref) self._test_v3_headers(token, '-Service') def test_auth_type(self): self.assertIsNone(self.request.auth_type) self.request.environ['AUTH_TYPE'] = 'NeGoTiatE' self.assertEqual('negotiate', self.request.auth_type) def test_user_token(self): token = uuid.uuid4().hex self.assertIsNone(self.request.user_token) self.request.headers['X-Auth-Token'] = token self.assertEqual(token, self.request.user_token) def test_storage_token(self): storage_token = uuid.uuid4().hex user_token = uuid.uuid4().hex self.assertIsNone(self.request.user_token) self.request.headers['X-Storage-Token'] = storage_token self.assertEqual(storage_token, self.request.user_token) self.request.headers['X-Auth-Token'] = user_token self.assertEqual(user_token, self.request.user_token) def test_service_token(self): token = uuid.uuid4().hex self.assertIsNone(self.request.service_token) self.request.headers['X-Service-Token'] = token self.assertEqual(token, self.request.service_token) def test_token_auth(self): plugin = object() self.assertNotIn('keystone.token_auth', self.request.environ) self.request.token_auth = plugin self.assertIs(plugin, self.request.environ['keystone.token_auth']) self.assertIs(plugin, self.request.token_auth) def test_token_info(self): info = fixture.V3Token() self.assertNotIn('keystone.token_info', self.request.environ) self.request.token_info = info self.assertIs(info, self.request.environ['keystone.token_info']) self.assertIs(info, self.request.token_info) def test_token_without_catalog(self): token = fixture.V3Token() auth_ref = access.create(body=token) self.request.set_service_catalog_headers(auth_ref) self.assertNotIn('X-Service-Catalog', self.request.headers) class CatalogConversionTests(utils.TestCase): PUBLIC_URL = 'http://server:5000/v2.0' ADMIN_URL = 'http://admin:35357/v2.0' INTERNAL_URL = 'http://internal:5000/v2.0' REGION_ONE = 'RegionOne' REGION_TWO = 'RegionTwo' REGION_THREE = 'RegionThree' def test_basic_convert(self): token = fixture.V3Token() s = token.add_service(type='identity') s.add_standard_endpoints(public=self.PUBLIC_URL, admin=self.ADMIN_URL, internal=self.INTERNAL_URL, region=self.REGION_ONE) auth_ref = access.create(body=token) catalog_data = auth_ref.service_catalog.catalog catalog = _request._normalize_catalog(catalog_data) self.assertEqual(1, len(catalog)) service = catalog[0] self.assertEqual(1, len(service['endpoints'])) endpoints = service['endpoints'][0] self.assertEqual('identity', service['type']) self.assertEqual(4, len(endpoints)) self.assertEqual(self.PUBLIC_URL, endpoints['publicURL']) self.assertEqual(self.ADMIN_URL, endpoints['adminURL']) self.assertEqual(self.INTERNAL_URL, endpoints['internalURL']) self.assertEqual(self.REGION_ONE, endpoints['region']) def test_multi_region(self): token = fixture.V3Token() s = token.add_service(type='identity') s.add_endpoint('internal', self.INTERNAL_URL, region=self.REGION_ONE) s.add_endpoint('public', self.PUBLIC_URL, region=self.REGION_TWO) s.add_endpoint('admin', self.ADMIN_URL, region=self.REGION_THREE) auth_ref = access.create(body=token) catalog_data = auth_ref.service_catalog.catalog catalog = _request._normalize_catalog(catalog_data) self.assertEqual(1, len(catalog)) service = catalog[0] # the 3 regions will come through as 3 separate endpoints expected = [{'internalURL': self.INTERNAL_URL, 'region': self.REGION_ONE}, {'publicURL': self.PUBLIC_URL, 'region': self.REGION_TWO}, {'adminURL': self.ADMIN_URL, 'region': self.REGION_THREE}] self.assertEqual('identity', service['type']) self.assertEqual(3, len(service['endpoints'])) for e in expected: self.assertIn(e, expected) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/auth_token/test_user_auth_plugin.py0000664000175000017500000001406600000000000032701 0ustar00zuulzuul00000000000000# 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 keystoneauth1 import fixture from keystoneauth1 import loading from keystonemiddleware.auth_token import _base from keystonemiddleware.tests.unit.auth_token import base # NOTE(jamielennox): just some sample values that we can use for testing BASE_URI = 'https://keystone.example.com:1234' AUTH_URL = 'https://keystone.auth.com:1234' class BaseUserPluginTests(object): def configure_middleware(self, auth_type, **kwargs): opts = loading.get_auth_plugin_conf_options(auth_type) self.cfg.register_opts(opts, group=_base.AUTHTOKEN_GROUP) # Since these tests cfg.config() themselves rather than waiting for # auth_token to do it on __init__ we need to register the base auth # options (e.g., auth_plugin) loading.register_auth_conf_options(self.cfg.conf, group=_base.AUTHTOKEN_GROUP) self.cfg.config(group=_base.AUTHTOKEN_GROUP, auth_type=auth_type, **kwargs) def assertTokenDataEqual(self, token_id, token, token_data): self.assertEqual(token_id, token_data.auth_token) self.assertEqual(token.user_id, token_data.user_id) try: trust_id = token.trust_id except KeyError: trust_id = None self.assertEqual(trust_id, token_data.trust_id) self.assertEqual(self.get_role_names(token), token_data.role_names) def get_plugin(self, token_id, service_token_id=None): headers = {'X-Auth-Token': token_id} if service_token_id: headers['X-Service-Token'] = service_token_id m = self.create_simple_middleware() resp = self.call(m, headers=headers) return resp.request.environ['keystone.token_auth'] def test_user_information(self): token_id, token = self.get_token() plugin = self.get_plugin(token_id) self.assertTokenDataEqual(token_id, token, plugin.user) self.assertFalse(plugin.has_service_token) self.assertIsNone(plugin.service) def test_with_service_information(self): token_id, token = self.get_token() service_id, service = self.get_token(service=True) plugin = self.get_plugin(token_id, service_id) self.assertTokenDataEqual(token_id, token, plugin.user) self.assertTokenDataEqual(service_id, service, plugin.service) class V3UserPluginTests(BaseUserPluginTests, base.BaseAuthTokenTestCase): def setUp(self): super(V3UserPluginTests, self).setUp() self.service_token_id = uuid.uuid4().hex self.service_token = fixture.V3Token() s = self.service_token.add_service('identity', name='keystone') s.add_standard_endpoints(public=BASE_URI, admin=BASE_URI, internal=BASE_URI) self.configure_middleware(auth_type='v3password', auth_url='%s/v3/' % AUTH_URL, user_id=self.service_token.user_id, password=uuid.uuid4().hex, project_id=self.service_token.project_id) auth_discovery = fixture.DiscoveryList(href=AUTH_URL) self.requests_mock.get(AUTH_URL, json=auth_discovery) base_discovery = fixture.DiscoveryList(href=BASE_URI) self.requests_mock.get(BASE_URI, json=base_discovery) self.requests_mock.post( '%s/v3/auth/tokens' % AUTH_URL, headers={'X-Subject-Token': self.service_token_id}, json=self.service_token) def get_role_names(self, token): return [x['name'] for x in token['token'].get('roles', [])] def get_token(self, project=True, service=False): token_id = uuid.uuid4().hex token = fixture.V3Token() if project: token.set_project_scope() token.add_role() if service: token.add_role('service') request_headers = {'X-Auth-Token': self.service_token_id, 'X-Subject-Token': token_id} headers = {'X-Subject-Token': token_id} self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, request_headers=request_headers, headers=headers, json=token) return token_id, token def assertTokenDataEqual(self, token_id, token, token_data): super(V3UserPluginTests, self).assertTokenDataEqual(token_id, token, token_data) self.assertEqual(token.user_domain_id, token_data.user_domain_id) self.assertEqual(token.project_id, token_data.project_id) self.assertEqual(token.project_domain_id, token_data.project_domain_id) def test_domain_scope(self): token_id, token = self.get_token(project=False) token.set_domain_scope() plugin = self.get_plugin(token_id) self.assertEqual(token.domain_id, plugin.user.domain_id) self.assertIsNone(plugin.user.project_id) def test_trust_scope(self): token_id, token = self.get_token(project=False) token.set_trust_scope() plugin = self.get_plugin(token_id) self.assertEqual(token.trust_id, plugin.user.trust_id) self.assertEqual(token.trustor_user_id, plugin.user.trustor_user_id) self.assertEqual(token.trustee_user_id, plugin.user.trustee_user_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/client_fixtures.py0000664000175000017500000005546400000000000027342 0ustar00zuulzuul00000000000000# Copyright 2013 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 base64 import datetime import hashlib import os import ssl import uuid from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.serialization import Encoding from cryptography import x509 import fixtures from keystoneauth1 import fixture from oslo_serialization import jsonutils import testresources TESTDIR = os.path.dirname(os.path.abspath(__file__)) ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..', '..')) class Examples(fixtures.Fixture): """Example tokens and certs loaded from the examples directory. To use this class correctly, the module needs to override the test suite class to use testresources.OptimisingTestSuite (otherwise the files will be read on every test). This is done by defining a load_tests function in the module, like this: def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) (see http://docs.python.org/2/library/unittest.html#load-tests-protocol ) """ def setUp(self): super(Examples, self).setUp() self.KERBEROS_BIND = 'USER@REALM' self.SERVICE_KERBEROS_BIND = 'SERVICE_USER@SERVICE_REALM' self.UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d" self.UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df' self.UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776' self.UUID_TOKEN_BIND = '3fc54048ad64405c98225ce0897af7c5' self.UUID_TOKEN_UNKNOWN_BIND = '8885fdf4d42e4fb9879e6379fa1eaf48' self.v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1' self.v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79' self.v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792' self.v3_UUID_TOKEN_BIND = '2f61f73e1c854cbb9534c487f9bd63c2' self.v3_UUID_TOKEN_UNKNOWN_BIND = '7ed9781b62cd4880b8d8c6788ab1d1e2' self.v3_SYSTEM_SCOPED_TOKEN = '9ca6e88364b6418a88ffc02e6a24afd8' self.UUID_SERVICE_TOKEN_DEFAULT = 'fe4c0710ec2f492748596c1b53ab124' self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab' self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229' self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05' self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex self.v3_APP_CRED_TOKEN = '6f506fa9641448bbaecbd12dd30678a9' self.v3_APP_CRED_ACCESS_RULES = 'c417747898c44629b08791f2579e40a5' self.v3_APP_CRED_EMPTY_ACCESS_RULES = 'c75905c307f04fdd9979126582d7aae' self.v3_APP_CRED_MATCHING_RULES = 'ad49decc7106489d95ca9ed874b6cb66' self.v3_OAUTH2_CREDENTIAL = uuid.uuid4().hex self.V3_OAUTH2_MTLS_CERTIFICATE = self._create_pem_certificate( self._create_dn( country_name='jp', state_or_province_name='kanagawa', locality_name='kawasaki', organization_name='fujitsu', organizational_unit_name='test', common_name='root' ) ) self.V3_OAUTH2_MTLS_CERTIFICATE_DIFF = self._create_pem_certificate( self._create_dn( country_name='jp', state_or_province_name='kanagawa', locality_name='kawasaki', organization_name='fujitsu', organizational_unit_name='test', common_name='diff' ) ) # JSON responses keyed by token ID self.TOKEN_RESPONSES = {} # basic values PROJECT_ID = 'tenant_id1' PROJECT_NAME = 'tenant_name1' USER_ID = 'user_id1' USER_NAME = 'user_name1' DOMAIN_ID = 'domain_id1' DOMAIN_NAME = 'domain_name1' ROLE_NAME1 = 'role1' ROLE_NAME2 = 'role2' SERVICE_PROJECT_ID = 'service_project_id1' SERVICE_PROJECT_NAME = 'service_project_name1' SERVICE_USER_ID = 'service_user_id1' SERVICE_USER_NAME = 'service_user_name1' SERVICE_DOMAIN_ID = 'service_domain_id1' SERVICE_DOMAIN_NAME = 'service_domain_name1' SERVICE_ROLE_NAME1 = 'service' SERVICE_ROLE_NAME2 = 'service_role2' APP_CRED_ID = 'app_cred_id1' self.SERVICE_TYPE = 'identity' self.UNVERSIONED_SERVICE_URL = 'https://keystone.example.com:1234/' self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + 'v2.0' # Generated V2 Tokens token = fixture.V2Token(token_id=self.UUID_TOKEN_DEFAULT, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint(public=self.SERVICE_URL) self.TOKEN_RESPONSES[self.UUID_TOKEN_DEFAULT] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_UNSCOPED, user_id=USER_ID, user_name=USER_NAME) self.TOKEN_RESPONSES[self.UUID_TOKEN_UNSCOPED] = token token = fixture.V2Token(token_id='valid-token', tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) self.TOKEN_RESPONSES[self.UUID_TOKEN_NO_SERVICE_CATALOG] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_BIND, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) token['access']['token']['bind'] = {'kerberos': self.KERBEROS_BIND} self.TOKEN_RESPONSES[self.UUID_TOKEN_BIND] = token token = fixture.V2Token(token_id=self.UUID_SERVICE_TOKEN_BIND, tenant_id=SERVICE_PROJECT_ID, tenant_name=SERVICE_PROJECT_NAME, user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME) token.add_role(SERVICE_ROLE_NAME1) token.add_role(SERVICE_ROLE_NAME2) token['access']['token']['bind'] = { 'kerberos': self.SERVICE_KERBEROS_BIND} self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_BIND] = token token = fixture.V2Token(token_id=self.UUID_TOKEN_UNKNOWN_BIND, tenant_id=PROJECT_ID, tenant_name=PROJECT_NAME, user_id=USER_ID, user_name=USER_NAME) token.add_role(ROLE_NAME1) token.add_role(ROLE_NAME2) token['access']['token']['bind'] = {'FOO': 'BAR'} self.TOKEN_RESPONSES[self.UUID_TOKEN_UNKNOWN_BIND] = token token = fixture.V2Token(token_id=self.UUID_SERVICE_TOKEN_DEFAULT, tenant_id=SERVICE_PROJECT_ID, tenant_name=SERVICE_PROJECT_NAME, user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME) token.add_role(name=SERVICE_ROLE_NAME1) token.add_role(name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint(public=self.SERVICE_URL) self.TOKEN_RESPONSES[self.UUID_SERVICE_TOKEN_DEFAULT] = token # Generated V3 Tokens token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(id=ROLE_NAME1, name=ROLE_NAME1) token.add_role(id=ROLE_NAME2, name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_DEFAULT] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_UNSCOPED] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME) token.system = {'all': True} token.add_role(id=ROLE_NAME1, name=ROLE_NAME1) token.add_role(id=ROLE_NAME2, name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_SYSTEM_SCOPED_TOKEN] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, domain_id=DOMAIN_ID, domain_name=DOMAIN_NAME) token.add_role(id=ROLE_NAME1, name=ROLE_NAME1) token.add_role(id=ROLE_NAME2, name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_DOMAIN_SCOPED] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'kerberos': self.KERBEROS_BIND} self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_BIND] = token token = fixture.V3Token(user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME, user_domain_id=SERVICE_DOMAIN_ID, user_domain_name=SERVICE_DOMAIN_NAME, project_id=SERVICE_PROJECT_ID, project_name=SERVICE_PROJECT_NAME, project_domain_id=SERVICE_DOMAIN_ID, project_domain_name=SERVICE_DOMAIN_NAME) token.add_role(name=SERVICE_ROLE_NAME1) token.add_role(name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'kerberos': self.SERVICE_KERBEROS_BIND} self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_BIND] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) token['token']['bind'] = {'FOO': 'BAR'} self.TOKEN_RESPONSES[self.v3_UUID_TOKEN_UNKNOWN_BIND] = token token = fixture.V3Token(user_id=SERVICE_USER_ID, user_name=SERVICE_USER_NAME, user_domain_id=SERVICE_DOMAIN_ID, user_domain_name=SERVICE_DOMAIN_NAME, project_id=SERVICE_PROJECT_ID, project_name=SERVICE_PROJECT_NAME, project_domain_id=SERVICE_DOMAIN_ID, project_domain_name=SERVICE_DOMAIN_NAME) token.add_role(id=SERVICE_ROLE_NAME1, name=SERVICE_ROLE_NAME1) token.add_role(id=SERVICE_ROLE_NAME2, name=SERVICE_ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_DEFAULT] = token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, is_admin_project=False) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_NOT_IS_ADMIN_PROJECT] = token # Application credential token token = fixture.V3Token(user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, application_credential_id=APP_CRED_ID) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) svc = token.add_service('compute') svc.add_endpoint('public', 'https://nova.openstack.example.org/v2.1') self.TOKEN_RESPONSES[self.v3_APP_CRED_TOKEN] = token # Application credential with access_rules token access_rules = [{ 'path': '/v2.1/servers', 'method': 'GET', 'service': 'compute' }] token = fixture.V3Token( user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, application_credential_id=APP_CRED_ID, application_credential_access_rules=access_rules) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) svc = token.add_service('compute') svc.add_endpoint('public', 'https://nova.openstack.example.org') svc = token.add_service('image') svc.add_endpoint('public', 'https://glance.openstack.example.org') self.TOKEN_RESPONSES[self.v3_APP_CRED_ACCESS_RULES] = token # Application credential with explicitly empty access_rules access_rules = [] token = fixture.V3Token( user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, application_credential_id=APP_CRED_ID, application_credential_access_rules=access_rules) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) self.TOKEN_RESPONSES[self.v3_APP_CRED_EMPTY_ACCESS_RULES] = token # Application credential with matching rules access_rules = [ { 'path': '/v2.1/servers/{server_id}', 'method': 'GET', 'service': 'compute' }, { 'path': '/v2/images/*', 'method': 'GET', 'service': 'image' }, { 'path': '**', 'method': 'GET', 'service': 'identity' }, { 'path': '/v3/{project_id}/types/{volume_type_id}', 'method': 'GET', 'service': 'block-storage' }, { 'path': '/v1/*/*/*', 'method': 'GET', 'service': 'object-store' } ] token = fixture.V3Token( user_id=USER_ID, user_name=USER_NAME, user_domain_id=DOMAIN_ID, user_domain_name=DOMAIN_NAME, project_id=PROJECT_ID, project_name=PROJECT_NAME, project_domain_id=DOMAIN_ID, project_domain_name=DOMAIN_NAME, application_credential_id=APP_CRED_ID, application_credential_access_rules=access_rules) token.add_role(name=ROLE_NAME1) token.add_role(name=ROLE_NAME2) svc = token.add_service(self.SERVICE_TYPE) svc.add_endpoint('public', self.SERVICE_URL) svc = token.add_service('compute') svc.add_endpoint('public', 'https://nova.openstack.example.org') svc = token.add_service('image') svc.add_endpoint('public', 'https://glance.openstack.example.org') svc = token.add_service('block-storage') svc.add_endpoint('public', 'https://cinder.openstack.example.org') svc = token.add_service('object-store') svc.add_endpoint('public', 'https://swift.openstack.example.org') self.TOKEN_RESPONSES[self.v3_APP_CRED_MATCHING_RULES] = token # oauth2 credential token cert_pem = ssl.DER_cert_to_PEM_cert(self.V3_OAUTH2_MTLS_CERTIFICATE) thumb_sha256 = hashlib.sha256(cert_pem.encode('ascii')).digest() cert_thumb = base64.urlsafe_b64encode(thumb_sha256).decode('ascii') token = fixture.V3Token( methods=['oauth2_credential'], user_id=USER_ID, user_name=USER_NAME, project_id=PROJECT_ID, oauth2_thumbprint=cert_thumb, ) self.TOKEN_RESPONSES[self.v3_OAUTH2_CREDENTIAL] = token self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in self.TOKEN_RESPONSES.items()]) def _create_dn( self, common_name=None, locality_name=None, state_or_province_name=None, organization_name=None, organizational_unit_name=None, country_name=None, street_address=None, domain_component=None, user_id=None, email_address=None, ): oid = x509.NameOID attr = x509.NameAttribute dn = [] if common_name: dn.append(attr(oid.COMMON_NAME, common_name)) if locality_name: dn.append(attr(oid.LOCALITY_NAME, locality_name)) if state_or_province_name: dn.append(attr(oid.STATE_OR_PROVINCE_NAME, state_or_province_name)) if organization_name: dn.append(attr(oid.ORGANIZATION_NAME, organization_name)) if organizational_unit_name: dn.append( attr( oid.ORGANIZATIONAL_UNIT_NAME, organizational_unit_name)) if country_name: dn.append(attr(oid.COUNTRY_NAME, country_name)) if street_address: dn.append(attr(oid.STREET_ADDRESS, street_address)) if domain_component: dn.append(attr(oid.DOMAIN_COMPONENT, domain_component)) if user_id: dn.append(attr(oid.USER_ID, user_id)) if email_address: dn.append(attr(oid.EMAIL_ADDRESS, email_address)) return x509.Name(dn) def _create_certificate(self, subject_dn, ca=None, ca_key=None): private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) issuer = ca.subject if ca else subject_dn if not ca_key: ca_key = private_key today = datetime.datetime.today() cert = x509.CertificateBuilder( issuer_name=issuer, subject_name=subject_dn, public_key=private_key.public_key(), serial_number=x509.random_serial_number(), not_valid_before=today, not_valid_after=today + datetime.timedelta(365, 0, 0), ).sign(ca_key, hashes.SHA256()) return cert, private_key def _create_pem_certificate(self, subject_dn, ca=None, ca_key=None): cert, _ = self._create_certificate(subject_dn, ca=ca, ca_key=ca_key) return cert.public_bytes(Encoding.PEM) EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_access_rules.py0000664000175000017500000000446100000000000027634 0ustar00zuulzuul00000000000000# Copyright 2019 SUSE LLC # # 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 keystonemiddleware.auth_token import _path_matches from keystonemiddleware.tests.unit import utils class TestAccessRules(utils.BaseTestCase): def test_path_matches(self): good_matches = [ ('/v2/servers', '/v2/servers'), ('/v2/servers/123', '/v2/servers/{server_id}'), ('/v2/servers/123/', '/v2/servers/{server_id}/'), ('/v2/servers/123', '/v2/servers/*'), ('/v2/servers/123/', '/v2/servers/*/'), ('/v2/servers/123', '/v2/servers/**'), ('/v2/servers/123/', '/v2/servers/**'), ('/v2/servers/123/456', '/v2/servers/**'), ('/v2/servers', '**'), ('/v2/servers/', '**'), ('/v2/servers/123', '**'), ('/v2/servers/123/456', '**'), ('/v2/servers/123/volume/456', '**'), ('/v2/servers/123/456', '/v2/*/*/*'), ('/v2/123/servers/466', '/v2/{project_id}/servers/{server_id}'), ] for (request, pattern) in good_matches: self.assertIsNotNone(_path_matches(request, pattern)) bad_matches = [ ('/v2/servers/someuuid', '/v2/servers'), ('/v2/servers//', '/v2/servers/{server_id}'), ('/v2/servers/123/', '/v2/servers/{server_id}'), ('/v2/servers/123/456', '/v2/servers/{server_id}'), ('/v2/servers/123/456', '/v2/servers/*'), ('/v2/servers', 'v2/servers'), ('/v2/servers/123/456/789', '/v2/*/*/*'), ('/v2/servers/123/', '/v2/*/*/*'), ('/v2/servers/', '/v2/servers/{server_id}'), ('/v2/servers', '/v2/servers/{server_id}'), ] for (request, pattern) in bad_matches: self.assertIsNone(_path_matches(request, pattern)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_ec2_token_middleware.py0000664000175000017500000001527000000000000031227 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from oslo_serialization import jsonutils import requests import webob from keystonemiddleware import ec2_token from keystonemiddleware.tests.unit import utils TOKEN_ID = 'fake-token-id' EMPTY_RESPONSE = {} class FakeResponse(object): reason = "Test Reason" headers = {'x-subject-token': TOKEN_ID} def __init__(self, json, status_code=400): self._json = json self.status_code = status_code def json(self): return self._json class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __call__(self, env, start_response): resp = webob.Response() resp.environ = env return resp(env, start_response) class EC2TokenMiddlewareTestBase(utils.TestCase): TEST_PROTOCOL = 'https' TEST_HOST = 'fakehost' TEST_PORT = 35357 TEST_URL = '%s://%s:%d/v3/ec2tokens' % (TEST_PROTOCOL, TEST_HOST, TEST_PORT) def setUp(self): super(EC2TokenMiddlewareTestBase, self).setUp() self.middleware = ec2_token.EC2Token(FakeApp(), {}) def _validate_ec2_error(self, response, http_status, ec2_code): self.assertEqual(http_status, response.status_code, 'Expected HTTP status %s' % http_status) error_msg = '%s' % ec2_code error_msg = error_msg.encode() self.assertIn(error_msg, response.body) class EC2TokenMiddlewareTestGood(EC2TokenMiddlewareTestBase): @mock.patch.object( requests, 'post', return_value=FakeResponse(EMPTY_RESPONSE, status_code=200)) def test_protocol_old_versions(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' req.body = b'Action=ListUsers&Version=2010-05-08' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_code) self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token']) mock_request.assert_called_with( 'http://localhost:5000/v3/ec2tokens', data=mock.ANY, headers={'Content-Type': 'application/json'}, verify=True, cert=None, timeout=mock.ANY) data = jsonutils.loads(mock_request.call_args[1]['data']) expected_data = { 'ec2Credentials': { 'access': 'test-key-id', 'headers': {'Host': 'localhost:80', 'Content-Length': '35'}, 'host': 'localhost:80', 'verb': 'GET', 'params': {'AWSAccessKeyId': 'test-key-id'}, 'signature': 'test-signature', 'path': '/test', 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01' 'bf2c23245fa365ef83fe8f1f955085e2'}} self.assertDictEqual(expected_data, data) @mock.patch.object( requests, 'post', return_value=FakeResponse(EMPTY_RESPONSE, status_code=200)) def test_protocol_v4(self, mock_request): req = webob.Request.blank('/test') auth_str = ( 'AWS4-HMAC-SHA256' ' Credential=test-key-id/20110909/us-east-1/iam/aws4_request,' ' SignedHeaders=content-type;host;x-amz-date,' ' Signature=test-signature') req.headers['Authorization'] = auth_str req.body = b'Action=ListUsers&Version=2010-05-08' resp = req.get_response(self.middleware) self.assertEqual(200, resp.status_code) self.assertEqual(TOKEN_ID, req.headers['X-Auth-Token']) mock_request.assert_called_with( 'http://localhost:5000/v3/ec2tokens', data=mock.ANY, headers={'Content-Type': 'application/json'}, verify=True, cert=None, timeout=mock.ANY) data = jsonutils.loads(mock_request.call_args[1]['data']) expected_data = { 'ec2Credentials': { 'access': 'test-key-id', 'headers': {'Host': 'localhost:80', 'Content-Length': '35', 'Authorization': auth_str}, 'host': 'localhost:80', 'verb': 'GET', 'params': {}, 'signature': 'test-signature', 'path': '/test', 'body_hash': 'b6359072c78d70ebee1e81adcbab4f01' 'bf2c23245fa365ef83fe8f1f955085e2'}} self.assertDictEqual(expected_data, data) class EC2TokenMiddlewareTestBad(EC2TokenMiddlewareTestBase): def test_no_signature(self): req = webob.Request.blank('/test') resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') def test_no_key_id(self): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') @mock.patch.object( requests, 'post', return_value=FakeResponse(EMPTY_RESPONSE)) def test_communication_failure(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') mock_request.assert_called_with( 'http://localhost:5000/v3/ec2tokens', data=mock.ANY, headers=mock.ANY, verify=mock.ANY, cert=mock.ANY, timeout=mock.ANY) @mock.patch.object( requests, 'post', return_value=FakeResponse(EMPTY_RESPONSE)) def test_no_result_data(self, mock_request): req = webob.Request.blank('/test') req.GET['Signature'] = 'test-signature' req.GET['AWSAccessKeyId'] = 'test-key-id' resp = req.get_response(self.middleware) self._validate_ec2_error(resp, 400, 'AuthFailure') mock_request.assert_called_with( 'http://localhost:5000/v3/ec2tokens', data=mock.ANY, headers=mock.ANY, verify=mock.ANY, cert=mock.ANY, timeout=mock.ANY) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_entry_points.py0000664000175000017500000000250200000000000027710 0ustar00zuulzuul00000000000000# 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 stevedore from testtools import matchers from keystonemiddleware.tests.unit import utils class TestPasteDeploymentEntryPoints(utils.BaseTestCase): def test_entry_points(self): expected_factory_names = [ 'audit', 'auth_token', 'ec2_token', 's3_token', ] em = stevedore.ExtensionManager('paste.filter_factory') exp_factories = set(['keystonemiddleware.' + name + ':filter_factory' for name in expected_factory_names]) actual_factories = set(['{0.__module__}:{0.__name__}'.format( extension.plugin) for extension in em]) # Ensure that all factories are defined by their names self.assertThat(actual_factories, matchers.ContainsAll(exp_factories)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py0000664000175000017500000027400200000000000033662 0ustar00zuulzuul00000000000000# Copyright 2023 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 base64 import copy import hashlib from http import HTTPStatus import jwt.utils import logging import ssl from testtools import matchers import time from unittest import mock import uuid import webob.dec import fixtures from oslo_config import cfg import testresources from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import session from keystonemiddleware.auth_token import _cache from keystonemiddleware import external_oauth2_token from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FakeApp from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import v3FakeApp from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import VERSION_LIST_v3 from keystonemiddleware.tests.unit import client_fixtures from keystonemiddleware.tests.unit import utils JWT_KEY_CONTENT = ( '-----BEGIN PRIVATE KEY-----\n' 'MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDegNuQgmQL7n10\n' '+Z3itXtpiNHlvZwCYOS66+3PakAw1OoRB6SiHeNYnuVRHlraTDKnnfgHhX/1AVs7\n' 'P36QU5PVYznGip2PXZlCh8MeQhpXgKKt25LPnpQOnUssHyq+OqTHZB6eS2C7xMHf\n' 'wzPrYRwxhbVgUUVe85cdiXaL5ZRqXNotM00wH1hck4s+1fsnKv7UeGbwM1WwMn6/\n' '0E1eKwYzlKm4Vmkcivy8WBI7Ijp/MPOUyRXN/mPh8L2VOq0D1E3pufYoYmpBkiQi\n' 'Ii8nz5CXrhDpM0tGKD+RZ+howE2i+frI2gNDfU5xMx+k+qjD0jftDrQ+OZUujUtq\n' '6JfdrvtPBT01XZw8GV5Rm9vEwMRduWUDGdRB3chOTeTUdsIG765+Ot7GE7nYrAs0\n' 's/ryAm1FnNJocTzje7k07IzdBpWzrTrx087Kfcsn6evEABOxim0i+AHUR94QR9/V\n' 'EP3/+SkJ7zl9P1KzOZZCWtUTnfQxrLhEnCwwjtl35vWlzst+TR7HDwIzQRQVLFH9\n' 'zMTz8tw6coPifkbVzdwCLGoKge4llDPcVx/TmIGFD3saT0E68yxXe6k3cdIg6lZf\n' 'dB0yutVBzECrx+LiIpxwQWRKHNiR58KsHHmgXDb8ORBCjpmctD+JsdBhf8hDRMXP\n' '9sV/fbMUwgrRceyj9AV2x59tE9+UHwIDAQABAoICABb6V7JkxNA2oN4jqRpwg34y\n' 'kvqWyjW0q+ph0v1Ii7h/RGzdzTKww3mzbxshd2Bz3gdRWPvt3Xj/2twTgo6FEw9G\n' 'YAEQ75SOpfUo8A1/5hiDQEmUE2U9iyy3Mbwsu81JYRr2S/Ms9aBugVcKYaI9NRwo\n' 'IsL/oZpcrY5vU76+xsT1MdLZKW9+zTFCS28Byh4RYp+uj3Le2kqH7G8Co/rFlq5c\n' '++n9gn1gHRmWPsu8jS31cDI9UfMkAkyi//EZTiTHGAS7H6CsCS0cWn7r6NLDrLr9\n' 'TuHGWk+0eFwbzvSCZ4IdLrjvSsb9ecxW6z2uZR9T5lKk4hhK+g0EqnUv7/8Eww8E\n' 'wA2J1zhuQ0UzoAowjj5338whBQROKSO4u3ppxhNUSP7fUgYdEKUQEg7rlfEzI+pG\n' 'dV1LtG0GZBzdZXpE/PTpASjefCkC6olmZpUvajHJGqP0a/ygA9SEBm+B/Q4ii7+0\n' 'luk6Lj6z+vSWatU7LrLnQeprN82NWxtkH+u2gjMOq1N8r4FOFvbZYBp1NMvtH4iP\n' 'R6jLdJWYx/KOr4lCkbgTszlVhPop8dktOPQSPL4u6RxdmsGBf028oWKXLrj1D1Ua\n' 'dBWR1L1CCnI8X6jxL6eT52qF+NY2JxanX6NnzxE/KqedWXmKDxn0M3ETfizz9UG4\n' '8UmsMgJ8UUALRbWHjlEBAoIBAQDvQmYWhYtUQjcaeLkhjd9qPXjLGIL6NYnrYEUK\n' 'Yenn0mh7SKZTTL8yz/QVOecD/QiBetmQJ5FsmUmqorGWYfbWs6C+p2yHQ0U9X7rE\n' '3ynFm0MDluuNZMWYRu0Yb6gvCYjitlt/efGKDalP1Al1hX2w9fUGdj32K6fulEX6\n' 'dcl4r2bq4i+rOwe9YDD9yvkvh1+aCwA56JCTBoEBsbmOdKTC7431rT8BTLbBaXwy\n' 'hf35P9wzU079QwwqDKdUlMQjUz9gWZkYFHkPfce2MCm+T0aHNnjQtLXRGOcIj15P\n' 'B64+GB9b86XNZlqpuY2rceF+LDwaw4rgQkXDr+TdAsjrtcdHAoIBAQDuElNxRc9t\n' 'hKwZGBVIWaHI3Y3oaTymi277DwhDzvmJgwBAddfEaC1rCt/giXxtmhhnAXpDD4sk\n' '3m8iWw1jODRxOv2UDkUvSRV5tfY+QTG0nVVmMpX3lPWpIYxEVg34WYSq0xnXKrpW\n' 'zxUOqD1fW2i2lXZtFAb6ZNt/hHts7KUPzk9/ZbAomVHO6JO4Ac3n0LTDSCmQHhRO\n' '5gV0ea4Sh6AVOiFD20rMAnTFNnxnI+wLMt0SNAzouhRMulDqOcAmoH2DKG8PCcEt\n' 'dQpUDwITxXuomsjhIHIli760MwSlwWZbrh5h7NAj1VmnQBtMkLnBtnE7cFSVdcPt\n' 'BAFnq72txGhpAoIBAQDIWYKhM1zTxsrbyOHF3kaKcUVYVINBQFnolunZYtp6vG+v\n' 'ZMuaj3a/9vE+YQk5Bsb7ncLXerrFBKtyTuCEvC315d8iJ5KyxbsSRLpiJzmUdoos\n' 'VFGVSiBIfoQF5WIhWUueBPQjkBqZ7wyrgzQUjB8PczamHZePL0lleBYNQFrgS4jU\n' 'AWnHahv2EbmUnEYD7ck5diLPWxbNdzHKGGf4iWZ6shze8B8FWJbk6Q8OQ7PD5xze\n' 'gdFwNJfYElaAdj60Ef7NENopFuO0/C+jOTuLWFkH2q5anihuGvtD6MIhTZ4z8wE3\n' 'f5SEpkQfQfkG6srXW/VMuBfv6K8AyabNB4r2Dnb7AoIBADHy2lrroKeDrG/fY6e4\n' 'Vn9ELJ/UZIs0ueYmsz82z5gQSh88Gjb0/IJ2153OerKsH+6MmtAzFKh5mquEmvx0\n' 'MFyJWeaUT+Op272bdbx+BSW11NMKTfiR4jDH/xvfSjMO5QzKGaPRLSNFc0+N8MJu\n' '9TtJhH1CNGyYeIz6iMLDq6XzTS6XcSwzbryQg12Z00+NtD88hqvcA7rB++cCGIl+\n' 'txF9Drmj6r9+zG0MD3G8UavP0h4dmY/CarvmY0+hKjVweqTn+NUY4NTet3oHZBIt\n' '3tHzF65UFl7WQP6hrZnxR754e5tkCg9aleLHSnL38mE4G+2ylax99stlib3shHFO\n' 'wfECggEBAJrW8BmZXbD8ss3c7kHPzleAf1q/6bPnxRXB0luCPz7tkMfdkOQ2cG1t\n' 'rcnsKcyR2woEbtdRK938KxZgTgzKYVhR8spKFSh01/d9OZAP6f+iCoR2zzOlSFo4\n' 'pejnQY0LHEwGZmnzghLoqJSUgROAR49CvLO1mI48CaEUuLmqzPYWNXMHDDU2N5XO\n' 'uF0/ph68fnI+f+0ZUgdpVPFRnfSrAqzEhzEMh1vnZ4ZxEVpgUcn/hRfNZ3hN0LEr\n' 'fjm2bWxg2j0rxjS0mUDQpaMj0253jVYRiC3M3cCh0NSZtwaXVJYCVxetpjBTPfJr\n' 'jIgmPTKGR0FedjAeCBByH9vkw8iRg7w=\n' '-----END PRIVATE KEY-----\n') MEMCACHED_SERVERS = ['localhost:11211'] def get_authorization_header(token): return {'Authorization': f'Bearer {token}'} def get_config( introspect_endpoint=None, audience=None, auth_method=None, client_id=None, client_secret=None, thumbprint_verify=None, jwt_key_file=None, jwt_algorithm=None, jwt_bearer_time_out=None, mapping_project_id=None, mapping_project_name=None, mapping_project_domain_id=None, mapping_project_domain_name=None, mapping_user_id=None, mapping_user_name=None, mapping_user_domain_id=None, mapping_user_domain_name=None, mapping_roles=None, mapping_system_scope=None, mapping_expires_at=None, memcached_servers=None, memcache_use_advanced_pool=None, memcache_pool_dead_retry=None, memcache_pool_maxsize=None, memcache_pool_unused_timeout=None, memcache_pool_conn_get_timeout=None, memcache_pool_socket_timeout=None, memcache_security_strategy=None, memcache_secret_key=None): conf = {} if introspect_endpoint is not None: conf['introspect_endpoint'] = introspect_endpoint if audience is not None: conf['audience'] = audience if auth_method is not None: conf['auth_method'] = auth_method if client_id is not None: conf['client_id'] = client_id if client_secret is not None: conf['client_secret'] = client_secret if jwt_key_file is not None: conf['jwt_key_file'] = jwt_key_file if jwt_algorithm is not None: conf['jwt_algorithm'] = jwt_algorithm if jwt_bearer_time_out is not None: conf['jwt_bearer_time_out'] = jwt_bearer_time_out if thumbprint_verify is not None: conf['thumbprint_verify'] = thumbprint_verify if mapping_project_id is not None: conf['mapping_project_id'] = mapping_project_id if mapping_project_name is not None: conf['mapping_project_name'] = mapping_project_name if mapping_project_id is not None: conf['mapping_project_domain_id'] = mapping_project_domain_id if mapping_project_domain_name is not None: conf['mapping_project_domain_name'] = mapping_project_domain_name if mapping_user_id is not None: conf['mapping_user_id'] = mapping_user_id if mapping_project_id is not None: conf['mapping_project_id'] = mapping_project_id if mapping_user_name is not None: conf['mapping_user_name'] = mapping_user_name if mapping_user_domain_id is not None: conf['mapping_user_domain_id'] = mapping_user_domain_id if mapping_project_id is not None: conf['mapping_user_domain_name'] = mapping_user_domain_name if mapping_roles is not None: conf['mapping_roles'] = mapping_roles if mapping_system_scope is not None: conf['mapping_system_scope'] = mapping_system_scope if memcached_servers is not None: conf['memcached_servers'] = memcached_servers if memcached_servers is not None: conf['mapping_expires_at'] = mapping_expires_at if memcache_use_advanced_pool is not None: conf['memcache_use_advanced_pool'] = memcache_use_advanced_pool if memcache_pool_dead_retry is not None: conf['memcache_pool_dead_retry'] = memcache_pool_dead_retry if memcache_pool_maxsize is not None: conf['memcache_pool_maxsize'] = memcache_pool_maxsize if memcache_pool_unused_timeout is not None: conf['memcache_pool_unused_timeout'] = memcache_pool_unused_timeout if memcache_pool_conn_get_timeout is not None: conf['memcache_pool_conn_get_timeout'] = memcache_pool_conn_get_timeout if memcache_pool_socket_timeout is not None: conf['memcache_pool_socket_timeout'] = memcache_pool_socket_timeout if memcache_security_strategy is not None: conf['memcache_security_strategy'] = memcache_security_strategy if memcache_secret_key is not None: conf['memcache_secret_key'] = memcache_secret_key return conf class FakeOauth2TokenV3App(v3FakeApp): def __init__(self, expected_env=None, need_service_token=False, app_response_status_code=200): super(FakeOauth2TokenV3App, self).__init__(expected_env, need_service_token) self._status_code = app_response_status_code @webob.dec.wsgify def __call__(self, req): resp = webob.Response() if self._status_code == 200: resp.status_code = 200 resp.body = FakeApp.SUCCESS else: resp.status_code = self._status_code resp.body = b'Error' return resp class FakeSocket(object): def __init__(self, binary_peer_cert): self.binary_peer_cert = binary_peer_cert def getpeercert(self, binary_form=True): if binary_form: return self.binary_peer_cert else: return None class FakeWsgiInput(object): def __init__(self, fake_socket): self.fake_socket = fake_socket def get_socket(self): return self.fake_socket class BaseExternalOauth2TokenMiddlewareTest(base.BaseAuthTokenTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): cfg.CONF.clear() super(BaseExternalOauth2TokenMiddlewareTest, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) self.expected_env = dict() self.fake_app = FakeOauth2TokenV3App self.middleware = None self.conf = {} self.auth_version = 'v3.0' self._auth_url = 'http://localhost/identity' self._introspect_endpoint = ( 'https://localhost:8443/realms/x509/' 'protocol/openid-connect/token/introspect') self._audience = 'https://localhost:8443/realms/x509' def set_middleware(self, expected_env=None, conf=None): """Configure the class ready to call the oauth2_token middleware. Set up the various fake items needed to run the middleware. Individual tests that need to further refine these can call this function to override the class defaults. """ if conf: self.conf.update(conf) if expected_env: self.expected_env.update(expected_env) self.middleware = external_oauth2_token.ExternalAuth2Protocol( self.fake_app(expected_env=self.expected_env), self.conf) def call(self, middleware, method='GET', path='/', headers=None, expected_status=HTTPStatus.OK, expected_body_string=None, **kwargs): req = webob.Request.blank(path, **kwargs) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) self.assertEqual(expected_status, resp.status_int) if expected_body_string: self.assertIn(expected_body_string, str(resp.body)) resp.request = req return resp def call_middleware(self, pem_client_cert=None, der_client_cert=None, **kwargs): if pem_client_cert: # apache kwargs.update({'environ': {'SSL_CLIENT_CERT': pem_client_cert}}) elif der_client_cert: # socket fake_socket = FakeSocket(der_client_cert) fake_wsgi_input = FakeWsgiInput(fake_socket) kwargs.update({'environ': {'wsgi.input': fake_wsgi_input}}) return self.call(self.middleware, **kwargs) def _introspect_response(self, request, context, auth_method=None, introspect_client_id=None, introspect_client_secret=None, access_token=None, active=True, exp_time=None, cert_thumb=None, metadata=None, status_code=200, system_scope=False ): if auth_method == 'tls_client_auth': body = 'client_id=%s&token=%s&token_type_hint=access_token' % ( introspect_client_id, access_token ) self.assertEqual(request.text, body) elif auth_method == 'client_secret_post': body = ('client_id=%s&client_secret=%s' '&token=%s&token_type_hint=access_token') % ( introspect_client_id, introspect_client_secret, access_token) self.assertEqual(request.text, body) elif auth_method == 'client_secret_basic': body = 'token=%s&token_type_hint=access_token' % access_token self.assertEqual(request.text, body) auth_basic = request._request.headers.get('Authorization') self.assertIsNotNone(auth_basic) auth = 'Basic ' + base64.standard_b64encode( ("%s:%s" % (introspect_client_id, introspect_client_secret)).encode('ascii') ).decode('ascii') self.assertEqual(auth_basic, auth) elif auth_method == 'private_key_jwt': self.assertIn('client_id=%s' % introspect_client_id, request.text) self.assertIn(('client_assertion_type=urn%3Aietf%3Aparams%3A' 'oauth%3Aclient-assertion-type%3Ajwt-bearer'), request.text) self.assertIn('client_assertion=', request.text) self.assertIn('token=%s' % access_token, request.text) self.assertIn('token_type_hint=access_token', request.text) elif auth_method == 'client_secret_jwt': self.assertIn('client_id=%s' % introspect_client_id, request.text) self.assertIn(('client_assertion_type=urn%3Aietf%3Aparams%3A' 'oauth%3Aclient-assertion-type%3Ajwt-bearer'), request.text) self.assertIn('client_assertion=', request.text) self.assertIn('token=%s' % access_token, request.text) self.assertIn('token_type_hint=access_token', request.text) resp = { 'iat': 1670311634, 'jti': str(uuid.uuid4()), 'iss': str(uuid.uuid4()), 'aud': str(uuid.uuid4()), 'sub': str(uuid.uuid4()), 'typ': 'Bearer', 'azp': str(uuid.uuid4()), 'acr': '1', 'scope': 'default' } if system_scope: resp['system_scope'] = 'all' if exp_time is not None: resp['exp'] = exp_time else: resp['exp'] = time.time() + 3600 if cert_thumb is not None: resp['cnf'] = { 'x5t#S256': cert_thumb } if metadata: for key in metadata: resp[key] = metadata[key] if active is not None: resp['active'] = active context.status_code = status_code return resp def _check_env_value_project_scope(self, request_environ, user_id, user_name, user_domain_id, user_domain_name, project_id, project_name, project_domain_id, project_domain_name, roles, is_admin=True): self.assertEqual('Confirmed', request_environ['HTTP_X_IDENTITY_STATUS']) self.assertEqual(roles, request_environ['HTTP_X_ROLES']) self.assertEqual(roles, request_environ['HTTP_X_ROLE']) self.assertEqual(user_id, request_environ['HTTP_X_USER_ID']) self.assertEqual(user_name, request_environ['HTTP_X_USER_NAME']) self.assertEqual(user_domain_id, request_environ['HTTP_X_USER_DOMAIN_ID'], ) self.assertEqual(user_domain_name, request_environ['HTTP_X_USER_DOMAIN_NAME']) if is_admin: self.assertEqual('true', request_environ['HTTP_X_IS_ADMIN_PROJECT']) else: self.assertNotIn('HTTP_X_IS_ADMIN_PROJECT', request_environ) self.assertEqual(user_name, request_environ['HTTP_X_USER']) self.assertEqual(project_id, request_environ['HTTP_X_PROJECT_ID']) self.assertEqual(project_name, request_environ['HTTP_X_PROJECT_NAME']) self.assertEqual(project_domain_id, request_environ['HTTP_X_PROJECT_DOMAIN_ID']) self.assertEqual(project_domain_name, request_environ['HTTP_X_PROJECT_DOMAIN_NAME']) self.assertEqual(project_id, request_environ['HTTP_X_TENANT_ID']) self.assertEqual(project_name, request_environ['HTTP_X_TENANT_NAME']) self.assertEqual(project_id, request_environ['HTTP_X_TENANT']) self.assertNotIn('HTTP_OPENSTACK_SYSTEM_SCOPE', request_environ) self.assertNotIn('HTTP_X_DOMAIN_ID', request_environ) self.assertNotIn('HTTP_X_DOMAIN_NAME', request_environ) def _check_env_value_domain_scope(self, request_environ, user_id, user_name, user_domain_id, user_domain_name, domain_id, domain_name, roles, is_admin=True): self.assertEqual('Confirmed', request_environ['HTTP_X_IDENTITY_STATUS']) self.assertEqual(roles, request_environ['HTTP_X_ROLES']) self.assertEqual(roles, request_environ['HTTP_X_ROLE']) self.assertEqual(user_id, request_environ['HTTP_X_USER_ID']) self.assertEqual(user_name, request_environ['HTTP_X_USER_NAME']) self.assertEqual(user_domain_id, request_environ['HTTP_X_USER_DOMAIN_ID'], ) self.assertEqual(user_domain_name, request_environ['HTTP_X_USER_DOMAIN_NAME']) if is_admin: self.assertEqual('true', request_environ['HTTP_X_IS_ADMIN_PROJECT']) else: self.assertNotIn('HTTP_X_IS_ADMIN_PROJECT', request_environ) self.assertEqual(user_name, request_environ['HTTP_X_USER']) self.assertEqual(domain_id, request_environ['HTTP_X_DOMAIN_ID']) self.assertEqual(domain_name, request_environ['HTTP_X_DOMAIN_NAME']) self.assertNotIn('HTTP_OPENSTACK_SYSTEM_SCOPE', request_environ) self.assertNotIn('HTTP_X_PROJECT_ID', request_environ) self.assertNotIn('HTTP_X_PROJECT_NAME', request_environ) self.assertNotIn('HTTP_X_PROJECT_DOMAIN_ID', request_environ) self.assertNotIn('HTTP_X_PROJECT_DOMAIN_NAME', request_environ) self.assertNotIn('HTTP_X_TENANT_ID', request_environ) self.assertNotIn('HTTP_X_TENANT_NAME', request_environ) self.assertNotIn('HTTP_X_TENANT', request_environ) def _check_env_value_system_scope(self, request_environ, user_id, user_name, user_domain_id, user_domain_name, roles, is_admin=True, system_scope=True): self.assertEqual('Confirmed', request_environ['HTTP_X_IDENTITY_STATUS']) self.assertEqual(roles, request_environ['HTTP_X_ROLES']) self.assertEqual(roles, request_environ['HTTP_X_ROLE']) self.assertEqual(user_id, request_environ['HTTP_X_USER_ID']) self.assertEqual(user_name, request_environ['HTTP_X_USER_NAME']) self.assertEqual(user_domain_id, request_environ['HTTP_X_USER_DOMAIN_ID'], ) self.assertEqual(user_domain_name, request_environ['HTTP_X_USER_DOMAIN_NAME']) if is_admin: self.assertEqual('true', request_environ['HTTP_X_IS_ADMIN_PROJECT']) else: self.assertNotIn('HTTP_X_IS_ADMIN_PROJECT', request_environ) self.assertEqual(user_name, request_environ['HTTP_X_USER']) self.assertEqual('all', request_environ['HTTP_OPENSTACK_SYSTEM_SCOPE']) self.assertNotIn('HTTP_X_DOMAIN_ID', request_environ) self.assertNotIn('HTTP_X_DOMAIN_NAME', request_environ) self.assertNotIn('HTTP_X_PROJECT_ID', request_environ) self.assertNotIn('HTTP_X_PROJECT_NAME', request_environ) self.assertNotIn('HTTP_X_PROJECT_DOMAIN_ID', request_environ) self.assertNotIn('HTTP_X_PROJECT_DOMAIN_NAME', request_environ) self.assertNotIn('HTTP_X_TENANT_ID', request_environ) self.assertNotIn('HTTP_X_TENANT_NAME', request_environ) self.assertNotIn('HTTP_X_TENANT', request_environ) class ExternalOauth2TokenMiddlewareTlsClientAuthTest( BaseExternalOauth2TokenMiddlewareTest): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(ExternalOauth2TokenMiddlewareTlsClientAuthTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._auth_method = 'tls_client_auth' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, thumbprint_verify=True, mapping_project_id='access_project.id', mapping_project_name='access_project.name', mapping_project_domain_id='access_project.domain.id', mapping_project_domain_name='access_project.domain.name', mapping_user_id='client_id', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', ) self._token = str(uuid.uuid4()) + '_user_token' self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'access_project': { 'id': self._project_id, 'name': self._project_name, 'domain': { 'id': self._project_domain_id, 'name': self._project_domain_name } }, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'client_id': self._user_id, 'username': self._user_name, } cert = self.examples.V3_OAUTH2_MTLS_CERTIFICATE self._pem_client_cert = cert.decode('ascii') self._der_client_cert = ssl.PEM_cert_to_DER_cert(self._pem_client_cert) thumb_sha256 = hashlib.sha256(self._der_client_cert).digest() self._cert_thumb = jwt.utils.base64url_encode(thumb_sha256).decode( 'ascii') def test_basic_200(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertEqual(resp.request.environ['HTTP_X_IDENTITY_STATUS'], 'Confirmed') self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) def test_thumbprint_verify_is_false_200(self): conf = copy.deepcopy(self._test_conf) conf['thumbprint_verify'] = False self.set_middleware(conf=conf) metadata = copy.deepcopy(self._default_metadata) metadata['access_project'].pop('id') roles = 'reader' metadata['roles'] = roles def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb='this is an incorrectly thumbprint.', metadata=metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertEqual(resp.request.environ['HTTP_X_IDENTITY_STATUS'], 'Confirmed') self._check_env_value_domain_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_domain_id, self._project_domain_name, roles, is_admin=False) def test_confirm_certificate_thumbprint_get_socket_except_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': {'test': 'test'}} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_socket_is_none_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(None)} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_peercert_is_none_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_peercert_error_format_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket('Error Format'))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_wsgi_input_is_none_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': None} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_is_not_match_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb='NotMatchThumbprint', metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_confirm_certificate_thumbprint_apache_default_200(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', pem_client_cert=self._pem_client_cert ) self.assertEqual(FakeApp.SUCCESS, resp.body) self.assertEqual(resp.request.environ['HTTP_X_IDENTITY_STATUS'], 'Confirmed') self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) def test_confirm_certificate_thumbprint_pem_der_none_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, access_token=self._token, active=True, cert_thumb=self._cert_thumb, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', pem_client_cert=None, der_client_cert=None ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) class ExternalOauth2TokenMiddlewarePrivateJWTKeyTest( BaseExternalOauth2TokenMiddlewareTest): def setUp(self): super(ExternalOauth2TokenMiddlewarePrivateJWTKeyTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._test_client_secret = str(uuid.uuid4()) self._jwt_key_file = '/root/key.pem' self._auth_method = 'private_key_jwt' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, client_secret=self._test_client_secret, jwt_key_file=self._jwt_key_file, jwt_algorithm='RS256', jwt_bearer_time_out='2800', mapping_project_id='access_project.id', mapping_project_name='access_project.name', mapping_project_domain_id='access_project.domain.id', mapping_project_domain_name='access_project.domain.name', mapping_user_id='client_id', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', ) self._token = str(uuid.uuid4()) + '_user_token' self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'access_project': { 'id': self._project_id, 'name': self._project_name, 'domain': { 'id': self._project_domain_id, 'name': self._project_domain_name } }, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'client_id': self._user_id, 'username': self._user_name, } @mock.patch('os.path.isfile') @mock.patch('builtins.open', mock.mock_open(read_data=JWT_KEY_CONTENT)) def test_basic_200(self, mocker_path_isfile): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) def mocker_isfile_side_effect(filename): if filename == self._jwt_key_file: return True else: return False mocker_path_isfile.side_effect = mocker_isfile_side_effect resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertTrue(mocker_path_isfile.called) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) @mock.patch('os.path.isfile') @mock.patch('builtins.open', mock.mock_open(read_data=JWT_KEY_CONTENT)) def test_introspect_by_private_key_jwt_error_alg_500( self, mocker_path_isfile): conf = copy.deepcopy(self._test_conf) conf['jwt_algorithm'] = 'HS256' self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) def mocker_isfile_side_effect(filename): if filename == self._jwt_key_file: return True else: return False mocker_path_isfile.side_effect = mocker_isfile_side_effect self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) @mock.patch('os.path.isfile') @mock.patch('builtins.open', mock.mock_open(read_data='')) def test_introspect_by_private_key_jwt_error_file_no_content_500( self, mocker_path_isfile): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) def mocker_isfile_side_effect(filename): if filename == self._jwt_key_file: return True else: return False mocker_path_isfile.side_effect = mocker_isfile_side_effect self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) @mock.patch('os.path.isfile') def test_introspect_by_private_key_jwt_error_file_can_not_read_500( self, mocker_path_isfile): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) def mocker_isfile_side_effect(filename): if filename == self._jwt_key_file: return True else: return False mocker_path_isfile.side_effect = mocker_isfile_side_effect self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_introspect_by_private_key_jwt_error_file_not_exist_500( self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) class ExternalOauth2TokenMiddlewareClientSecretJWTTest( BaseExternalOauth2TokenMiddlewareTest): def setUp(self): super(ExternalOauth2TokenMiddlewareClientSecretJWTTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._test_client_secret = str(uuid.uuid4()) self._auth_method = 'client_secret_jwt' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, client_secret=self._test_client_secret, jwt_key_file='test', jwt_algorithm='HS256', jwt_bearer_time_out='2800', mapping_project_id='access_project.id', mapping_project_name='access_project.name', mapping_project_domain_id='access_project.domain.id', mapping_project_domain_name='access_project.domain.name', mapping_user_id='client_id', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', ) self._token = str(uuid.uuid4()) + '_user_token' self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'access_project': { 'id': self._project_id, 'name': self._project_name, 'domain': { 'id': self._project_domain_id, 'name': self._project_domain_name } }, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'client_id': self._user_id, 'username': self._user_name, } def test_basic_200(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) def test_introspect_by_client_secret_jwt_error_alg_500(self): conf = copy.deepcopy(self._test_conf) conf['jwt_algorithm'] = 'RS256' self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_fetch_token_introspect_response_201_500(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata, status_code=201 ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_fetch_token_introspect_response_active_is_false_401(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=False, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp, status_code=500) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) def test_fetch_token_introspect_response_500(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata, status_code=500 ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) @mock.patch.object(session.Session, 'request') def test_fetch_token_introspect_timeout_500(self, mock_session_request): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) mock_session_request.side_effect = ksa_exceptions.RequestTimeout( 'time out') self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) class ExternalOauth2TokenMiddlewareClientSecretPostTest( BaseExternalOauth2TokenMiddlewareTest): def setUp(self): super(ExternalOauth2TokenMiddlewareClientSecretPostTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._test_client_secret = str(uuid.uuid4()) self._auth_method = 'client_secret_post' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, client_secret=self._test_client_secret, thumbprint_verify=False, mapping_project_id='project_id', mapping_project_name='project_name', mapping_project_domain_id='domain_id', mapping_project_domain_name='domain_name', mapping_user_id='user', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', ) self._token = str(uuid.uuid4()) + '_user_token' self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'project_id': self._project_id, 'project_name': self._project_name, 'domain_id': self._project_domain_id, 'domain_name': self._project_domain_name, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'user': self._user_id, 'username': self._user_name, } def test_basic_200(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) def test_process_request_no_access_token_in_header_401(self): conf = copy.deepcopy(self._test_conf) test_audience = 'https://test_audience' conf['audience'] = test_audience self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers={}, expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % test_audience) def test_read_data_from_token_key_type_not_dict_403(self): conf = copy.deepcopy(self._test_conf) conf['mapping_user_id'] = 'user.id' self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=403, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_read_data_from_token_key_not_fount_in_metadata_403(self): conf = copy.deepcopy(self._test_conf) conf['mapping_user_id'] = 'user_id' self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=403, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_read_data_from_token_key_value_type_is_not_match_403(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) metadata = copy.deepcopy(self._default_metadata) metadata['user'] = { 'id': str(uuid.uuid4()), 'name': 'testName' } def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=403, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_read_data_from_token_key_config_error_is_not_dict_500(self): conf = copy.deepcopy(self._test_conf) conf['mapping_project_id'] = '..project_id' self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_read_data_from_token_key_config_error_is_not_set_500(self): conf = copy.deepcopy(self._test_conf) conf.pop('mapping_roles') self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) class ExternalOauth2TokenMiddlewareClientSecretBasicTest( BaseExternalOauth2TokenMiddlewareTest): def setUp(self): super(ExternalOauth2TokenMiddlewareClientSecretBasicTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._test_client_secret = str(uuid.uuid4()) self._auth_method = 'client_secret_basic' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, client_secret=self._test_client_secret, thumbprint_verify=False, mapping_project_id='access_project.id', mapping_project_name='access_project.name', mapping_project_domain_id='access_project.domain.id', mapping_project_domain_name='access_project.domain.name', mapping_user_id='client_id', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', ) self._token = str(uuid.uuid4()) + '_user_token' self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'access_project': { 'id': self._project_id, 'name': self._project_name, 'domain': { 'id': self._project_domain_id, 'name': self._project_domain_name } }, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'client_id': self._user_id, 'username': self._user_name, } self._clear_call_count = 0 def test_basic_200(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_project_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_id, self._project_name, self._project_domain_id, self._project_domain_name, self._roles) def test_domain_scope_200(self): conf = copy.deepcopy(self._test_conf) conf.pop('mapping_project_id') self.set_middleware(conf=conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_domain_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._project_domain_id, self._project_domain_name, self._roles) def test_system_scope_200(self): conf = copy.deepcopy(self._test_conf) conf.pop('mapping_project_id') conf['mapping_system_scope'] = "system.all" self.set_middleware(conf=conf) self._default_metadata["system"] = {"all": True} def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata, system_scope=True ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(FakeApp.SUCCESS, resp.body) self._check_env_value_system_scope( resp.request.environ, self._user_id, self._user_name, self._user_domain_id, self._user_domain_name, self._roles) def test_process_response_401(self): conf = copy.deepcopy(self._test_conf) conf.pop('mapping_project_id') self.set_middleware(conf=conf) self.middleware = external_oauth2_token.ExternalAuth2Protocol( FakeOauth2TokenV3App(expected_env=self.expected_env, app_response_status_code=401), self.conf) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) resp = self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertEqual(resp.headers.get('WWW-Authenticate'), 'Authorization OAuth 2.0 uri="%s"' % self._audience) class ExternalAuth2ProtocolTest(BaseExternalOauth2TokenMiddlewareTest): def setUp(self): super(ExternalAuth2ProtocolTest, self).setUp() self._test_client_id = str(uuid.uuid4()) self._test_client_secret = str(uuid.uuid4()) self._auth_method = 'client_secret_basic' self._test_conf = get_config( introspect_endpoint=self._introspect_endpoint, audience=self._audience, auth_method=self._auth_method, client_id=self._test_client_id, client_secret=self._test_client_secret, thumbprint_verify=False, mapping_project_id='access_project.id', mapping_project_name='access_project.name', mapping_project_domain_id='access_project.domain.id', mapping_project_domain_name='access_project.domain.name', mapping_user_id='client_id', mapping_user_name='username', mapping_user_domain_id='user_domain.id', mapping_user_domain_name='user_domain.name', mapping_roles='roles', mapping_system_scope='system.all', mapping_expires_at='exp', memcached_servers=','.join(MEMCACHED_SERVERS), memcache_use_advanced_pool=True, memcache_pool_dead_retry=300, memcache_pool_maxsize=10, memcache_pool_unused_timeout=60, memcache_pool_conn_get_timeout=10, memcache_pool_socket_timeout=3, memcache_security_strategy=None, memcache_secret_key=None ) uuid_token_default = self.examples.v3_UUID_TOKEN_DEFAULT uuid_serv_token_default = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT uuid_token_bind = self.examples.v3_UUID_TOKEN_BIND uuid_service_token_bind = self.examples.v3_UUID_SERVICE_TOKEN_BIND self.token_dict = { 'uuid_token_default': uuid_token_default, 'uuid_service_token_default': uuid_serv_token_default, 'uuid_token_bind': uuid_token_bind, 'uuid_service_token_bind': uuid_service_token_bind, } self._token = self.token_dict['uuid_token_default'] self._user_id = str(uuid.uuid4()) + '_user_id' self._user_name = str(uuid.uuid4()) + '_user_name' self._user_domain_id = str(uuid.uuid4()) + '_user_domain_id' self._user_domain_name = str(uuid.uuid4()) + '_user_domain_name' self._project_id = str(uuid.uuid4()) + '_project_id' self._project_name = str(uuid.uuid4()) + '_project_name' self._project_domain_id = str(uuid.uuid4()) + 'project_domain_id' self._project_domain_name = str(uuid.uuid4()) + 'project_domain_name' self._roles = 'admin,member,reader' self._default_metadata = { 'access_project': { 'id': self._project_id, 'name': self._project_name, 'domain': { 'id': self._project_domain_id, 'name': self._project_domain_name } }, 'user_domain': { 'id': self._user_domain_id, 'name': self._user_domain_name }, 'roles': self._roles, 'client_id': self._user_id, 'username': self._user_name, 'exp': int(time.time()) + 3600 } self._clear_call_count = 0 cert = self.examples.V3_OAUTH2_MTLS_CERTIFICATE self._pem_client_cert = cert.decode('ascii') self._der_client_cert = ssl.PEM_cert_to_DER_cert(self._pem_client_cert) thumb_sha256 = hashlib.sha256(self._der_client_cert).digest() self._cert_thumb = jwt.utils.base64url_encode(thumb_sha256).decode( 'ascii') def test_token_cache_factory_insecure(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.assertIsInstance(self.middleware._token_cache, _cache.TokenCache) def test_token_cache_factory_secure(self): conf = copy.deepcopy(self._test_conf) conf["memcache_secret_key"] = "test_key" conf["memcache_security_strategy"] = "MAC" self.set_middleware(conf=conf) self.assertIsInstance(self.middleware._token_cache, _cache.SecureTokenCache) conf["memcache_security_strategy"] = "ENCRYPT" self.set_middleware(conf=conf) self.assertIsInstance(self.middleware._token_cache, _cache.SecureTokenCache) def test_caching_token_on_verify(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertThat(1, matchers.Equals(cache.set.call_count)) self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) # Assert that the token wasn't cached again. self.assertThat(1, matchers.Equals(cache.set.call_count)) def test_caching_token_timeout(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) self._default_metadata['exp'] = int(time.time()) - 3600 orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertThat(1, matchers.Equals(cache.set.call_count)) # Confirm that authentication fails due to timeout. self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) @mock.patch('keystonemiddleware.auth_token._cache.TokenCache.get') def test_caching_token_type_invalid(self, mock_cache_get): mock_cache_get.return_value = "test" conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) def test_caching_token_not_active(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=False, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertThat(1, matchers.Equals(cache.set.call_count)) self.call_middleware( headers=get_authorization_header(self._token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) # Assert that the token wasn't cached again. self.assertThat(1, matchers.Equals(cache.set.call_count)) def test_caching_token_invalid(self): conf = copy.deepcopy(self._test_conf) self.set_middleware(conf=conf) self.middleware._token_cache._env_cache_name = 'cache' cache = _cache._FakeClient() self.middleware._token_cache.initialize(env={'cache': cache}) orig_cache_set = cache.set cache.set = mock.Mock(side_effect=orig_cache_set) def mock_resp(request, context): return self._introspect_response( request, context, auth_method=self._auth_method, introspect_client_id=self._test_client_id, introspect_client_secret=self._test_client_secret, access_token=self._token, active=True, metadata=self._default_metadata ) self.requests_mock.post(self._introspect_endpoint, json=mock_resp) self.requests_mock.get(self._auth_url, json=VERSION_LIST_v3, status_code=300) self.call_middleware( headers=get_authorization_header(self._token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertThat(1, matchers.Equals(cache.set.call_count)) # Confirm that authentication fails due to invalid token. self.call_middleware( headers=get_authorization_header(str(uuid.uuid4()) + '_token'), expected_status=500, method='GET', path='/vnfpkgm/v1/vnf_packages', der_client_cert=self._der_client_cert, environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self._token = self.token_dict['uuid_token_default'] class FilterFactoryTest(utils.BaseTestCase): def test_filter_factory(self): certfile = '/certfile_01' keyfile = '/keyfile_01' cafile = '/cafile_01' insecure = True http_connect_timeout = 1000 introspect_endpoint = 'http://introspect_endpoint_01' audience = 'http://audience_01' auth_method = 'private_key_jwt' client_id = 'client_id_01' client_secret = 'client_secret_01' thumbprint_verify = True jwt_key_file = '/jwt_key_file_01' jwt_algorithm = 'HS512' jwt_bearer_time_out = 1000 mapping_project_id = 'test_project.id' mapping_project_name = 'test_project.name' mapping_project_domain_id = 'test_project.domain.id' mapping_project_domain_name = 'test_project.domain.name' mapping_user_id = 'test_client_id' mapping_user_name = 'test_username' mapping_user_domain_id = 'test_user_domain.id' mapping_user_domain_name = 'test_user_domain.name' mapping_roles = 'test_roles' conf = { 'certfile': certfile, 'keyfile': keyfile, 'cafile': cafile, 'insecure': insecure, 'http_connect_timeout': http_connect_timeout, 'introspect_endpoint': introspect_endpoint, 'audience': audience, 'auth_method': auth_method, 'client_id': client_id, 'client_secret': client_secret, 'thumbprint_verify': thumbprint_verify, 'jwt_key_file': jwt_key_file, 'jwt_algorithm': jwt_algorithm, 'jwt_bearer_time_out': jwt_bearer_time_out, 'mapping_project_id': mapping_project_id, 'mapping_project_name': mapping_project_name, 'mapping_project_domain_id': mapping_project_domain_id, 'mapping_project_domain_name': mapping_project_domain_name, 'mapping_user_id': mapping_user_id, 'mapping_user_name': mapping_user_name, 'mapping_user_domain_id': mapping_user_domain_id, 'mapping_user_domain_name': mapping_user_domain_name, 'mapping_roles': mapping_roles } auth_filter = external_oauth2_token.filter_factory(conf) app = FakeApp() m = auth_filter(app) self.assertIsInstance(m, external_oauth2_token.ExternalAuth2Protocol) self.assertEqual(certfile, m._get_config_option('certfile', is_required=False)) self.assertEqual(keyfile, m._get_config_option('keyfile', is_required=False)) self.assertEqual(cafile, m._get_config_option('cafile', is_required=False)) self.assertEqual(insecure, m._get_config_option('insecure', is_required=False)) self.assertEqual(http_connect_timeout, m._get_config_option('http_connect_timeout', is_required=False)) self.assertEqual(introspect_endpoint, m._get_config_option('introspect_endpoint', is_required=False)) self.assertEqual(audience, m._get_config_option('audience', is_required=False)) self.assertEqual(auth_method, m._get_config_option('auth_method', is_required=False)) self.assertEqual(client_id, m._get_config_option('client_id', is_required=False)) self.assertEqual(client_secret, m._get_config_option('client_secret', is_required=False)) self.assertEqual(thumbprint_verify, m._get_config_option('thumbprint_verify', is_required=False)) self.assertEqual(jwt_key_file, m._get_config_option('jwt_key_file', is_required=False)) self.assertEqual(jwt_algorithm, m._get_config_option('jwt_algorithm', is_required=False)) self.assertEqual(jwt_bearer_time_out, m._get_config_option('jwt_bearer_time_out', is_required=False)) self.assertEqual(mapping_project_id, m._get_config_option('mapping_project_id', is_required=False)) self.assertEqual(mapping_project_name, m._get_config_option('mapping_project_name', is_required=False)) self.assertEqual(mapping_project_domain_id, m._get_config_option('mapping_project_domain_id', is_required=False)) self.assertEqual(mapping_project_domain_name, m._get_config_option('mapping_project_domain_name', is_required=False)) self.assertEqual(mapping_user_id, m._get_config_option('mapping_user_id', is_required=False)) self.assertEqual(mapping_user_name, m._get_config_option('mapping_user_name', is_required=False)) self.assertEqual(mapping_user_domain_id, m._get_config_option('mapping_user_domain_id', is_required=False)) self.assertEqual(mapping_user_domain_name, m._get_config_option('mapping_user_domain_name', is_required=False)) self.assertEqual(mapping_roles, m._get_config_option('mapping_roles', is_required=False)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_fixtures.py0000664000175000017500000000530100000000000027024 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import uuid from oslo_utils import timeutils from keystonemiddleware import fixture from keystonemiddleware.tests.unit.auth_token import test_auth_token_middleware class AuthTokenFixtureTest( test_auth_token_middleware.BaseAuthTokenMiddlewareTest): def setUp(self): self.token_id = uuid.uuid4().hex self.user_id = uuid.uuid4().hex self.username = uuid.uuid4().hex self.project_id = uuid.uuid4().hex self.project_name = uuid.uuid4().hex self.role_list = [uuid.uuid4().hex, uuid.uuid4().hex] super(AuthTokenFixtureTest, self).setUp() self.atm_fixture = self.useFixture(fixture.AuthTokenFixture()) self.atm_fixture.add_token_data(token_id=self.token_id, user_id=self.user_id, user_name=self.username, role_list=self.role_list, project_id=self.project_id, project_name=self.project_name) self.set_middleware() self.middleware._app.expected_env = { 'HTTP_X_USER_ID': self.user_id, 'HTTP_X_USER_NAME': self.username, 'HTTP_X_PROJECT_ID': self.project_id, 'HTTP_X_PROJECT_NAME': self.project_name, 'HTTP_X_ROLES': ','.join(self.role_list)} def test_auth_token_fixture_valid_token(self): resp = self.call_middleware(headers={'X-Auth-Token': self.token_id}) self.assertIn('keystone.token_info', resp.request.environ) def test_auth_token_fixture_invalid_token(self): self.call_middleware( headers={'X-Auth-Token': uuid.uuid4().hex}, expected_status=401) def test_auth_token_fixture_expired_token(self): expired_token_id = uuid.uuid4().hex self.atm_fixture.add_token_data( token_id=expired_token_id, user_id=self.user_id, role_list=self.role_list, expires=(timeutils.utcnow() - datetime.timedelta(seconds=86400))) self.call_middleware( headers={'X-Auth-Token': expired_token_id}, expected_status=401) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_oauth2_mtls_token_middleware.py0000664000175000017500000003432300000000000033017 0ustar00zuulzuul00000000000000# Copyright 2022 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 http.client as http_client import json import logging import ssl from unittest import mock import uuid import webob.dec import fixtures from oslo_config import cfg import testresources from keystoneauth1 import access from keystoneauth1 import exceptions as ksa_exceptions from keystonemiddleware import oauth2_mtls_token from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import BASE_URI from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import ENDPOINT_NOT_FOUND_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import ERROR_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FAKE_ADMIN_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FAKE_ADMIN_TOKEN_ID from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FakeApp from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FakeOsloCache from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import request_timeout_response from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import TIMEOUT_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import VERSION_LIST_v3 from keystonemiddleware.tests.unit import client_fixtures from keystonemiddleware.tests.unit.test_oauth2_token_middleware \ import FakeOauth2TokenV3App from keystonemiddleware.tests.unit.test_oauth2_token_middleware \ import get_authorization_header from keystonemiddleware.tests.unit import utils _no_value = object() class FakeSocket(object): def __init__(self, binary_peer_cert): self.binary_peer_cert = binary_peer_cert def getpeercert(self, binary_form=True): return self.binary_peer_cert class FakeWsgiInput(object): def __init__(self, fake_socket): self.fake_socket = fake_socket def get_socket(self): return self.fake_socket class BaseOauth2mTlsTokenMiddlewareTest(base.BaseAuthTokenTestCase): def setUp(self, expected_env=None, auth_version=None, fake_app=None): cfg.CONF.clear() super(BaseOauth2mTlsTokenMiddlewareTest, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) self.useFixture( fixtures.MockPatchObject(oauth2_mtls_token.OAuth2mTlsProtocol, '_create_oslo_cache', return_value=FakeOsloCache)) self.expected_env = expected_env or dict() self.fake_app = fake_app or FakeApp self.middleware = None self.conf = { 'identity_uri': 'https://keystone.example.com:1234/testadmin/', 'auth_version': auth_version, 'www_authenticate_uri': 'https://keystone.example.com:1234', 'admin_user': uuid.uuid4().hex, } self.auth_version = auth_version def call_middleware(self, **kwargs): return self.call(self.middleware, **kwargs) def set_middleware(self, expected_env=None, conf=None): """Configure the class ready to call the oauth2_token middleware. Set up the various fake items needed to run the middleware. Individual tests that need to further refine these can call this function to override the class defaults. """ if conf: self.conf.update(conf) if expected_env: self.expected_env.update(expected_env) self.middleware = oauth2_mtls_token.OAuth2mTlsProtocol( self.fake_app(self.expected_env), self.conf) def call(self, middleware, method='GET', path='/', headers=None, expected_status=http_client.OK, expected_body_string=None, **kwargs): req = webob.Request.blank(path, **kwargs) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) self.assertEqual(expected_status, resp.status_int) if expected_body_string: self.assertIn(expected_body_string, str(resp.body)) resp.request = req return resp def assertUnauthorizedResp(self, resp): error = json.loads(resp.body) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) self.assertEqual( 'Keystone uri="%s"' % self.conf.get('www_authenticate_uri'), resp.headers['WWW-Authenticate']) self.assertEqual( 'Unauthorized', error.get('error').get('title')) self.assertEqual( 'The request you have made requires authentication.', error.get('error').get('message')) class Oauth2mTlsTokenMiddlewareTest(BaseOauth2mTlsTokenMiddlewareTest, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(Oauth2mTlsTokenMiddlewareTest, self).setUp( auth_version='v3.0', fake_app=FakeOauth2TokenV3App) self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.set_middleware(conf={'service_type': 'tacker'}) def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksa_exceptions.ConnectFailure(msg) if token_id == ENDPOINT_NOT_FOUND_TOKEN: raise ksa_exceptions.EndpointNotFound() if token_id == TIMEOUT_TOKEN: request_timeout_response(request, context) try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: response = "" context.status_code = 404 return response def call_middleware(self, client_cert=_no_value, **kwargs): if client_cert is _no_value: client_cert = self.examples.V3_OAUTH2_MTLS_CERTIFICATE if client_cert: fake_socket = FakeSocket(client_cert) fake_wsgi_input = FakeWsgiInput(fake_socket) kwargs.update({'environ': {'wsgi.input': fake_wsgi_input}}) return self.call(self.middleware, **kwargs) def test_basic(self): token = self.examples.v3_OAUTH2_CREDENTIAL token_data = self.examples.TOKEN_RESPONSES[token] resp = self.call_middleware( headers=get_authorization_header(token), expected_status=200, method='GET', path='/vnfpkgm/v1/vnf_packages', ) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertTrue(token_auth.has_user_token) self.assertEqual(token_data.user_id, token_auth.user.user_id) self.assertEqual(token_data.project_id, token_auth.user.project_id) self.assertEqual(token_data.user_domain_id, token_auth.user.user_domain_id) self.assertEqual(token_data.project_domain_id, token_auth.user.project_domain_id) self.assertEqual(token_data.oauth2_thumbprint, token_auth.user.oauth2_credential_thumbprint) def test_not_oauth2_credential_token(self): token = self.examples.v3_APP_CRED_TOKEN resp = self.call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', ) self.assertUnauthorizedResp(resp) self.assertIn( 'Invalid OAuth2.0 certificate-bound access token: ' 'The token is not an OAuth2.0 credential access token.', self.logger.output) def test_thumbprint_not_match(self): diff_cert = self.examples.V3_OAUTH2_MTLS_CERTIFICATE_DIFF token = self.examples.v3_OAUTH2_CREDENTIAL resp = self.call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', client_cert=diff_cert ) self.assertUnauthorizedResp(resp) self.assertIn('The two thumbprints do not match.', self.logger.output) @mock.patch.object(ssl, 'DER_cert_to_PEM_cert') def test_gen_thumbprint_exception(self, mock_DER_cert_to_PEM_cert): except_msg = 'Boom!' mock_DER_cert_to_PEM_cert.side_effect = Exception(except_msg) token = self.examples.v3_OAUTH2_CREDENTIAL resp = self.call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages' ) self.assertUnauthorizedResp(resp) self.assertIn(except_msg, self.logger.output) def test_without_cert(self): token = self.examples.v3_OAUTH2_CREDENTIAL resp = self.call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', client_cert=None ) self.assertUnauthorizedResp(resp) self.assertIn('Unable to obtain the client certificate.', self.logger.output) def test_not_wsgi_input(self): token = self.examples.v3_OAUTH2_CREDENTIAL resp = super(Oauth2mTlsTokenMiddlewareTest, self).call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': None} ) self.assertUnauthorizedResp(resp) self.assertIn('Unable to obtain the client certificate.', self.logger.output) def test_not_socket(self): token = self.examples.v3_OAUTH2_CREDENTIAL resp = super(Oauth2mTlsTokenMiddlewareTest, self).call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(None)} ) self.assertUnauthorizedResp(resp) self.assertIn('Unable to obtain the client certificate.', self.logger.output) def test_not_peer_cert(self): token = self.examples.v3_OAUTH2_CREDENTIAL resp = super(Oauth2mTlsTokenMiddlewareTest, self).call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', environ={'wsgi.input': FakeWsgiInput(FakeSocket(None))} ) self.assertUnauthorizedResp(resp) self.assertIn('Unable to obtain the client certificate.', self.logger.output) @mock.patch.object(access, 'create') def test_keystonemiddleware_exceptiton(self, mock_create): except_msg = 'Unrecognized auth response' mock_create.side_effect = Exception(except_msg) token = self.examples.v3_OAUTH2_CREDENTIAL resp = self.call_middleware( headers=get_authorization_header(token), expected_status=401, method='GET', path='/vnfpkgm/v1/vnf_packages', ) self.assertUnauthorizedResp(resp) self.assertIn( 'Invalid token contents.', self.logger.output) self.assertIn( 'Invalid OAuth2.0 certificate-bound access token: %s' % 'Token authorization failed', self.logger.output) def test_request_no_token(self): resp = self.call_middleware(expected_status=401) self.assertUnauthorizedResp(resp) def test_request_blank_token(self): resp = self.call_middleware(headers=get_authorization_header(''), expected_status=401) self.assertUnauthorizedResp(resp) def _get_cached_token(self, token): return self.middleware._token_cache.get(token) def assert_valid_last_url(self, token_id): self.assertLastPath('/v3/auth/tokens') def assertLastPath(self, path): if path: self.assertEqual(BASE_URI + path, self.requests_mock.last_request.url) else: self.assertIsNone(self.requests_mock.last_request) def test_http_error_not_cached_token(self): """Test to don't cache token as invalid on network errors. We use UUID tokens since they are the easiest one to reach get_http_connection. """ self.set_middleware(conf={'http_request_max_retries': '0'}) self.call_middleware(headers=get_authorization_header(ERROR_TOKEN), expected_status=503) self.assertIsNone(self._get_cached_token(ERROR_TOKEN)) self.assert_valid_last_url(ERROR_TOKEN) class FilterFactoryTest(utils.BaseTestCase): def test_filter_factory(self): conf = {} auth_filter = oauth2_mtls_token.filter_factory(conf) m = auth_filter(FakeOauth2TokenV3App()) self.assertIsInstance(m, oauth2_mtls_token.OAuth2mTlsProtocol) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_oauth2_token_middleware.py0000664000175000017500000003141500000000000031757 0ustar00zuulzuul00000000000000# Copyright 2022 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 fixtures import http.client as http_client import logging import testresources import uuid import webob.dec from oslo_config import cfg from keystoneauth1 import exceptions as ksa_exceptions from keystonemiddleware import oauth2_token from keystonemiddleware.tests.unit.auth_token import base from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import BASE_URI from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import ENDPOINT_NOT_FOUND_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import ERROR_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FAKE_ADMIN_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FAKE_ADMIN_TOKEN_ID from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware\ import FakeApp from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import FakeOsloCache from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import request_timeout_response from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import TIMEOUT_TOKEN from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import v3FakeApp from keystonemiddleware.tests.unit.auth_token.test_auth_token_middleware \ import VERSION_LIST_v3 from keystonemiddleware.tests.unit import client_fixtures from keystonemiddleware.tests.unit import utils def get_authorization_header(token): return {'Authorization': f'Bearer {token}'} class FakeOauth2TokenV3App(v3FakeApp): @webob.dec.wsgify def __call__(self, req): resp = webob.Response() resp.body = FakeApp.SUCCESS return resp class BaseOauth2TokenMiddlewareTest(base.BaseAuthTokenTestCase): def setUp(self, expected_env=None, auth_version=None, fake_app=None): cfg.CONF.clear() super(BaseOauth2TokenMiddlewareTest, self).setUp() self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) self.useFixture(fixtures.MockPatchObject(oauth2_token.OAuth2Protocol, '_create_oslo_cache', return_value=FakeOsloCache)) self.expected_env = expected_env or dict() self.fake_app = fake_app or FakeApp self.middleware = None self.conf = { 'identity_uri': 'https://keystone.example.com:1234/testadmin/', 'auth_version': auth_version, 'www_authenticate_uri': 'https://keystone.example.com:1234', 'admin_user': uuid.uuid4().hex, } self.auth_version = auth_version def call_middleware(self, **kwargs): return self.call(self.middleware, **kwargs) def set_middleware(self, expected_env=None, conf=None): """Configure the class ready to call the oauth2_token middleware. Set up the various fake items needed to run the middleware. Individual tests that need to further refine these can call this function to override the class defaults. """ if conf: self.conf.update(conf) if expected_env: self.expected_env.update(expected_env) self.middleware = oauth2_token.OAuth2Protocol( self.fake_app(self.expected_env), self.conf) def call(self, middleware, method='GET', path='/', headers=None, expected_status=http_client.OK, expected_body_string=None): req = webob.Request.blank(path) req.method = method for k, v in (headers or {}).items(): req.headers[k] = v resp = req.get_response(middleware) self.assertEqual(expected_status, resp.status_int) if expected_body_string: self.assertIn(expected_body_string, str(resp.body)) resp.request = req return resp class Oauth2TokenMiddlewareTest(BaseOauth2TokenMiddlewareTest, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def setUp(self): super(Oauth2TokenMiddlewareTest, self).setUp( auth_version='v3.0', fake_app=FakeOauth2TokenV3App) self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, text=FAKE_ADMIN_TOKEN) self.requests_mock.get(BASE_URI, json=VERSION_LIST_v3, status_code=300) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, text=self.token_response, headers={'X-Subject-Token': uuid.uuid4().hex}) self.set_middleware() def token_response(self, request, context): auth_id = request.headers.get('X-Auth-Token') token_id = request.headers.get('X-Subject-Token') self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID) if token_id == ERROR_TOKEN: msg = "Network connection refused." raise ksa_exceptions.ConnectFailure(msg) if token_id == ENDPOINT_NOT_FOUND_TOKEN: raise ksa_exceptions.EndpointNotFound() if token_id == TIMEOUT_TOKEN: request_timeout_response(request, context) try: response = self.examples.JSON_TOKEN_RESPONSES[token_id] except KeyError: response = "" context.status_code = 404 return response def test_app_cred_token_without_access_rules(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_TOKEN token_data = self.examples.TOKEN_RESPONSES[token] resp = self.call_middleware(headers=get_authorization_header(token)) self.assertEqual(FakeApp.SUCCESS, resp.body) token_auth = resp.request.environ['keystone.token_auth'] self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) def test_app_cred_access_rules_token(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_ACCESS_RULES token_data = self.examples.TOKEN_RESPONSES[token] resp = self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v2.1/servers') token_auth = resp.request.environ['keystone.token_auth'] self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) self.assertEqual(token_data.application_credential_access_rules, token_auth.user.application_credential_access_rules) resp = self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2.1/servers/someuuid') self.assertEqual(token_data.application_credential_id, token_auth.user.application_credential_id) self.assertEqual(token_data.application_credential_access_rules, token_auth.user.application_credential_access_rules) def test_app_cred_no_access_rules_token(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_EMPTY_ACCESS_RULES self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2.1/servers') def test_app_cred_matching_rules(self): self.set_middleware(conf={'service_type': 'compute'}) token = self.examples.v3_APP_CRED_MATCHING_RULES self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v2.1/servers/foobar') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2.1/servers/foobar/barfoo') self.set_middleware(conf={'service_type': 'image'}) self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v2/images/foobar') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2/images/foobar/barfoo') self.set_middleware(conf={'service_type': 'identity'}) self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v3/projects/123/users/456/roles/member') self.set_middleware(conf={'service_type': 'block-storage'}) self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v3/123/types/456') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v3/123/types') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2/123/types/456') self.set_middleware(conf={'service_type': 'object-store'}) self.call_middleware(headers=get_authorization_header(token), expected_status=200, method='GET', path='/v1/1/2/3') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v1/1/2') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/v2/1/2') self.call_middleware(headers=get_authorization_header(token), expected_status=401, method='GET', path='/info') def test_request_no_token(self): resp = self.call_middleware(expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def test_request_blank_token(self): resp = self.call_middleware(headers=get_authorization_header(''), expected_status=401) self.assertEqual('Keystone uri="https://keystone.example.com:1234"', resp.headers['WWW-Authenticate']) def test_request_not_app_cred_token(self): self.call_middleware( headers=get_authorization_header( self.examples.v3_UUID_TOKEN_DEFAULT), expected_status=200) def _get_cached_token(self, token): return self.middleware._token_cache.get(token) def assert_valid_last_url(self, token_id): self.assertLastPath('/v3/auth/tokens') def assertLastPath(self, path): if path: self.assertEqual(BASE_URI + path, self.requests_mock.last_request.url) else: self.assertIsNone(self.requests_mock.last_request) def test_http_error_not_cached_token(self): """Test to don't cache token as invalid on network errors. We use UUID tokens since they are the easiest one to reach get_http_connection. """ self.set_middleware(conf={'http_request_max_retries': '0'}) self.call_middleware(headers=get_authorization_header(ERROR_TOKEN), expected_status=503) self.assertIsNone(self._get_cached_token(ERROR_TOKEN)) self.assert_valid_last_url(ERROR_TOKEN) class FilterFactoryTest(utils.BaseTestCase): def test_filter_factory(self): conf = {} auth_filter = oauth2_token.filter_factory(conf) m = auth_filter(FakeOauth2TokenV3App()) self.assertIsInstance(m, oauth2_token.OAuth2Protocol) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_opts.py0000664000175000017500000001156600000000000026152 0ustar00zuulzuul00000000000000# Copyright (c) 2014 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 stevedore from testtools import matchers from keystonemiddleware.auth_token import _opts as new_opts from keystonemiddleware import opts as old_opts from keystonemiddleware.tests.unit import utils class OptsTestCase(utils.TestCase): def test_original_list_all_options(self): result_of_old_opts = old_opts.list_auth_token_opts() self.assertThat(result_of_old_opts, matchers.HasLength(1)) for group in (g for (g, _l) in result_of_old_opts): self.assertEqual('keystone_authtoken', group) # This is the original list that includes deprecated options expected_opt_names = [ 'auth_admin_prefix', 'auth_host', 'interface', 'auth_port', 'auth_protocol', 'www_authenticate_uri', 'auth_uri', 'identity_uri', 'auth_version', 'delay_auth_decision', 'http_connect_timeout', 'http_request_max_retries', 'admin_token', 'admin_user', 'admin_password', 'admin_tenant_name', 'cache', 'certfile', 'keyfile', 'cafile', 'region_name', 'insecure', 'memcached_servers', 'token_cache_time', 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', 'memcache_pool_conn_get_timeout', 'memcache_pool_socket_timeout', 'memcache_sasl_enabled', 'memcache_username', 'memcache_password', 'include_service_catalog', 'enforce_token_bind', 'auth_type', 'auth_section', 'service_token_roles', 'service_token_roles_required', 'service_type', ] opt_names = [o.name for (g, l) in result_of_old_opts for o in l] self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names))) for opt in opt_names: self.assertIn(opt, expected_opt_names) def _test_list_auth_token_opts(self, result): self.assertThat(result, matchers.HasLength(1)) for group in (g for (g, _l) in result): self.assertEqual('keystone_authtoken', group) # This is the sample config generator list WITHOUT deprecations expected_opt_names = [ 'www_authenticate_uri', 'interface', 'auth_uri', 'auth_version', 'delay_auth_decision', 'http_connect_timeout', 'http_request_max_retries', 'cache', 'certfile', 'keyfile', 'cafile', 'region_name', 'insecure', 'memcached_servers', 'token_cache_time', 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', 'memcache_pool_conn_get_timeout', 'memcache_pool_socket_timeout', 'memcache_sasl_enabled', 'memcache_username', 'memcache_password', 'include_service_catalog', 'enforce_token_bind', 'auth_type', 'auth_section', 'service_token_roles', 'service_token_roles_required', 'service_type', ] opt_names = [o.name for (g, l) in result for o in l] self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names))) for opt in opt_names: self.assertIn(opt, expected_opt_names) def test_list_auth_token_opts(self): self._test_list_auth_token_opts(new_opts.list_opts()) def test_entry_point(self): em = stevedore.ExtensionManager('oslo.config.opts', invoke_on_load=True) for extension in em: if extension.name == 'keystonemiddleware.auth_token': break else: self.fail('keystonemiddleware.auth_token not found') self._test_list_auth_token_opts(extension.obj) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/test_s3_token_middleware.py0000664000175000017500000002360200000000000031101 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import urllib.parse import fixtures from oslo_serialization import jsonutils import requests from requests_mock.contrib import fixture as rm_fixture from testtools import matchers import webob from keystonemiddleware import s3_token from keystonemiddleware.tests.unit import utils GOOD_RESPONSE = {'access': {'token': {'id': 'TOKEN_ID', 'tenant': {'id': 'TENANT_ID'}}}} class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __call__(self, env, start_response): resp = webob.Response() resp.environ = env return resp(env, start_response) class S3TokenMiddlewareTestBase(utils.TestCase): TEST_WWW_AUTHENTICATE_URI = 'https://fakehost/identity' TEST_URL = '%s/v3/s3tokens' % (TEST_WWW_AUTHENTICATE_URI, ) def setUp(self): super(S3TokenMiddlewareTestBase, self).setUp() self.conf = { 'www_authenticate_uri': self.TEST_WWW_AUTHENTICATE_URI, } self.requests_mock = self.useFixture(rm_fixture.Fixture()) def start_fake_response(self, status, headers): self.response_status = int(status.split(' ', 1)[0]) self.response_headers = dict(headers) class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestGood, self).setUp() self.middleware = s3_token.S3Token(FakeApp(), self.conf) self.requests_mock.post(self.TEST_URL, status_code=201, json=GOOD_RESPONSE) # Ignore the request and pass to the next middleware in the # pipeline if no path has been specified. def test_no_path_request(self): req = webob.Request.blank('/') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) # Ignore the request and pass to the next middleware in the # pipeline if no Authorization header has been specified def test_without_authorization(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) def test_without_auth_storage_token(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'badboy' self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) def test_authorized(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID')) self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID') def test_authorized_http(self): protocol = 'http' host = 'fakehost' port = 35357 self.requests_mock.post( '%s://%s:%s/v3/s3tokens' % (protocol, host, port), status_code=201, json=GOOD_RESPONSE) self.middleware = ( s3_token.filter_factory({'auth_protocol': protocol, 'auth_host': host, 'auth_port': port})(FakeApp())) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(req.path.startswith('/v1/AUTH_TENANT_ID')) self.assertEqual(req.headers['X-Auth-Token'], 'TOKEN_ID') def test_authorization_nova_toconnect(self): req = webob.Request.blank('/v1/AUTH_swiftint/c/o') req.headers['Authorization'] = 'access:FORCED_TENANT_ID:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) path = req.environ['PATH_INFO'] self.assertTrue(path.startswith('/v1/AUTH_FORCED_TENANT_ID')) @mock.patch.object(requests, 'post') def test_insecure(self, MOCK_REQUEST): self.middleware = ( s3_token.filter_factory({'insecure': 'True'})(FakeApp())) text_return_value = jsonutils.dumps(GOOD_RESPONSE).encode() MOCK_REQUEST.return_value = utils.TestResponse({ 'status_code': 201, 'text': text_return_value}) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) self.assertTrue(MOCK_REQUEST.called) mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertIs(mock_kwargs['verify'], False) def test_insecure_option(self): # insecure is passed as a string. # Some non-secure values. true_values = ['true', 'True', '1', 'yes'] for val in true_values: config = {'insecure': val, 'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertIs(False, middleware._verify) # Some "secure" values, including unexpected value. false_values = ['false', 'False', '0', 'no', 'someweirdvalue'] for val in false_values: config = {'insecure': val, 'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertEqual('false_ind', middleware._verify) # Default is secure. config = {'certfile': 'false_ind'} middleware = s3_token.filter_factory(config)(FakeApp()) self.assertIs('false_ind', middleware._verify) def test_unicode_path(self): url = u'/v1/AUTH_cfa/c/euro\u20ac'.encode('utf8') req = webob.Request.blank(urllib.parse.quote(url)) req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' req.get_response(self.middleware) class S3TokenMiddlewareTestBad(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestBad, self).setUp() self.middleware = s3_token.S3Token(FakeApp(), self.conf) def test_unauthorized_token(self): ret = {"error": {"message": "EC2 access key not found.", "code": 401, "title": "Unauthorized"}} self.requests_mock.post(self.TEST_URL, status_code=403, json=ret) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) s3_denied_req = self.middleware._deny_request('AccessDenied') self.assertEqual(resp.body, s3_denied_req.body) self.assertEqual(resp.status_int, s3_denied_req.status_int) def test_bogus_authorization(self): req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'badboy' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) self.assertEqual(resp.status_int, 400) s3_invalid_req = self.middleware._deny_request('InvalidURI') self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) def test_fail_to_connect_to_keystone(self): with mock.patch.object(self.middleware, '_json_request') as o: s3_invalid_req = self.middleware._deny_request('InvalidURI') o.side_effect = s3_token.ServiceError(s3_invalid_req) req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) def test_bad_reply(self): self.requests_mock.post(self.TEST_URL, status_code=201, text="") req = webob.Request.blank('/v1/AUTH_cfa/c/o') req.headers['Authorization'] = 'access:signature' req.headers['X-Storage-Token'] = 'token' resp = req.get_response(self.middleware) s3_invalid_req = self.middleware._deny_request('InvalidURI') self.assertEqual(resp.body, s3_invalid_req.body) self.assertEqual(resp.status_int, s3_invalid_req.status_int) class S3TokenMiddlewareTestDeprecatedOptions(S3TokenMiddlewareTestBase): def setUp(self): super(S3TokenMiddlewareTestDeprecatedOptions, self).setUp() self.conf = { 'auth_uri': self.TEST_WWW_AUTHENTICATE_URI, } self.logger = self.useFixture(fixtures.FakeLogger()) self.middleware = s3_token.S3Token(FakeApp(), self.conf) self.requests_mock.post(self.TEST_URL, status_code=201, json=GOOD_RESPONSE) def test_logs_warning(self): req = webob.Request.blank('/') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) log = "Use of the auth_uri option was deprecated in the Queens " \ "release in favor of www_authenticate_uri." self.assertThat(self.logger.output, matchers.Contains(log)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/keystonemiddleware/tests/unit/utils.py0000664000175000017500000001222100000000000025253 0ustar00zuulzuul00000000000000# 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 time from unittest import mock import uuid import warnings import fixtures from oslo_log import log as logging import oslotest.base as oslotest import requests import webob import webtest class BaseTestCase(oslotest.BaseTestCase): def setUp(self): super(BaseTestCase, self).setUp() # If keystonemiddleware calls any deprecated function this will raise # an exception. warnings.filterwarnings('error', category=DeprecationWarning, module='^keystonemiddleware\\.') self.addCleanup(warnings.resetwarnings) self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) class TestCase(BaseTestCase): TEST_DOMAIN_ID = '1' TEST_DOMAIN_NAME = 'aDomain' TEST_GROUP_ID = uuid.uuid4().hex TEST_ROLE_ID = uuid.uuid4().hex TEST_TENANT_ID = '1' TEST_TENANT_NAME = 'aTenant' TEST_TOKEN = 'aToken' TEST_TRUST_ID = 'aTrust' TEST_USER = 'test' TEST_USER_ID = uuid.uuid4().hex TEST_ROOT_URL = 'http://127.0.0.1:5000/' def setUp(self): super(TestCase, self).setUp() self.time_patcher = mock.patch.object(time, 'time', lambda: 1234) self.time_patcher.start() def tearDown(self): self.time_patcher.stop() super(TestCase, self).tearDown() class MiddlewareTestCase(BaseTestCase): def create_middleware(self, cb, **kwargs): raise NotImplementedError("implement this in your tests") def create_simple_middleware(self, status='200 OK', body='', headers=None, **kwargs): def cb(req): resp = webob.Response(body, status) resp.headers.update(headers or {}) return resp return self.create_middleware(cb, **kwargs) def create_app(self, *args, **kwargs): return webtest.TestApp(self.create_middleware(*args, **kwargs)) def create_simple_app(self, *args, **kwargs): return webtest.TestApp(self.create_simple_middleware(*args, **kwargs)) class TestResponse(requests.Response): """Utility class to wrap requests.Response. Class used to wrap requests.Response and provide some convenience to initialize with a dict. """ def __init__(self, data): self._text = None super(TestResponse, self).__init__() if isinstance(data, dict): self.status_code = data.get('status_code', 200) headers = data.get('headers') if headers: self.headers.update(headers) # Fake the text attribute to streamline Response creation # _content is defined by requests.Response self._content = data.get('text') else: self.status_code = data def __eq__(self, other): """Test if the response is equivalent to another response. This works by comparing the attribute dictionaries of both TestResponse instances. """ return self.__dict__ == other.__dict__ @property def text(self): return self.content class DisableModuleFixture(fixtures.Fixture): """A fixture to provide support for unloading/disabling modules.""" def __init__(self, module, *args, **kw): super(DisableModuleFixture, self).__init__(*args, **kw) self.module = module self._finders = [] self._cleared_modules = {} def tearDown(self): super(DisableModuleFixture, self).tearDown() for finder in self._finders: sys.meta_path.remove(finder) sys.modules.update(self._cleared_modules) def clear_module(self): cleared_modules = {} for fullname in list(sys.modules.keys()): if (fullname == self.module or fullname.startswith(self.module + '.')): cleared_modules[fullname] = sys.modules.pop(fullname) return cleared_modules def setUp(self): """Ensure ImportError for the specified module.""" super(DisableModuleFixture, self).setUp() # Clear 'module' references in sys.modules self._cleared_modules.update(self.clear_module()) finder = NoModuleFinder(self.module) self._finders.append(finder) sys.meta_path.insert(0, finder) class NoModuleFinder(object): """Disallow further imports of 'module'.""" def __init__(self, module): self.module = module def find_module(self, fullname, path): if fullname == self.module or fullname.startswith(self.module + '.'): raise ImportError ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1740154104.024752 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/0000775000175000017500000000000000000000000023114 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/PKG-INFO0000644000175000017500000000741400000000000024215 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: keystonemiddleware Version: 10.9.0 Summary: Middleware for OpenStack Identity Home-page: https://docs.openstack.org/keystonemiddleware/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: keystoneauth1>=3.12.0 Requires-Dist: oslo.cache>=1.26.0 Requires-Dist: oslo.config>=5.2.0 Requires-Dist: oslo.context>=2.19.2 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.log>=3.36.0 Requires-Dist: oslo.serialization>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: pbr>=2.0.0 Requires-Dist: pycadf>=1.1.0 Requires-Dist: PyJWT>=2.4.0 Requires-Dist: python-keystoneclient>=3.20.0 Requires-Dist: requests>=2.14.2 Requires-Dist: WebOb>=1.7.1 Provides-Extra: audit-notifications Requires-Dist: oslo.messaging>=5.29.0; extra == "audit-notifications" Provides-Extra: test Requires-Dist: hacking~=6.1.0; extra == "test" Requires-Dist: flake8-docstrings~=1.7.0; extra == "test" Requires-Dist: coverage>=4.0; extra == "test" Requires-Dist: cryptography>=3.0; extra == "test" Requires-Dist: fixtures>=3.0.0; extra == "test" Requires-Dist: oslotest>=3.2.0; extra == "test" Requires-Dist: stevedore>=1.20.0; extra == "test" Requires-Dist: requests-mock>=1.2.0; extra == "test" Requires-Dist: stestr>=2.0.0; extra == "test" Requires-Dist: testresources>=2.0.0; extra == "test" Requires-Dist: testtools>=2.2.0; extra == "test" Requires-Dist: python-binary-memcached>=0.29.0; extra == "test" Requires-Dist: python-memcached>=1.59; extra == "test" Requires-Dist: WebTest>=2.0.27; extra == "test" Requires-Dist: oslo.messaging>=5.29.0; extra == "test" Requires-Dist: PyJWT>=2.4.0; extra == "test" Requires-Dist: bandit>=1.1.0; extra == "test" ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/keystonemiddleware.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Middleware for the OpenStack Identity API (Keystone) ==================================================== .. image:: https://img.shields.io/pypi/v/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/keystonemiddleware.svg :target: https://pypi.org/project/keystonemiddleware/ :alt: Downloads This package contains middleware modules designed to provide authentication and authorization features to web services other than `Keystone `. The most prominent module is ``keystonemiddleware.auth_token``. This package does not expose any CLI or Python API features. For information on contributing, see ``CONTRIBUTING.rst``. * License: Apache License, Version 2.0 * Documentation: https://docs.openstack.org/keystonemiddleware/latest/ * Source: https://opendev.org/openstack/keystonemiddleware * Bugs: https://bugs.launchpad.net/keystonemiddleware * Release notes: https://docs.openstack.org/releasenotes/keystonemiddleware/ For any other information, refer to the parent project, Keystone: https://github.com/openstack/keystone ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/SOURCES.txt0000664000175000017500000001631600000000000025007 0ustar00zuulzuul00000000000000.coveragerc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini config-generator/keystonemiddleware.conf doc/.gitignore doc/Makefile doc/requirements.txt doc/ext/__init__.py doc/ext/apidoc.py doc/source/audit.rst doc/source/conf.py doc/source/index.rst doc/source/installation.rst doc/source/middlewarearchitecture.rst doc/source/images/audit.png doc/source/images/graphs_authComp.svg doc/source/images/graphs_authCompDelegate.svg keystonemiddleware/__init__.py keystonemiddleware/ec2_token.py keystonemiddleware/exceptions.py keystonemiddleware/external_oauth2_token.py keystonemiddleware/fixture.py keystonemiddleware/i18n.py keystonemiddleware/oauth2_mtls_token.py keystonemiddleware/oauth2_token.py keystonemiddleware/opts.py keystonemiddleware/s3_token.py keystonemiddleware.egg-info/PKG-INFO keystonemiddleware.egg-info/SOURCES.txt keystonemiddleware.egg-info/dependency_links.txt keystonemiddleware.egg-info/entry_points.txt keystonemiddleware.egg-info/not-zip-safe keystonemiddleware.egg-info/pbr.json keystonemiddleware.egg-info/requires.txt keystonemiddleware.egg-info/top_level.txt keystonemiddleware/_common/__init__.py keystonemiddleware/_common/config.py keystonemiddleware/audit/__init__.py keystonemiddleware/audit/_api.py keystonemiddleware/audit/_notifier.py keystonemiddleware/auth_token/__init__.py keystonemiddleware/auth_token/_auth.py keystonemiddleware/auth_token/_base.py keystonemiddleware/auth_token/_cache.py keystonemiddleware/auth_token/_exceptions.py keystonemiddleware/auth_token/_identity.py keystonemiddleware/auth_token/_memcache_crypt.py keystonemiddleware/auth_token/_opts.py keystonemiddleware/auth_token/_request.py keystonemiddleware/auth_token/_user_plugin.py keystonemiddleware/echo/__init__.py keystonemiddleware/echo/__main__.py keystonemiddleware/echo/service.py keystonemiddleware/locale/en_GB/LC_MESSAGES/keystonemiddleware.po keystonemiddleware/locale/ko_KR/LC_MESSAGES/keystonemiddleware.po keystonemiddleware/tests/__init__.py keystonemiddleware/tests/unit/__init__.py keystonemiddleware/tests/unit/client_fixtures.py keystonemiddleware/tests/unit/test_access_rules.py keystonemiddleware/tests/unit/test_ec2_token_middleware.py keystonemiddleware/tests/unit/test_entry_points.py keystonemiddleware/tests/unit/test_external_oauth2_token_middleware.py keystonemiddleware/tests/unit/test_fixtures.py keystonemiddleware/tests/unit/test_oauth2_mtls_token_middleware.py keystonemiddleware/tests/unit/test_oauth2_token_middleware.py keystonemiddleware/tests/unit/test_opts.py keystonemiddleware/tests/unit/test_s3_token_middleware.py keystonemiddleware/tests/unit/utils.py keystonemiddleware/tests/unit/audit/__init__.py keystonemiddleware/tests/unit/audit/base.py keystonemiddleware/tests/unit/audit/test_audit_api.py keystonemiddleware/tests/unit/audit/test_audit_middleware.py keystonemiddleware/tests/unit/audit/test_audit_oslo_messaging.py keystonemiddleware/tests/unit/audit/test_logging_notifier.py keystonemiddleware/tests/unit/auth_token/__init__.py keystonemiddleware/tests/unit/auth_token/base.py keystonemiddleware/tests/unit/auth_token/test_auth.py keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py keystonemiddleware/tests/unit/auth_token/test_base_middleware.py keystonemiddleware/tests/unit/auth_token/test_cache.py keystonemiddleware/tests/unit/auth_token/test_config.py keystonemiddleware/tests/unit/auth_token/test_connection_pool.py keystonemiddleware/tests/unit/auth_token/test_memcache_crypt.py keystonemiddleware/tests/unit/auth_token/test_request.py keystonemiddleware/tests/unit/auth_token/test_user_auth_plugin.py releasenotes/notes/.placeholder releasenotes/notes/allow-expired-5ddbabcffc5678af.yaml releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e154.yaml releasenotes/notes/bp-enhance-oauth2-interoperability-b1a00f10887d33dd.yaml releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml releasenotes/notes/bp-oauth2-client-credentials-ext-19a40c655ee43f57.yaml releasenotes/notes/bp-support-oauth2-mtls-2d2686c9d5b1fe1f.yaml releasenotes/notes/bp-whitelist-extension-for-app-creds-badf088c8ad584bb.yaml releasenotes/notes/bug-1490804-87c0ff8e764945c1.yaml releasenotes/notes/bug-1544840-a534127f8663e40f.yaml releasenotes/notes/bug-1583690-da67472d7afff0bf.yaml releasenotes/notes/bug-1583699-dba4fe6c057e2be5.yaml releasenotes/notes/bug-1583702-a4469dc1556878b9.yaml releasenotes/notes/bug-1649735-3c68f3243e474775.yaml releasenotes/notes/bug-1677308-a2fa7de67f21cd84.yaml releasenotes/notes/bug-1695038-2cbedcabf8ecc057.yaml releasenotes/notes/bug-1737115-fa3d41e3d3cd7177.yaml releasenotes/notes/bug-1737119-4afe548d28fbf8bb.yaml releasenotes/notes/bug-1747655-6e563d9317bb0f13.yaml releasenotes/notes/bug-1762362-3d092b15c7bab3a4.yaml releasenotes/notes/bug-1766731-3b29192cfeb77964.yaml releasenotes/notes/bug-1782404-c4e37bbc83756a89.yaml releasenotes/notes/bug-1789351-102e2e5119be38b4.yaml releasenotes/notes/bug-1800017-0e5a9b8f62b5ca60.yaml releasenotes/notes/bug-1803940-9a39c66014763af0.yaml releasenotes/notes/bug-1809101-6b5088443d5970ba.yaml releasenotes/notes/bug-1813739-80eae72371903119.yaml releasenotes/notes/bug_1540115-677cf5016bc46348.yaml releasenotes/notes/change-default-identity-endpoint-fab39579255c31bb.yaml releasenotes/notes/delay_auth_instead_of_503-f9b46bf4fbc11455.yaml releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.yaml releasenotes/notes/deprecate-eventlet-unsafe-memcacheclientpool-f8b4a6733513d73e.yaml releasenotes/notes/drop-py-2-7-6655f421a9cac0a2.yaml releasenotes/notes/drop-python-3-6-and-3-7-c407d5898c5eafec.yaml releasenotes/notes/ec2-v2-removal-6a886210cbc9d3e9.yaml releasenotes/notes/enable-sasl-protocol-946149083f604216.yaml releasenotes/notes/fix-audit-no-service-endpoint-ports-72b2009d631dcf19.yaml releasenotes/notes/fix-cache-data-corrupted-issue-d1bd546625690581.yaml releasenotes/notes/interface-option-ed551d2a3162668d.yaml releasenotes/notes/ksm_4.1.0-3cd78446d8e63616.yaml releasenotes/notes/remove-py38-438c67d4d2da02a3.yaml releasenotes/notes/remove_kwargs_to_fetch_token-20e3451ed192ab6a.yaml releasenotes/notes/removed-as-of-ussuri-4e1ea485ba8801c9.yaml releasenotes/notes/rename-auth-uri-d223d883f5898aee.yaml releasenotes/notes/s3token_auth_uri-490c1287d90b9df7.yaml releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.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.po releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/dependency_links.txt0000664000175000017500000000000100000000000027162 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/entry_points.txt0000664000175000017500000000114500000000000026413 0ustar00zuulzuul00000000000000[oslo.config.opts] keystonemiddleware.audit = keystonemiddleware.audit:list_opts keystonemiddleware.auth_token = keystonemiddleware.auth_token._opts:list_opts [paste.filter_factory] audit = keystonemiddleware.audit:filter_factory auth_token = keystonemiddleware.auth_token:filter_factory ec2_token = keystonemiddleware.ec2_token:filter_factory external_oauth2_token = keystonemiddleware.external_oauth2_token:filter_factory oauth2_mtls_token = keystonemiddleware.oauth2_mtls_token:filter_factory oauth2_token = keystonemiddleware.oauth2_token:filter_factory s3_token = keystonemiddleware.s3_token:filter_factory ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/not-zip-safe0000664000175000017500000000000100000000000025342 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/pbr.json0000664000175000017500000000005600000000000024573 0ustar00zuulzuul00000000000000{"git_version": "557d173", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/requires.txt0000664000175000017500000000116600000000000025520 0ustar00zuulzuul00000000000000keystoneauth1>=3.12.0 oslo.cache>=1.26.0 oslo.config>=5.2.0 oslo.context>=2.19.2 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.serialization>=2.18.0 oslo.utils>=3.33.0 pbr>=2.0.0 pycadf>=1.1.0 PyJWT>=2.4.0 python-keystoneclient>=3.20.0 requests>=2.14.2 WebOb>=1.7.1 [audit_notifications] oslo.messaging>=5.29.0 [test] hacking~=6.1.0 flake8-docstrings~=1.7.0 coverage>=4.0 cryptography>=3.0 fixtures>=3.0.0 oslotest>=3.2.0 stevedore>=1.20.0 requests-mock>=1.2.0 stestr>=2.0.0 testresources>=2.0.0 testtools>=2.2.0 python-binary-memcached>=0.29.0 python-memcached>=1.59 WebTest>=2.0.27 oslo.messaging>=5.29.0 PyJWT>=2.4.0 bandit>=1.1.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154103.0 keystonemiddleware-10.9.0/keystonemiddleware.egg-info/top_level.txt0000664000175000017500000000002300000000000025641 0ustar00zuulzuul00000000000000keystonemiddleware ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9847507 keystonemiddleware-10.9.0/releasenotes/0000775000175000017500000000000000000000000020214 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0167518 keystonemiddleware-10.9.0/releasenotes/notes/0000775000175000017500000000000000000000000021344 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023615 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/allow-expired-5ddbabcffc5678af.yaml0000664000175000017500000000335600000000000027615 0ustar00zuulzuul00000000000000--- prelude: > Fetching expired tokens when using a valid service token is now allowed. This will help with long running operations that must continue between services longer than the original expiry of the token. features: - AuthToken middleware will now allow fetching an expired token when a valid service token is present. This service token must contain any one of the roles specified in ``service_token_roles``. - Service tokens are compared against a list of possible roles for validity. This will ensure that only services are submitting tokens as an ``X-Service-Token``. For backwards compatibility, if ``service_token_roles_required`` is not set, a warning will be emitted. To enforce the check properly, set ``service_token_roles_required`` to ``True``. It currently defaults to ``False`` upgrade: - Set the ``service_token_roles`` to a list of roles that services may have. The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may apply to accept the service token. Ensure service users have one of these roles so interservice communication continues to work correctly. When verified, set the ``service_token_roles_required`` flag to ``True`` to enforce this behaviour. This will become the default setting in future releases. deprecations: - For backwards compatibility the ``service_token_roles_required`` option in ``[keystone_authtoken]`` was added. The option defaults to ``False`` and has been immediately deprecated. This will allow the current behaviour that service tokens are validated but not checked for roles to continue. The option should be set to ``True`` as soon as possible. The option will default to ``True`` in a future release. ././@PaxHeader0000000000000000000000000000021200000000000011450 xustar0000000000000000116 path=keystonemiddleware-10.9.0/releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e154.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/authprotocol-accepts-oslo-config-config-a37212b60f58e150000664000175000017500000000076000000000000033100 0ustar00zuulzuul00000000000000--- features: - > [`bug 1540022 `_] The auth_token middleware will now accept a conf setting named ``oslo_config_config``. If this is set its value must be an existing oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over ``oslo_config_project``. This feature is useful to applications that are instantiating the auth_token middleware themselves and wish to use an existing configuration. ././@PaxHeader0000000000000000000000000000020500000000000011452 xustar0000000000000000111 path=keystonemiddleware-10.9.0/releasenotes/notes/bp-enhance-oauth2-interoperability-b1a00f10887d33dd.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bp-enhance-oauth2-interoperability-b1a00f10887d33dd.yam0000664000175000017500000000047500000000000033046 0ustar00zuulzuul00000000000000--- features: - | [`blueprint enhance-oauth2-interoperability `_] Added the ability to authenticate using a system-scoped token and the ability to authenticate using a cached token to the external_oauth2_token filter. ././@PaxHeader0000000000000000000000000000020500000000000011452 xustar0000000000000000111 path=keystonemiddleware-10.9.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bp-enhance-oauth2-interoperability-dd998d4e0eafed3c.yam0000664000175000017500000000062500000000000033371 0ustar00zuulzuul00000000000000--- features: - | [`blueprint enhance-oauth2-interoperability `_] The external_oauth2_token filter has been added for accepting or denying incoming requests containing OAuth 2.0 access tokens that are obtained from an external authorization server by users through their OAuth 2.0 credentials. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bp-oauth2-client-credentials-ext-19a40c655ee43f57.yaml0000664000175000017500000000053700000000000032545 0ustar00zuulzuul00000000000000--- features: - | [`blueprint oauth2-client-credentials-ext `_] The oauth2_token filter has been added for accepting or denying incoming requests containing OAuth2.0 client credentials access tokens passed via the Authorization headers as bearer tokens. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bp-support-oauth2-mtls-2d2686c9d5b1fe1f.yaml0000664000175000017500000000062700000000000031030 0ustar00zuulzuul00000000000000--- features: - | [`blueprint support-oauth2-mtls `_] The oauth2_mtls_token filter has been added for accepting or denying incoming requests containing OAuth 2.0 certificate-bound access tokens that are obtained from keystone identity server by users through their OAuth 2.0 credentials and Mutual-TLS certificates. ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=keystonemiddleware-10.9.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-badf088c8ad584bb.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-badf088c8ad584bb.y0000664000175000017500000000047500000000000033270 0ustar00zuulzuul00000000000000--- features: - | [`spec `_] The auth_token middleware now has support for accepting or denying incoming requests based on access rules provided by users in their keystone application credentials. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1490804-87c0ff8e764945c1.yaml0000664000175000017500000000124500000000000026015 0ustar00zuulzuul00000000000000--- features: - > [`bug 1490804 `_] The auth_token middleware validates the token's audit IDs during offline token validation if the Identity server includes audit IDs in the token revocation list. security: - > [`bug 1490804 `_] [`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token revocation when the PKI or PKIZ token provider is used. The complete remediation for this vulnerability requires the corresponding fix in the Identity (keystone) project. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1544840-a534127f8663e40f.yaml0000664000175000017500000000037600000000000025722 0ustar00zuulzuul00000000000000--- features: - > [`bug 1544840 `_] Adding audit middleware specific notification related configuration to allow a different notification driver and transport for audit if needed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1583690-da67472d7afff0bf.yaml0000664000175000017500000000077200000000000026312 0ustar00zuulzuul00000000000000--- features: - > [`bug 1583690 `_] For services such as Swift, which may not be utilizing oslo_config, we need to be able to determine the project name from local config. If project name is specified in both local config and oslo_config, the one in local config will be used instead. In case project is undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so operators can take corrective actions. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1583699-dba4fe6c057e2be5.yaml0000664000175000017500000000063500000000000026312 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1583699 `_] Some service APIs (such as Swift list public containers) do not require a token. Therefore, there will be no identity or service catalog information available. In these cases, audit now fills in the default (i.e. taxonomy.UNKNOWN) for both initiator and target instead of raising an exception. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1583702-a4469dc1556878b9.yaml0000664000175000017500000000060600000000000025735 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1583702 `_] Some services such as Swift does not use Oslo (global) config. In that case, the options are conveyed via local config. This patch utilized an established pattern in auth_token middleware, which is to first look for the given option in local config, then Oslo global config. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1649735-3c68f3243e474775.yaml0000664000175000017500000000173500000000000025665 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1649735 `_] The auth_token middleware no longer attempts to retrieve the revocation list from the Keystone server. The deprecated options `revocations_cache_time` and `check_revocations_for_cached` have been removed. Keystone no longer issues PKI/PKIZ tokens and now keystonemiddleware's Support for PKI/PKIZ and associated offline validation has been removed. This includes the deprecated config options `signing_dir`, and `hash_algorithms`. upgrade: - > [`bug 1649735 `_] Keystonemiddleware no longer supports PKI/PKIZ tokens, all associated offline validation has been removed. The configuration options `signing_dir`, and `hash_algorithms` have been removed, if they still exist in your configuration(s), they are now safe to remove. Please consider utilizing the newer fernet or JWS token formats.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1677308-a2fa7de67f21cd84.yaml0000664000175000017500000000127600000000000026230 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is unmaintained, and replaces it with the ``cryptography`` library. upgrade: - | [`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` to ``cryptography``. All data will be encrypted and decrypted using identical blocksize, padding, algorithm (AES) and mode (CBC). Data previously encrypted using ``pycrypto`` can be decrypted using both ``pycrypto`` and ``cryptography``. The same is true of data encrypted using ``cryptography``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1695038-2cbedcabf8ecc057.yaml0000664000175000017500000000127200000000000026434 0ustar00zuulzuul00000000000000--- features: - > [`bug 1695038 `_] The use_oslo_messaging configuration option is added for services such as Swift, which need the audit middleware to use the local logger instead of the oslo.messaging notifier regardless of whether the oslo.messaging package is present or not. Leave this option set to its default True value to keep the previous behavior unchanged - the audit middleware will use the oslo.messaging notifier if the oslo.messaging package is present, and the local logger otherwise. Services that rely on the local logger for audit notifications must set this option to False. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1737115-fa3d41e3d3cd7177.yaml0000664000175000017500000000033500000000000026130 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1737115 `_] Last release have accidentaly make python-memcached a hard dependency, this have changed back to an optional one. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1737119-4afe548d28fbf8bb.yaml0000664000175000017500000000037200000000000026307 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1737119 `_] If the application was not using the global cfg.CONF object, the configuration was not read from the configuration file. This have been fixed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1747655-6e563d9317bb0f13.yaml0000664000175000017500000000067300000000000026007 0ustar00zuulzuul00000000000000--- fixes: - | [`bug/1747655 `_] When keystone is temporarily unavailable, keystonemiddleware correctly sends a 503 response to the HTTP client but was not identifying which service was down, leading to confusion on whether it was keystone or the service using keystonemiddleware that was unavailable. This change identifies keystone in the error response. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1762362-3d092b15c7bab3a4.yaml0000664000175000017500000000035600000000000026117 0ustar00zuulzuul00000000000000--- features: - > [`bug 1762362 `_] The value of the header "WWW-Authenticate" in a 401 (Unauthorized) response now is double quoted to follow the RFC requirement. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1766731-3b29192cfeb77964.yaml0000664000175000017500000000044300000000000026014 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1766731 `_] Keystonemiddleware now supports system scoped tokens. When a system-scoped token is parsed by auth_token middleware, it will set the ``OpenStack-System-Scope`` header accordingly. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1782404-c4e37bbc83756a89.yaml0000664000175000017500000000062500000000000026070 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1782404 `_] Keystonemiddleware incorrectly implemented an abstraction for the memcache client pool that utilized a `queue.Queue` `get` method instead of the supplied `acquire()` context manager. The `acquire()` context manager properly places the client connection back into the pool after `__exit__`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1789351-102e2e5119be38b4.yaml0000664000175000017500000000042700000000000025773 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1789351 `_] Fixed the bug that when initialize `AuthProtocol`, it'll raise "dictionary changed size during iteration" error if the input `CONF` object contains deprecated options. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1800017-0e5a9b8f62b5ca60.yaml0000664000175000017500000000042200000000000026113 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1800017 `_] Fix audit middleware service catalog parsing for the scenario where a service does not contain any endpoints. In that case, we should just skip over that service. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1803940-9a39c66014763af0.yaml0000664000175000017500000000026200000000000025711 0ustar00zuulzuul00000000000000--- features: - > [`bug 1803940 `_] Request ID and global request ID have been added to CADF notifications. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1809101-6b5088443d5970ba.yaml0000664000175000017500000000043200000000000025706 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1809101 `_] Fix req.context of Keystone audit middleware and Glance conflict with each other issue. The audit middleware now stores the admin context to req.environ['audit.context']. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug-1813739-80eae72371903119.yaml0000664000175000017500000000065500000000000025643 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1813739 `_] When admin identity endpoint is not created yet, keystonemiddleware emit EndpointNotFound exception. Even after admin identity endpoint created, auth_token middleware could not be notified of update since it does not invalidate existing auth. Add an invalidation step so that endpoint updates can be detected. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/bug_1540115-677cf5016bc46348.yaml0000664000175000017500000000051700000000000025774 0ustar00zuulzuul00000000000000--- features: - > [`bug 1540115 `_] Optional dependencies can now be installed using `extras`. To install audit related libraries, use ``pip install keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware documentation for further information. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/change-default-identity-endpoint-fab39579255c31bb.yaml0000664000175000017500000000076000000000000032765 0ustar00zuulzuul00000000000000--- prelude: > Since the removal of the Identity API v2 Keystone no longer has any special functionality that requires using the admin endpoint for it. So this release changes the default endpoint being used from ``admin`` to ``internal``, allowing deployments to work without an admin endpoint. upgrade: - | [`bug 1830002 `_] The default Identity endpoint has been changed from ``admin`` to ``internal``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/delay_auth_instead_of_503-f9b46bf4fbc11455.yaml0000664000175000017500000000064500000000000031435 0ustar00zuulzuul00000000000000--- fixes: - | When ``delay_auth_decision`` is enabled and a Keystone failure prevents a final decision about whether a token is valid or invalid, it will be marked invalid and the application will be responsible for a final auth decision. This is similar to what happens when a token is confirmed *not* valid. This allows a Keystone outage to only affect Keystone users in a multi-auth system. ././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=keystonemiddleware-10.9.0/releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/deprecate-caching-tokens-in-process-a412b0f1dea84cb9.ya0000664000175000017500000000146700000000000033160 0ustar00zuulzuul00000000000000--- deprecations: - > With the release of 4.2.0 of keystonemiddleware we no longer recommend using the in-process token cache. In-process caching may result in inconsistent validation, poor UX and race conditions. It is recommended that the `memcached_servers` option is set in the `keystone_authtoken` configuration section of the various services (e.g. nova, glance, ...) with the endpoint of running memcached server(s). When the feature is removed, not setting the `memcached_servers` option will cause keystone to validate tokens more frequently, increasing load. In production, use of caching is highly recommended. This feature is deprecated as of 4.2.0 and is targeted for removal in keystonemiddleware 5.0.0 or in the `O` development cycle, whichever is later. ././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000121 path=keystonemiddleware-10.9.0/releasenotes/notes/deprecate-eventlet-unsafe-memcacheclientpool-f8b4a6733513d73e.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/deprecate-eventlet-unsafe-memcacheclientpool-f8b4a673350000664000175000017500000000213200000000000033467 0ustar00zuulzuul00000000000000--- deprecations: - | We no longer recommend using the eventlet unsafe keystonemiddleware's memcacheclientpool. This implementation may result in growing connections to memcached. It is recommended that the ``memcache_use_advanced_pool`` option is set to ``True`` in the ``keystone_authtoken`` configuration section of the various services (e.g. nova, glance, ...) when memcached is used for token cache. upgrade: - | [`bug 1892852 `_] [`bug 1888394 `_] [`bug 1883659 `_] Keystonemiddleware now using eventlet-safe implementation of ``MemcacheClientPool`` from oslo.cache's library by default. The ``keystonemiddleware`` implementation is now deprecated. For backwards compatibility, the ``[keystone_authtoken] memcache_use_advanced_pool`` option can be set to ``False`` config files of the various services (e.g. nova, glance, ...) when memcached is used for token cache. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/drop-py-2-7-6655f421a9cac0a2.yaml0000664000175000017500000000031300000000000026326 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of keystonemiddleware to support python 2.7 is OpenStack Train. The minimum version of Python now supported is Python 3.6.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/drop-python-3-6-and-3-7-c407d5898c5eafec.yaml0000664000175000017500000000020100000000000030444 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/ec2-v2-removal-6a886210cbc9d3e9.yaml0000664000175000017500000000043700000000000027120 0ustar00zuulzuul00000000000000--- other: - | [`bug 1845539 `_] The ec2 'url' config option now defaults to https://localhost:5000/v3/ec2tokens with the removal of ec2 v2.0 support. Keystonemiddleware no longer supports ec2tokens using the v2.0 API. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/enable-sasl-protocol-946149083f604216.yaml0000664000175000017500000000042600000000000030121 0ustar00zuulzuul00000000000000--- features: - | Add the feature to support SASL for keystonemiddleware to improve the security of authority. memcache_sasl_enabled: enable the SASL option or not. memcache_username: the user name for the SASL memcache_password: the user password for SASL././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=keystonemiddleware-10.9.0/releasenotes/notes/fix-audit-no-service-endpoint-ports-72b2009d631dcf19.yaml 22 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/fix-audit-no-service-endpoint-ports-72b2009d631dcf19.ya0000664000175000017500000000036600000000000032761 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1797584 `_] Fixed a bug where the audit code would select the wrong target service if the OpenStack service endpoints were not using unique TCP ports. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/fix-cache-data-corrupted-issue-d1bd546625690581.yaml0000664000175000017500000000032500000000000032122 0ustar00zuulzuul00000000000000--- fixes: - | In situation of encryption using memcached. Its possible that data in memcached becomes un-decryptable. The previous implementation of token cache was not correctly handling the case. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/interface-option-ed551d2a3162668d.yaml0000664000175000017500000000070400000000000027633 0ustar00zuulzuul00000000000000--- features: - | [`bug 1830002 `_] In order to allow an installation to work without deploying an admin Identity endpoint, a new option `interface` has been added, allowing select the Identity endpoint that is being used when verifying auth tokens. It defaults to `admin` in order to replicate the old behaviour, but may be set to `public` or `internal` as needed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/ksm_4.1.0-3cd78446d8e63616.yaml0000664000175000017500000000064100000000000025636 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1523311 `_] Do not list deprecated opts in sample config. - > [`bug 1333951 `_] Add support for parsing AWS v4 for ec2. - > [`bug 1423973 `_] Use oslo.config choices for config options. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/remove-py38-438c67d4d2da02a3.yaml0000664000175000017500000000016600000000000026536 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.8 support was dropped. The minimum version of Python now supported is Python 3.9. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/remove_kwargs_to_fetch_token-20e3451ed192ab6a.yaml0000664000175000017500000000032200000000000032353 0ustar00zuulzuul00000000000000--- other: - > The ``kwargs_to_fetch_token`` setting was removed from the ``BaseAuthProtocol`` class. Implementations of auth_token now assume kwargs will be passed to the ``fetch_token`` method. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/removed-as-of-ussuri-4e1ea485ba8801c9.yaml0000664000175000017500000000042700000000000030445 0ustar00zuulzuul00000000000000--- upgrade: - | [`bug 1845539 `_] [`bug 1777177 `_] keystonemiddleware no longer supports the keystone v2.0 api, all associated functionality has been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/rename-auth-uri-d223d883f5898aee.yaml0000664000175000017500000000065600000000000027500 0ustar00zuulzuul00000000000000--- deprecations: - | The auth_uri parameter of keystone_authtoken is deprecated in favor of www_authenticate_uri. The auth_uri option was often confused with the auth_url parameter of the keystoneauth plugin, which was also effectively always required. The parameter refers to the WWW-Authenticate header that is returned when the user needs to be redirected to the Identity service for authentication. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/s3token_auth_uri-490c1287d90b9df7.yaml0000664000175000017500000000052700000000000027672 0ustar00zuulzuul00000000000000--- features: - A new configuration option for the s3token middleware called auth_uri can be used to set the URI to be used for authentication. This replaces auth_host, auth_port, and auth_protocol. deprecations: - The auth_host, auth_port, and auth_protocol configuration options to the s3token middleware are now deprecated. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml0000664000175000017500000000036000000000000031170 0ustar00zuulzuul00000000000000--- prelude: > - Add the `X_IS_ADMIN_PROJECT` header. features: - Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the string value of 'True' or 'False' and can be used to enforce admin project policies. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/0000775000175000017500000000000000000000000021514 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000022765 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022766 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022766 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000022767 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/_static/0000775000175000017500000000000000000000000023142 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025413 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023651 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000026122 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/conf.py0000664000175000017500000002121000000000000023007 0ustar00zuulzuul00000000000000# 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. # keystonemiddleware Release Notes documentation build configuration file, # created by sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # 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. copyright = u'2015, Keystone Developers' # Release notes do not need a version number in the title, they # cover multiple releases. # 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 = 'native' # 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 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 = 'keystonemiddlewareReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # 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', 'keystonemiddlewareReleaseNotes.tex', u'keystonemiddleware Release Notes Documentation', u'Keystone 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', 'keystonemiddlewarereleasenotes', u'keystonemiddleware Release Notes Documentation', [u'Keystone 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', 'keystonemiddlewareReleaseNotes', u'keystonemiddleware Release Notes Documentation', u'Keystone Developers', 'keystonemiddlewareReleaseNotes', 'Middleware for the OpenStack Identity service.', '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/'] # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/keystonemiddleware' openstackdocs_bug_project = 'keystonemiddleware' openstackdocs_bug_tag = '' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/index.rst0000664000175000017500000000047700000000000023365 0ustar00zuulzuul00000000000000================================== keystonemiddleware Release Notes ================================== .. toctree:: :maxdepth: 1 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9847507 keystonemiddleware-10.9.0/releasenotes/source/locale/0000775000175000017500000000000000000000000022753 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9847507 keystonemiddleware-10.9.0/releasenotes/source/locale/en_GB/0000775000175000017500000000000000000000000023725 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025512 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000012007600000000000030551 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata # Andi Chandler , 2019. #zanata # Andi Chandler , 2020. #zanata # Andi Chandler , 2022. #zanata # Andi Chandler , 2023. #zanata # Andi Chandler , 2024. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-15 16:57+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2024-10-01 01:04+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "10.1.0" msgstr "10.1.0" msgid "10.3.0" msgstr "10.3.0" msgid "10.5.0" msgstr "10.5.0" msgid "2023.1 Series Release Notes" msgstr "2023.1 Series Release Notes" msgid "2023.2 Series Release Notes" msgstr "2023.2 Series Release Notes" msgid "2024.1 Series Release Notes" msgstr "2024.1 Series Release Notes" msgid "2024.2 Series Release Notes" msgstr "2024.2 Series Release Notes" msgid "4.1.0" msgstr "4.1.0" msgid "4.12.0" msgstr "4.12.0" msgid "4.16.0" msgstr "4.16.0" msgid "4.17.1" msgstr "4.17.1" msgid "4.17.1-3" msgstr "4.17.1-3" msgid "4.18.0" msgstr "4.18.0" msgid "4.2.0" msgstr "4.2.0" msgid "4.20.0" msgstr "4.20.0" msgid "4.22.0" msgstr "4.22.0" msgid "4.22.0-2" msgstr "4.22.0-2" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "5.0.0" msgstr "5.0.0" msgid "5.1.0" msgstr "5.1.0" msgid "5.2.0" msgstr "5.2.0" msgid "5.2.1" msgstr "5.2.1" msgid "5.2.2" msgstr "5.2.2" msgid "5.3.0" msgstr "5.3.0" msgid "6.0.0" msgstr "6.0.0" msgid "6.0.1" msgstr "6.0.1" msgid "6.1.0" msgstr "6.1.0" msgid "7.0.0" msgstr "7.0.0" msgid "8.0.0" msgstr "8.0.0" msgid "9.0.0" msgstr "9.0.0" msgid "9.1.0" msgstr "9.1.0" msgid "9.3.0" msgstr "9.3.0" msgid "" "A new configuration option for the s3token middleware called auth_uri can be " "used to set the URI to be used for authentication. This replaces auth_host, " "auth_port, and auth_protocol." msgstr "" "A new configuration option for the s3token middleware called auth_uri can be " "used to set the URI to be used for authentication. This replaces auth_host, " "auth_port, and auth_protocol." msgid "Add the `X_IS_ADMIN_PROJECT` header." msgstr "Add the `X_IS_ADMIN_PROJECT` header." msgid "" "Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the " "string value of 'True' or 'False' and can be used to enforce admin project " "policies." msgstr "" "Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has the " "string value of 'True' or 'False' and can be used to enforce admin project " "policies." msgid "" "AuthToken middleware will now allow fetching an expired token when a valid " "service token is present. This service token must contain any one of the " "roles specified in ``service_token_roles``." msgstr "" "AuthToken middleware will now allow fetching an expired token when a valid " "service token is present. This service token must contain any one of the " "roles specified in ``service_token_roles``." msgid "Bug Fixes" msgstr "Bug Fixes" msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Deprecation Notes" msgstr "Deprecation Notes" msgid "" "Fetching expired tokens when using a valid service token is now allowed. " "This will help with long running operations that must continue between " "services longer than the original expiry of the token." msgstr "" "Fetching expired tokens when using a valid service token is now allowed. " "This will help with long running operations that must continue between " "services longer than the original expiry of the token." msgid "" "For backwards compatibility the ``service_token_roles_required`` option in " "``[keystone_authtoken]`` was added. The option defaults to ``False`` and has " "been immediately deprecated. This will allow the current behaviour that " "service tokens are validated but not checked for roles to continue. The " "option should be set to ``True`` as soon as possible. The option will " "default to ``True`` in a future release." msgstr "" "For backwards compatibility the ``service_token_roles_required`` option in " "``[keystone_authtoken]`` was added. The option defaults to ``False`` and has " "been immediately deprecated. This will allow the current behaviour that " "service tokens are validated but not checked for roles to continue. The " "option should be set to ``True`` as soon as possible. The option will " "default to ``True`` in a future release." msgid "" "In situation of encryption using memcached. Its possible that data in " "memcached becomes un-decryptable. The previous implementation of token cache " "was not correctly handling the case." msgstr "" "In a situation of encryption using memcached. It is possible that data in " "memcached becomes un-decryptable. The previous implementation of token cache " "was not correctly handling the case." msgid "" "It is recommended that the ``memcache_use_advanced_pool`` option is set to " "``True`` in the ``keystone_authtoken`` configuration section of the various " "services (e.g. nova, glance, ...) when memcached is used for token cache." msgstr "" "It is recommended that the ``memcache_use_advanced_pool`` option is set to " "``True`` in the ``keystone_authtoken`` configuration section of the various " "services (e.g. nova, glance, ...) when Memcached is used for token cache." msgid "Mitaka Series Release Notes" msgstr "Mitaka Series Release Notes" msgid "New Features" msgstr "New Features" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" 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 "Prelude" msgstr "Prelude" msgid "" "Python 2.7 support has been dropped. Last release of keystonemiddleware to " "support python 2.7 is OpenStack Train. The minimum version of Python now " "supported is Python 3.6." msgstr "" "Python 2.7 support has been dropped. Last release of keystonemiddleware to " "support Python 2.7 is OpenStack Train. The minimum version of Python now " "supported is Python 3.6." msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Security Issues" msgstr "Security Issues" msgid "" "Service tokens are compared against a list of possible roles for validity. " "This will ensure that only services are submitting tokens as an ``X-Service-" "Token``. For backwards compatibility, if ``service_token_roles_required`` is " "not set, a warning will be emitted. To enforce the check properly, set " "``service_token_roles_required`` to ``True``. It currently defaults to " "``False``" msgstr "" "Service tokens are compared against a list of possible roles for validity. " "This will ensure that only services are submitting tokens as an ``X-Service-" "Token``. For backwards compatibility, if ``service_token_roles_required`` is " "not set, a warning will be emitted. To enforce the check properly, set " "``service_token_roles_required`` to ``True``. It currently defaults to " "``False``" msgid "" "Set the ``service_token_roles`` to a list of roles that services may have. " "The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may " "apply to accept the service token. Ensure service users have one of these " "roles so interservice communication continues to work correctly. When " "verified, set the ``service_token_roles_required`` flag to ``True`` to " "enforce this behaviour. This will become the default setting in future " "releases." msgstr "" "Set the ``service_token_roles`` to a list of roles that services may have. " "The likely list is ``service`` or ``admin``. Any ``service_token_roles`` may " "apply to accept the service token. Ensure service users have one of these " "roles so interservice communication continues to work correctly. When " "verified, set the ``service_token_roles_required`` flag to ``True`` to " "enforce this behaviour. This will become the default setting in future " "releases." msgid "" "Since the removal of the Identity API v2 Keystone no longer has any special " "functionality that requires using the admin endpoint for it. So this release " "changes the default endpoint being used from ``admin`` to ``internal``, " "allowing deployments to work without an admin endpoint." msgstr "" "Since the removal of the Identity API v2 Keystone no longer has any special " "functionality that requires using the admin endpoint for it. So this release " "changes the default endpoint being used from ``admin`` to ``internal``, " "allowing deployments to work without an admin endpoint." msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" msgid "" "The ``kwargs_to_fetch_token`` setting was removed from the " "``BaseAuthProtocol`` class. Implementations of auth_token now assume kwargs " "will be passed to the ``fetch_token`` method." msgstr "" "The ``kwargs_to_fetch_token`` setting was removed from the " "``BaseAuthProtocol`` class. Implementations of auth_token now assume kwargs " "will be passed to the ``fetch_token`` method." msgid "" "The auth_host, auth_port, and auth_protocol configuration options to the " "s3token middleware are now deprecated." msgstr "" "The auth_host, auth_port, and auth_protocol configuration options to the " "s3token middleware are now deprecated." msgid "" "The auth_uri parameter of keystone_authtoken is deprecated in favor of " "www_authenticate_uri. The auth_uri option was often confused with the " "auth_url parameter of the keystoneauth plugin, which was also effectively " "always required. The parameter refers to the WWW-Authenticate header that is " "returned when the user needs to be redirected to the Identity service for " "authentication." msgstr "" "The auth_uri parameter of keystone_authtoken is deprecated in favour of " "www_authenticate_uri. The auth_uri option was often confused with the " "auth_url parameter of the keystoneauth plugin, which was also effectively " "always required. The parameter refers to the WWW-Authenticate header that is " "returned when the user needs to be redirected to the Identity service for " "authentication." msgid "" "The lower constraint for python-memcached must be raised to version 1.58 in " "order to work with Python 3.4 and above." msgstr "" "The lower constraint for python-memcached must be raised to version 1.58 in " "order to work with Python 3.4 and above." msgid "Train Series Release Notes" msgstr "Train Series Release Notes" msgid "Upgrade Notes" msgstr "Upgrade Notes" msgid "Ussuri Series Release Notes" msgstr "Ussuri Series Release Notes" msgid "Victoria Series Release Notes" msgstr "Victoria Series Release Notes" msgid "Wallaby Series Release Notes" msgstr "Wallaby Series Release Notes" msgid "" "We no longer recommend using the eventlet unsafe keystonemiddleware's " "memcacheclientpool. This implementation may result in growing connections to " "memcached." msgstr "" "We no longer recommend using the eventlet unsafe keystonemiddleware's " "memcacheclientpool. This implementation may result in growing connections to " "Memcached." msgid "" "When ``delay_auth_decision`` is enabled and a Keystone failure prevents a " "final decision about whether a token is valid or invalid, it will be marked " "invalid and the application will be responsible for a final auth decision. " "This is similar to what happens when a token is confirmed *not* valid. This " "allows a Keystone outage to only affect Keystone users in a multi-auth " "system." msgstr "" "When ``delay_auth_decision`` is enabled and a Keystone failure prevents a " "final decision about whether a token is valid or invalid, it will be marked " "invalid and the application will be responsible for a final auth decision. " "This is similar to what happens when a token is confirmed *not* valid. This " "allows a Keystone outage to only affect Keystone users in a multi-auth " "system." msgid "" "With the release of 4.2.0 of keystonemiddleware we no longer recommend using " "the in-process token cache. In-process caching may result in inconsistent " "validation, poor UX and race conditions. It is recommended that the " "`memcached_servers` option is set in the `keystone_authtoken` configuration " "section of the various services (e.g. nova, glance, ...) with the endpoint " "of running memcached server(s). When the feature is removed, not setting the " "`memcached_servers` option will cause keystone to validate tokens more " "frequently, increasing load. In production, use of caching is highly " "recommended. This feature is deprecated as of 4.2.0 and is targeted for " "removal in keystonemiddleware 5.0.0 or in the `O` development cycle, " "whichever is later." msgstr "" "With the release of 4.2.0 of keystonemiddleware we no longer recommend using " "the in-process token cache. In-process caching may result in inconsistent " "validation, poor UX and race conditions. It is recommended that the " "`memcached_servers` option is set in the `keystone_authtoken` configuration " "section of the various services (e.g. nova, glance, ...) with the endpoint " "of running memcached server(s). When the feature is removed, not setting the " "`memcached_servers` option will cause keystone to validate tokens more " "frequently, increasing load. In production, use of caching is highly " "recommended. This feature is deprecated as of 4.2.0 and is targeted for " "removal in keystonemiddleware 5.0.0 or in the `O` development cycle, " "whichever is later." msgid "Xena Series Release Notes" msgstr "Xena Series Release Notes" msgid "Yoga Series Release Notes" msgstr "Yoga Series Release Notes" msgid "Zed Series Release Notes" msgstr "Zed Series Release Notes" msgid "" "[`blueprint enhance-oauth2-interoperability `_] Added the ability to " "authenticate using a system-scoped token and the ability to authenticate " "using a cached token to the external_oauth2_token filter." msgstr "" "[`blueprint enhance-oauth2-interoperability `_] Added the ability to " "authenticate using a system-scoped token and the ability to authenticate " "using a cached token to the external_oauth2_token filter." msgid "" "[`blueprint enhance-oauth2-interoperability `_] The " "external_oauth2_token filter has been added for accepting or denying " "incoming requests containing OAuth 2.0 access tokens that are obtained from " "an external authorization server by users through their OAuth 2.0 " "credentials." msgstr "" "[`blueprint enhance-oauth2-interoperability `_] The " "external_oauth2_token filter has been added for accepting or denying " "incoming requests containing OAuth 2.0 access tokens that are obtained from " "an external authorization server by users through their OAuth 2.0 " "credentials." msgid "" "[`blueprint oauth2-client-credentials-ext `_] The oauth2_token filter has " "been added for accepting or denying incoming requests containing OAuth2.0 " "client credentials access tokens passed via the Authorization headers as " "bearer tokens." msgstr "" "[`blueprint oauth2-client-credentials-ext `_] The oauth2_token filter has " "been added for accepting or denying incoming requests containing OAuth2.0 " "client credentials access tokens passed via the Authorisation headers as " "bearer tokens." msgid "" "[`blueprint support-oauth2-mtls `_] The oauth2_mtls_token filter has been added " "for accepting or denying incoming requests containing OAuth 2.0 certificate-" "bound access tokens that are obtained from keystone identity server by users " "through their OAuth 2.0 credentials and Mutual-TLS certificates." msgstr "" "[`blueprint support-oauth2-mtls `_] The oauth2_mtls_token filter has been added " "for accepting or denying incoming requests containing OAuth 2.0 certificate-" "bound access tokens that are obtained from Keystone identity server by users " "through their OAuth 2.0 credentials and Mutual-TLS certificates." msgid "" "[`bug 1333951 `_] Add support for parsing AWS v4 for ec2." msgstr "" "[`bug 1333951 `_] Add support for parsing AWS v4 for ec2." msgid "" "[`bug 1423973 `_] Use oslo.config choices for config options." msgstr "" "[`bug 1423973 `_] Use oslo.config choices for config options." msgid "" "[`bug 1490804 `_] The " "auth_token middleware validates the token's audit IDs during offline token " "validation if the Identity server includes audit IDs in the token revocation " "list." msgstr "" "[`bug 1490804 `_] The " "auth_token middleware validates the token's audit IDs during offline token " "validation if the Identity server includes audit IDs in the token revocation " "list." msgid "" "[`bug 1490804 `_] " "[`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token " "revocation when the PKI or PKIZ token provider is used. The complete " "remediation for this vulnerability requires the corresponding fix in the " "Identity (keystone) project." msgstr "" "[`bug 1490804 `_] " "[`CVE-2015-7546 `_] A bug is fixed where an attacker could avoid token " "revocation when the PKI or PKIZ token provider is used. The complete " "remediation for this vulnerability requires the corresponding fix in the " "Identity (Keystone) project." msgid "" "[`bug 1523311 `_] Do not list deprecated opts in sample config." msgstr "" "[`bug 1523311 `_] Do not list deprecated opts in sample config." msgid "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``olso_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgstr "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``olso_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgid "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgstr "" "[`bug 1540022 `_] The auth_token middleware will now accept a conf setting " "named ``oslo_config_config``. If this is set its value must be an existing " "oslo_config `ConfigOpts`. ``oslo_config_config`` takes precedence over " "``oslo_config_project``. This feature is useful to applications that are " "instantiating the auth_token middleware themselves and wish to use an " "existing configuration." msgid "" "[`bug 1540115 `_] Optional dependencies can now be installed using `extras`. " "To install audit related libraries, use ``pip install " "keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware " "documentation for further information." msgstr "" "[`bug 1540115 `_] Optional dependencies can now be installed using `extras`. " "To install audit related libraries, use ``pip install " "keystonemiddleware[audit_nofications]``. Refer to keystonemiddleware " "documentation for further information." msgid "" "[`bug 1544840 `_] Adding audit middleware specific notification related " "configuration to allow a different notification driver and transport for " "audit if needed." msgstr "" "[`bug 1544840 `_] Adding audit middleware specific notification related " "configuration to allow a different notification driver and transport for " "audit if needed." msgid "" "[`bug 1583690 `_] For services such as Swift, which may not be utilizing " "oslo_config, we need to be able to determine the project name from local " "config. If project name is specified in both local config and oslo_config, " "the one in local config will be used instead. In case project is " "undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so " "operators can take corrective actions." msgstr "" "[`bug 1583690 `_] For services such as Swift, which may not be utilising " "oslo_config, we need to be able to determine the project name from local " "config. If project name is specified in both local config and oslo_config, " "the one in local config will be used instead. In case project is " "undetermined (i.e. not set), we use taxonomy.UNKNOWN as an indicator so " "operators can take corrective actions." msgid "" "[`bug 1583699 `_] Some service APIs (such as Swift list public containers) do " "not require a token. Therefore, there will be no identity or service catalog " "information available. In these cases, audit now fills in the default (i.e. " "taxonomy.UNKNOWN) for both initiator and target instead of raising an " "exception." msgstr "" "[`bug 1583699 `_] Some service APIs (such as Swift list public containers) do " "not require a token. Therefore, there will be no identity or service " "catalogue information available. In these cases, audit now fills in the " "default (i.e. taxonomy.UNKNOWN) for both initiator and target instead of " "raising an exception." msgid "" "[`bug 1583702 `_] Some services such as Swift does not use Oslo (global) " "config. In that case, the options are conveyed via local config. This patch " "utilized an established pattern in auth_token middleware, which is to first " "look for the given option in local config, then Oslo global config." msgstr "" "[`bug 1583702 `_] Some services such as Swift does not use Oslo (global) " "config. In that case, the options are conveyed via local config. This patch " "utilized an established pattern in auth_token middleware, which is to first " "look for the given option in local config, then Oslo global config." msgid "" "[`bug 1649735 `_] " "Keystonemiddleware no longer supports PKI/PKIZ tokens, all associated " "offline validation has been removed. The configuration options " "`signing_dir`, and `hash_algorithms` have been removed, if they still exist " "in your configuration(s), they are now safe to remove. Please consider " "utilizing the newer fernet or JWS token formats." msgstr "" "[`bug 1649735 `_] " "Keystonemiddleware no longer supports PKI/PKIZ tokens, all associated " "offline validation has been removed. The configuration options " "`signing_dir`, and `hash_algorithms` have been removed, if they still exist " "in your configuration(s), they are now safe to remove. Please consider " "utilising the newer Fernet or JWS token formats." msgid "" "[`bug 1649735 `_] The " "auth_token middleware no longer attempts to retrieve the revocation list " "from the Keystone server. The deprecated options " "`check_revocations_for_cached` and `check_revocations_for_cached` have been " "removed." msgstr "" "[`bug 1649735 `_] The " "auth_token middleware no longer attempts to retrieve the revocation list " "from the Keystone server. The deprecated options " "`check_revocations_for_cached` and `check_revocations_for_cached` have been " "removed." msgid "" "[`bug 1649735 `_] The " "auth_token middleware no longer attempts to retrieve the revocation list " "from the Keystone server. The deprecated options `revocations_cache_time` " "and `check_revocations_for_cached` have been removed. Keystone no longer " "issues PKI/PKIZ tokens and now keystonemiddleware's Support for PKI/PKIZ and " "associated offline validation has been removed. This includes the deprecated " "config options `signing_dir`, and `hash_algorithms`." msgstr "" "[`bug 1649735 `_] The " "auth_token middleware no longer attempts to retrieve the revocation list " "from the Keystone server. The deprecated options `revocations_cache_time` " "and `check_revocations_for_cached` have been removed. Keystone no longer " "issues PKI/PKIZ tokens and now keystonemiddleware's Support for PKI/PKIZ and " "associated offline validation has been removed. This includes the deprecated " "config options `signing_dir`, and `hash_algorithms`." msgid "" "[`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is " "unmaintained, and replaces it with the ``cryptography`` library." msgstr "" "[`bug 1677308 `_] Removes ``pycrypto`` dependency as the library is " "unmaintained, and replaces it with the ``cryptography`` library." msgid "" "[`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` " "to ``cryptography``. All data will be encrypted and decrypted using " "identical blocksize, padding, algorithm (AES) and mode (CBC). Data " "previously encrypted using ``pycrypto`` can be decrypted using both " "``pycrypto`` and ``cryptography``. The same is true of data encrypted using " "``cryptography``." msgstr "" "[`bug 1677308 `_] There is no upgrade impact when switching from ``pycrypto`` " "to ``cryptography``. All data will be encrypted and decrypted using " "identical blocksize, padding, algorithm (AES) and mode (CBC). Data " "previously encrypted using ``pycrypto`` can be decrypted using both " "``pycrypto`` and ``cryptography``. The same is true of data encrypted using " "``cryptography``." msgid "" "[`bug 1695038 `_] The use_oslo_messaging configuration option is added for " "services such as Swift, which need the audit middleware to use the local " "logger instead of the oslo.messaging notifier regardless of whether the oslo." "messaging package is present or not. Leave this option set to its default " "True value to keep the previous behavior unchanged - the audit middleware " "will use the oslo.messaging notifier if the oslo.messaging package is " "present, and the local logger otherwise. Services that rely on the local " "logger for audit notifications must set this option to False." msgstr "" "[`bug 1695038 `_] The use_oslo_messaging configuration option is added for " "services such as Swift, which need the audit middleware to use the local " "logger instead of the oslo.messaging notifier regardless of whether the oslo." "messaging package is present or not. Leave this option set to its default " "True value to keep the previous behaviour unchanged - the audit middleware " "will use the oslo.messaging notifier if the oslo.messaging package is " "present, and the local logger otherwise. Services that rely on the local " "logger for audit notifications must set this option to False." msgid "" "[`bug 1737115 `_] Last release have accidentaly make python-memcached a hard " "dependency, this have changed back to an optional one." msgstr "" "[`bug 1737115 `_] Last release had accidentally made python-memcached a hard " "dependency, this has changed it back to an optional one." msgid "" "[`bug 1737119 `_] If the application was not using the global cfg.CONF " "object, the configuration was not read from the configuration file. This " "have been fixed." msgstr "" "[`bug 1737119 `_] If the application was not using the global cfg.CONF " "object, the configuration was not read from the configuration file. This " "have been fixed." msgid "" "[`bug 1762362 `_] The value of the header \"WWW-Authenticate\" in a 401 " "(Unauthorized) response now is double quoted to follow the RFC requirement." msgstr "" "[`bug 1762362 `_] The value of the header \"WWW-Authenticate\" in a 401 " "(Unauthorised) response now is double quoted to follow the RFC requirement." msgid "" "[`bug 1766731 `_] Keystonemiddleware now supports system scoped tokens. When " "a system-scoped token is parsed by auth_token middleware, it will set the " "``OpenStack-System-Scope`` header accordingly." msgstr "" "[`bug 1766731 `_] Keystonemiddleware now supports system scoped tokens. When " "a system-scoped token is parsed by auth_token middleware, it will set the " "``OpenStack-System-Scope`` header accordingly." msgid "" "[`bug 1782404 `_] Keystonemiddleware incorrectly implemented an abstraction " "for the memcache client pool that utilized a `queue.Queue` `get` method " "instead of the supplied `acquire()` context manager. The `acquire()` context " "manager properly places the client connection back into the pool after " "`__exit__`." msgstr "" "[`bug 1782404 `_] Keystonemiddleware incorrectly implemented an abstraction " "for the memcache client pool that utilized a `queue.Queue` `get` method " "instead of the supplied `acquire()` context manager. The `acquire()` context " "manager properly places the client connection back into the pool after " "`__exit__`." msgid "" "[`bug 1789351 `_] Fixed the bug that when initialize `AuthProtocol`, it'll " "raise \"dictionary changed size during iteration\" error if the input `CONF` " "object contains deprecated options." msgstr "" "[`bug 1789351 `_] Fixed the bug that when initialize `AuthProtocol`, it'll " "raise \"dictionary changed size during iteration\" error if the input `CONF` " "object contains deprecated options." msgid "" "[`bug 1797584 `_] Fixed a bug where the audit code would select the wrong " "target service if the OpenStack service endpoints were not using unique TCP " "ports." msgstr "" "[`bug 1797584 `_] Fixed a bug where the audit code would select the wrong " "target service if the OpenStack service endpoints were not using unique TCP " "ports." msgid "" "[`bug 1800017 `_] Fix audit middleware service catalog parsing for the " "scenario where a service does not contain any endpoints. In that case, we " "should just skip over that service." msgstr "" "[`bug 1800017 `_] Fix audit middleware service catalog parsing for the " "scenario where a service does not contain any endpoints. In that case, we " "should just skip over that service." msgid "" "[`bug 1803940 `_] Request ID and global request ID have been added to CADF " "notifications." msgstr "" "[`bug 1803940 `_] Request ID and global request ID have been added to CADF " "notifications." msgid "" "[`bug 1809101 `_] Fix req.context of Keystone audit middleware and Glance " "conflict with each other issue. The audit middleware now stores the admin " "context to req.environ['audit.context']." msgstr "" "[`bug 1809101 `_] Fix req.context of Keystone audit middleware and Glance " "conflict with each other issue. The audit middleware now stores the admin " "context to req.environ['audit.context']." msgid "" "[`bug 1813739 `_] When admin identity endpoint is not created yet, " "keystonemiddleware emit EndpointNotFound exception. Even after admin " "identity endpoint created, auth_token middleware could not be notified of " "update since it does not invalidate existing auth. Add an invalidation step " "so that endpoint updates can be detected." msgstr "" "[`bug 1813739 `_] When admin identity endpoint is not created yet, " "keystonemiddleware emit EndpointNotFound exception. Even after admin " "identity endpoint created, auth_token middleware could not be notified of " "update since it does not invalidate existing auth. Add an invalidation step " "so that endpoint updates can be detected." msgid "" "[`bug 1830002 `_] In order to allow an installation to work without deploying " "an admin Identity endpoint, a new option `interface` has been added, " "allowing select the Identity endpoint that is being used when verifying auth " "tokens. It defaults to `admin` in order to replicate the old behaviour, but " "may be set to `public` or `internal` as needed." msgstr "" "[`bug 1830002 `_] In order to allow an installation to work without deploying " "an admin Identity endpoint, a new option `interface` has been added, " "allowing select the Identity endpoint that is being used when verifying auth " "tokens. It defaults to `admin` in order to replicate the old behaviour, but " "may be set to `public` or `internal` as needed." msgid "" "[`bug 1830002 `_] The default Identity endpoint has been changed from " "``admin`` to ``internal``." msgstr "" "[`bug 1830002 `_] The default Identity endpoint has been changed from " "``admin`` to ``internal``." msgid "" "[`bug 1845539 `_] The ec2 " "'url' config option now defaults to https://localhost:5000/v3/ec2tokens with " "the removal of ec2 v2.0 support. Keystonemiddleware no longer supports " "ec2tokens using the v2.0 API." msgstr "" "[`bug 1845539 `_] The ec2 " "'url' config option now defaults to https://localhost:5000/v3/ec2tokens with " "the removal of ec2 v2.0 support. Keystonemiddleware no longer supports " "ec2tokens using the v2.0 API." msgid "" "[`bug 1845539 `_] [`bug " "1777177 `_] " "keystonemiddleware no longer supports the keystone v2.0 api, all associated " "functionality has been removed." msgstr "" "[`bug 1845539 `_] [`bug " "1777177 `_] " "keystonemiddleware no longer supports the Keystone v2.0 api, all associated " "functionality has been removed." msgid "" "[`bug 1892852 `_] [`bug 1888394 `_] [`bug 1883659 `_] Keystonemiddleware now using eventlet-" "safe implementation of ``MemcacheClientPool`` from oslo.cache's library by " "default. The ``keystonemiddleware`` implementation is now deprecated. For " "backwards compatibility, the ``[keystone_authtoken] " "memcache_use_advanced_pool`` option can be set to ``False`` config files of " "the various services (e.g. nova, glance, ...) when memcached is used for " "token cache." msgstr "" "[`bug 1892852 `_] [`bug 1888394 `_] [`bug 1883659 `_] Keystonemiddleware now using eventlet-" "safe implementation of ``MemcacheClientPool`` from oslo.cache's library by " "default. The ``keystonemiddleware`` implementation is now deprecated. For " "backwards compatibility, the ``[keystone_authtoken] " "memcache_use_advanced_pool`` option can be set to ``False`` config files of " "the various services (e.g. nova, glance, ...) when Memcached is used for " "token cache." msgid "" "[`bug/1747655 `_] When keystone is temporarily unavailable, " "keystonemiddleware correctly sends a 503 response to the HTTP client but was " "not identifying which service was down, leading to confusion on whether it " "was keystone or the service using keystonemiddleware that was unavailable. " "This change identifies keystone in the error response." msgstr "" "[`bug/1747655 `_] When keystone is temporarily unavailable, " "keystonemiddleware correctly sends a 503 response to the HTTP client but was " "not identifying which service was down, leading to confusion on whether it " "was keystone or the service using keystonemiddleware that was unavailable. " "This change identifies keystone in the error response." msgid "" "[`spec `_] The auth_token middleware now has " "support for accepting or denying incoming requests based on access rules " "provided by users in their keystone application credentials." msgstr "" "[`spec `_] The auth_token middleware now has " "support for accepting or denying incoming requests based on access rules " "provided by users in their Keystone application credentials." msgid "keystonemiddleware Release Notes" msgstr "keystonemiddleware Release Notes" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9847507 keystonemiddleware-10.9.0/releasenotes/source/locale/fr/0000775000175000017500000000000000000000000023362 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000000000000000025147 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000227600000000000030207 0ustar00zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware Release Notes 4.14.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-04-05 12:59+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 05:26+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "4.1.0" msgstr "4.1.0" msgid "4.2.0" msgstr "4.2.0" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "Bug Fixes" msgstr "Corrections de bugs" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Deprecation Notes" msgstr "Notes dépréciées " msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Security Issues" msgstr "Problèmes de sécurités" msgid "keystonemiddleware Release Notes" msgstr "Note de release de keystonemiddleware" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154103.9847507 keystonemiddleware-10.9.0/releasenotes/source/locale/ko_KR/0000775000175000017500000000000000000000000023760 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0207517 keystonemiddleware-10.9.0/releasenotes/source/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000000000000000025545 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po0000664000175000017500000000261100000000000030576 0ustar00zuulzuul00000000000000# Ian Y. Choi , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: keystonemiddleware Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-02-20 19:27+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-01-19 04:08+0000\n" "Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "4.1.0" msgstr "4.1.0" msgid "4.12.0" msgstr "4.12.0" msgid "4.2.0" msgstr "4.2.0" msgid "4.3.0" msgstr "4.3.0" msgid "4.5.0" msgstr "4.5.0" msgid "4.6.0" msgstr "4.6.0" msgid "Bug Fixes" msgstr "버그 수정" msgid "Current Series Release Notes" msgstr "최신 시리즈에 대한 릴리즈 노트" msgid "Deprecation Notes" msgstr "지원 종료된 기능 노트" msgid "Mitaka Series Release Notes" msgstr "Mitaka 시리즈에 대한 릴리즈 노트" msgid "New Features" msgstr "새로운 기능" msgid "Newton Series Release Notes" msgstr "Newton 시리즈에 대한 릴리즈 노트" msgid "Ocata Series Release Notes" msgstr "Ocata 시리즈에 대한 릴리즈 노트" msgid "Security Issues" msgstr "보안 이슈" msgid "Upgrade Notes" msgstr "업그레이드 노트" msgid "keystonemiddleware Release Notes" msgstr "keystonemiddleware 릴리즈 노트" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/mitaka.rst0000664000175000017500000000023200000000000023511 0ustar00zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/newton.rst0000664000175000017500000000021600000000000023557 0ustar00zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/ocata.rst0000664000175000017500000000021200000000000023330 0ustar00zuulzuul00000000000000============================ Ocata Series Release Notes ============================ .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000023176 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023543 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023370 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023363 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023367 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000024372 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023572 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000024060 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023676 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000023171 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023175 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000023032 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/requirements.txt0000664000175000017500000000121700000000000021010 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. keystoneauth1>=3.12.0 # Apache-2.0 oslo.cache>=1.26.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.serialization>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 pbr>=2.0.0 # Apache-2.0 pycadf>=1.1.0 # Apache-2.0 PyJWT>=2.4.0 # MIT python-keystoneclient>=3.20.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 WebOb>=1.7.1 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740154104.0287519 keystonemiddleware-10.9.0/setup.cfg0000664000175000017500000000307700000000000017353 0ustar00zuulzuul00000000000000[metadata] name = keystonemiddleware summary = Middleware for OpenStack Identity description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/keystonemiddleware/latest/ python_requires = >=3.9 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 :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 [files] packages = keystonemiddleware [extras] audit_notifications = oslo.messaging>=5.29.0 # Apache-2.0 [entry_points] oslo.config.opts = keystonemiddleware.auth_token = keystonemiddleware.auth_token._opts:list_opts keystonemiddleware.audit = keystonemiddleware.audit:list_opts paste.filter_factory = auth_token = keystonemiddleware.auth_token:filter_factory audit = keystonemiddleware.audit:filter_factory ec2_token = keystonemiddleware.ec2_token:filter_factory s3_token = keystonemiddleware.s3_token:filter_factory oauth2_token = keystonemiddleware.oauth2_token:filter_factory oauth2_mtls_token = keystonemiddleware.oauth2_mtls_token:filter_factory external_oauth2_token = keystonemiddleware.external_oauth2_token:filter_factory [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/setup.py0000664000175000017500000000200600000000000017233 0ustar00zuulzuul00000000000000# 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/test-requirements.txt0000664000175000017500000000103500000000000021763 0ustar00zuulzuul00000000000000hacking~=6.1.0 # Apache-2.0 flake8-docstrings~=1.7.0 # MIT coverage>=4.0 # Apache-2.0 cryptography>=3.0 # BSD/Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testtools>=2.2.0 # MIT python-binary-memcached>=0.29.0 # MIT python-memcached>=1.59 # PSF WebTest>=2.0.27 # MIT oslo.messaging>=5.29.0 # Apache-2.0 PyJWT>=2.4.0 # MIT # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740154056.0 keystonemiddleware-10.9.0/tox.ini0000664000175000017500000000416300000000000017042 0ustar00zuulzuul00000000000000[tox] minversion = 4.2.0 envlist = py3,pep8,releasenotes [testenv] usedevelop = True setenv = VIRTUAL_ENV={envdir} OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} [testenv:pep8] commands = flake8 bandit -r keystonemiddleware -x tests -n5 [testenv:bandit] # NOTE(browne): This is required for the integration test job of the bandit # project. Please do not remove. commands = bandit -r keystonemiddleware -x tests -n5 [testenv:venv] commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source keystonemiddleware --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:debug] commands = oslo_debug_helper -t keystonemiddleware/tests {posargs} [testenv:docs] deps = -r{toxinidir}/doc/requirements.txt commands= doc8 doc/source sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] deps = {[testenv:docs]deps} allowlist_externals = make rm commands = rm -rf doc/build/pdf sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] # D100: Missing docstring in public module # D101: Missing docstring in public class # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package # D107: Missing docstring in __init__ # D401: First line should be in imperative mood # W503 line break before binary operator # W504 line break after binary operator ignore = D100,D101,D102,D103,D104,D107,D401,W503,W504 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [hacking] import_exceptions = keystonemiddleware.i18n [doc8] extensions = .rst, .yaml # lines should not be longer than 79 characters. max-line-length = 79