././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/0000775000175000017500000000000000000000000016206 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/.coveragerc0000664000175000017500000000015100000000000020324 0ustar00zuulzuul00000000000000[run] branch = True source = keystoneclient omit = keystoneclient/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/.mailmap0000664000175000017500000000035400000000000017631 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/.stestr.conf0000664000175000017500000000011500000000000020454 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./keystoneclient/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/.zuul.yaml0000664000175000017500000000146300000000000020153 0ustar00zuulzuul00000000000000- job: name: keystoneclient-devstack-functional parent: devstack-minimal timeout: 4200 required-projects: - openstack/keystone - openstack/python-keystoneclient run: playbooks/run-ds-tox.yaml post-run: playbooks/tox-post.yaml vars: devstack_localrc: USE_PYTHON3: true devstack_services: key: true tox_envlist: functional zuul_work_dir: src/opendev.org/openstack/python-keystoneclient - project: templates: - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - check-requirements - lib-forward-testing-python3 - release-notes-jobs-python3 check: jobs: - keystoneclient-devstack-functional gate: jobs: - keystoneclient-devstack-functional ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/AUTHORS0000664000175000017500000002730500000000000017265 0ustar00zuulzuul00000000000000Aarni Koskela Aaron Rosen Adam Gandelman Adam Young Alan Pevec Alessio Ababilov Alessio Ababilov Alessio Ababilov Alex Gaynor Alex Meade Alexei Kornienko Alfredo Moralejo AmalaBasha Andre Naehring Andreas Jaeger Andreas Jaeger Andrey Kurilin Andy McCrae Anh Tran Ankit Agrawal Anthony Young Arthur Miranda Bernhard M. Wiedemann Bertrand Lallau Bhuvan Arumugam Boris Bobrov Boris Bobrov Boris Pavlovic Brad Pokorny Brant Knudson Brian Lamar Brian Waldon Bryan D. Payne Bryan Davidson Carlos D. Garza Cedric Brandily Chandan Kumar Chang Bo Guo ChangBo Guo(gcb) Charles V Bock Chen Chmouel Boudjnah Christian Berendt Christian Schwede Christophe Sauthier Christopher J Schaefer Chuck Short Chuck Thier Clark Boylan Claudiu Belu Clint Byrum Colleen Murphy Colleen Murphy Corey Bryant Cyril Roelandt Dan Prince Daniel Bengtsson Daniel Gonzalez Davanum Srinivas Davanum Srinivas Dave Chen Dave Wilde David C Kennedy David Stanek Dazhao Dean Troyer Deepti Ramakrishna Derek Higgins DhritiShikhar Dinesh Bhor Dirk Mueller Divyesh Khandeshi Dolph Mathews Dominik Heidler Donagh McCabe Doug Hellmann Doug Hellmann Dr. Jens Harbott Eduardo Magalhães Emilien Macchi Enrique Garcia Navalon Eric Brown Eric Guo Everett Toews Flavio Percoco Florent Flament Gabriel Hurley Gage Hugo George Tian Ghanshyam Mann Ghe Rivero Grzegorz Grasza Guang Yee Haneef Ali Harry Rybacki Hengqing Hu Henry Nash Hervé Beraud Hidekazu Nakamura Ian Cordasco Ian Cordasco Igor A. Lukyanenkov Ihar Hrachyshka Ilya Kharin Ionuț Arțăriși Ivan Udovichenko J. Matt Peterson Jaime Gil de Sagredo Luna Jakub Ruzicka James E. Blair Jamie Lennox Jamie Lennox Jamie Lennox Javeme Jay Pipes Jens Harbott Jeremy Stanley Jesse Andrews Joao Paulo Targino Joe Gordon Joe Gordon Joe Heck Joel Friedly JordanP Jose Castro Leon Joseph W. Breu Josh Kearney Joshua Harlow Juan Manuel Olle Jude Job Julien Danjou K Jonathan Harker Kannan Manickam Ken Thomas Ken'ichi Ohmichi Kevin L. Mitchell Kieran Spear Kristi Nikolla Kui Shi Kun Huang Ladquin Lance Bragstad Laurence Miao Lei Zhang Liem Nguyen Lin Hua Cheng Lorin Hochstein M V P Nitesh Maho Koshiya Marek Denis Mark McLoughlin MarounMaroun Masayuki Igawa Matt Fischer Matt Riedemann Matthew Treinish Matthieu Huin Michael J Fork Michael McCune Michael Simo Michael Solberg Mikhail Nikolaenko Monty Taylor Morgan Fainberg Mouad Benchchaoui Nachiappan Nachiappan VR N Nam Nguyen Hoai Navid Pustchi Neha Alhat Nicolas Simonds Nisha Yadav Ondřej Kobližek Ondřej Nový OpenStack Release Bot Paulo Ewerton Peng Yong Pete Zaitcev Peter Portante Pradeep Kilambi Qin Zhao QinglinCheng Qiu Yu Radomir Dopieralski Rakesh H S Rob Crittenden Rodrigo Duarte Rodrigo Duarte Sousa Rodrigo Duarte Sousa Rodrigo Duarte Sousa Rodrigo Duarte Sousa Roman Bogorodskiy Roxana Gherle Ruby Loo Rui Chen Rushi Agrawal Russell Bryant Sam Morrison Samuel Pilla Samuel de Medeiros Queiroz Santiago Baldassin Sascha Peilicke Sascha Peilicke Sean Dague Sean McGinnis Sergey Kraynev Sergio Cazzolato Steve Martinelli Steve Martinelli Steven Hardy Stuart McLaren Sushil Kumar THOMAS J. COCOZZELLO Takashi Kajinami Takashi Kajinami Tang Chen Thiago Paiva Brito Thierry Carrez Thomas Goirand Thomas Goirand Thomas Herve Tihomir Trifonov Tin Lam Tin Lam Tobias Diaz Tom Cocozzello Tom Fifield Tony Breeds Tovin Seven Trevor McKay Tristan Cacqueray Van Hung Pham Vasyl Khomenko Victor Morales Victor Silva Victor Stinner Vieri <15050873171@163.com> Vincent Untz Vishakha Agarwal Vishvananda Ishaya Vu Cong Tuan Wenxiang Wu Wu Wenxiang Xu (Simon) Chen Yaguang Tang Yaguang Tang YangLei Yatin Kumbhare Yukinori Sagara Yuuichi Fujioka Zhang Xin ZhengYue Zhenguo Niu ZhiQiang Fan ZhiQiang Fan ZhijunWei Zhongyue Luo Zhongyue Luo Ziad Sawalha ankitagrawal cao.yuan chenaidong1 chenxiao chenxing daniel-a-nguyen dineshbhor fujioka yuuichi guang-yee gugug henriquetruta hgangwx howardlee huangtianhua jacky06 jakedahn ji-xuepeng jun xie lawrancejing lhinds lin-hua-cheng lin-hua-cheng liu-sheng liuqing ljhuang llg8212 lrqrun lvxianguo mathrock melissaml nachiappan-veerappan-nachiappan openstack pawnesh.kumar pengyuesheng qingszhao rajiv root shu-mutou sonu.kumar sridhargaddam sunjia termie venkatamahesh wanghong wangzihao wingwj wu.chunyang wu.chunyang xingzhou yuyafei zhang-jinnan zhangboye zhangtongjian <125163227@qq.com> zhangyanxian zheng yin zhiyuan_cai zhubx007 zlyqqq ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/CONTRIBUTING.rst0000664000175000017500000000123400000000000020647 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: 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/python-keystoneclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753784.0 python_keystoneclient-5.6.0/ChangeLog0000664000175000017500000017473300000000000017777 0ustar00zuulzuul00000000000000CHANGES ======= 5.6.0 ----- * Remove Python 3.8 support * Update master for stable/2024.2 5.5.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 5.4.0 ----- * reno: Update master for unmaintained/yoga * tox: Drop envdir * Bump hacking * Update python classifier in setup.cfg 5.3.0 ----- * Remove six dependency * Remove deprecated pbr options * Stop installing python-all-dev in Ubuntu/Debian * Declare Python 3.10 support * Update master for stable/2023.2 5.2.0 ----- * Fix the gate * Update master for stable/2023.1 5.1.0 ----- * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed * Replace abc.abstractproperty with property and abc.abstractmethod 5.0.1 ----- * fix: remove error python2 defense code 5.0.0 ----- * Bump tox minversion to 3.18.0 * Update python testing as per zed cycle teting runtime 4.5.0 ----- * Drop lower-constraints.txt and its testing * Use TOX\_CONSTRAINTS\_FILE * use importlib.metadata to get keyring version * trivial: Drop os-testr * Remove translation sections from setup.cfg * Stop to use the \_\_future\_\_ module * remove unicode from code * Update master for stable/yoga * Use a stronger hash algorithm in the example certs * Update master for stable/xena * Update master for stable/wallaby * Update master for stable/victoria * Fix bindep.txt to work with newer CentOS and RHEL * setup.cfg: Replace dashes by underscores 4.4.0 ----- * Add access to /v3/auth/systems * Stop using an admin endpoint by default * Fix doc error to unblock the gate * Drop lower-constrait job 4.3.0 ----- * [goal] Migrate testing to ubuntu focal * Replace assertItemsEqual with assertCountEqual 4.1.1 ----- 4.1.0 ----- * Switch to newer openstackdocstheme and reno versions * Fix test-requirements.txt * Fix hacking min version to 3.0.1 * Bump default tox env from py37 to py38 * Add py38 package metadata * Fix the typo on attribute word * Use unittest.mock instead of third party mock * Fix docs publishing * Import os-client-config * Add Python3 victoria unit tests * Update master for stable/ussuri * Update the minversion parameter 4.0.0 ----- * Cleanup py27 support * Hacking: Fix F601 * Update hacking for Python3 * [ussuri][goal] Drop python 2.7 support and testing 3.22.0 ------ * Update master for stable/train 3.21.0 ------ * Fix unit tests broken by requests-mock * Generate pdf documentation * Add parent project filter for listing projects * Add support for app cred access rules * Bump the openstackdocstheme extension to 1.20 3.20.0 ------ * Blacklist bandit 1.6.0 & cap sphinx for 2.7 * Update the constraints url * Add Python 3 Train unit tests * Follow bandit B105: hardcoded\_password\_string * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Update the min version of tox * Update master for stable/stein * Drop py35 jobs * Make tests pass in 2020 * Update json module to jsonutils * Add support for app cred access rules header 3.19.0 ------ * add python 3.7 unit test job * Add return-request-id-to-caller function(v3/contrib) * Update hacking version * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Add release notes for return-request-id-to-caller * Fix keystoneclient-devstack-functional job * Use python3 for functional tests * Make the functional test voting * Add py36 tox environment * Convert functional tests to Zuulv3 * Add return-request-id-to-caller function(v3/contrib) 3.18.0 ------ * Don't quote {posargs} in tox.ini * Deprecate region enabled parameter * Import legacy keystoneclient-dsvm-functional * Use templates for cover and lower-constraints * 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 * fix misspelling of 'default' * Update reno for stable/rocky * refactor the getid method in keystoneclient/base.py * Switch to stestr * Add release note link in README 3.17.0 ------ * Update IdentityProviderManager docstring * Add support for project-specific limits * Add support for registered limits * Fix python3 test compat * fix tox python3 overrides * Remove PyPI downloads * fix a typo in docstring * Trivial: Update pypi url to new url 3.16.0 ------ * add lower-constraints job * Add return-request-id-to-caller function(v3) * Add Response class to return request-id to caller * Updated from global requirements * Updated from global requirements * Update links in README * Updated from global requirements * Override find function in project * Update reno for stable/queens 3.15.0 ------ * Add system role functionality * Add CRUD support for application credentials * Updated from global requirements * Add project tags to keystoneclient * Create doc/requirements.txt * Updated from global requirements 3.14.0 ------ * Updated from global requirements * Avoid tox\_install.sh for constraints support * Handle UTC+00:00 in datetime strings * Remove setting of version/release from releasenotes * Updated from global requirements * Remove functional tests for v2.0 API * Updated from global requirements * Use generic user for both zuul v2 and v3 * Updated from global requirements * Adds bandit nosec flag to hashlib.sha1 * Updated from global requirements * Remove use of positional decorator * Imported Translations from Zanata * Update reno for stable/pike * Updated from global requirements 3.13.0 ------ * Updated from global requirements * Updated from global requirements * Update URLs in documents according to document migration * Updated from global requirements * Bring back intersphinx reference to keystoneauth * Change locations of docs for intersphinx * Switch from oslosphinx to openstackdocstheme 3.12.0 ------ * Updated from global requirements * Add support for specifying role ids when creating trust * Updated from global requirements 3.11.0 ------ * Fix html\_last\_updated\_fmt for Python3 * Updated from global requirements * Moved release note to the correct path * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Stop using oslotest.mockpatch * Remove unused log * Remove pbr warnerrors in favor of sphinx check * Add support for endpoint group filtering * Replace six.iteritems() with .items() * Updated from global requirements * Fix failing PY2 and PY3 gate jobs * Remove log translations in python-keystoneclient * Updated from global requirements * Add support for endpoint group CRUD * Fix 12 warnings when building keystoneclient docs * Use https for \*.openstack.org references * Fix boto version strip regex * Update reno for stable/ocata 3.10.0 ------ * Allow Multiple Filters of the Same Key * Updated from global requirements * Fix response body being omitted in debug mode incorrectly * Updated from global requirements * Removes unnecessary utf-8 encoding 3.9.0 ----- * Only log application/json in session to start * X-Serivce-Token should be hashed in the log * Do not log binary data during request * remove hacking checks from keystoneclient * Remove references to Python 3.4 * Updated from global requirements * Prevent MemoryError when logging response bodies * Add Constraints support * re-work inference rule bindings * Updated from global requirements * Use assertIsNone(...) instead of assertEqual(None, ...) * Deprecate the generic client * Refactor test\_projects * Refactor test\_credentials * Updated from global requirements * Fix Failing tests with openssl >= 1.1.0 * skip failing functional test 3.8.0 ----- * Pass allow\_expired to token validate * Show team and repo badges on README * Replace 'assertFalse(a in b)' with 'assertNotIn(a, b)' * Fix some spelling mistaks in base.py & auth.py * Refactor test\_domain\_configs * Fix typo in access.py 3.7.0 ----- * Remove revocation event code * Enable code coverage report in console output * Do not add last\_request\_id * Updated from global requirements * Fix typo in httpclient.py * Updated from global requirements * Support domain-specific configuration management * Updated from global requirements * Updated from global requirements * Updated from global requirements * Increase readability of 'find()' method and small improvements * Updated from global requirements * [doc] remove auth plugin docs * Updated coverage configuration file * Updated from global requirements * Updated from global requirements * Remove redundant variable declaration * Enable release notes translation 3.6.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * TrivialFix: Fixed typo in some files * Import module instead of object * TrivialFix: Using assertIsNone() instead of assertEqual(None) * Updated from global requirements * Fix non-ascii attributes * Updated from global requirements * Correct output for Implied Roles * Revert "Add auth functional tests" * Import module instead of object * standardize release note page ordering * Update reno for stable/newton * Remove unauthenticated functions * Use exceptions from Keystoneauth * Use AUTH\_INTERFACE object from keystoneauth * Use fixtures from keystoneauth 3.5.0 ----- * Minor docstring fix in mappings.py * Updated from global requirements * Follow up patch for Improve docs for v3 policies * Follow up patch for Improve docs for v3 services * Follow up patch for Improve docs for v3 domains * Follow up patch for Add ec2 functional tests * Add auth functional tests * Reuse Domain and Project resouce definitions * Improve docs for v3 tokens * Fix no content return type doc * Follow up patch for Improve docs for v3 ec2 * Add ec2 functional tests * Improve docs for v3 auth * Improve docs for v3 ec2 * Add credential functional tests * Move other-requirements.txt to bindep.txt * Add \_\_ne\_\_ built-in function * Remove deprecated 'data' credential argument * Improve docs for v3 credentials * Add role functional tests * Fix missing service\_catalog parameter in Client object * Add Python 3.5 classifier * Follow up patch for Improve docs for v3 roles * Updated from global requirements * Improve docs for v3 roles * Correct test\_implied\_roles 3.4.0 ----- * Updated from global requirements * Do not send user ids as payload * Updated from global requirements * Add endpoint functional tests * Improve docs for v3 endpoints 3.3.0 ----- * Add region functional tests * Add project functional tests * Use assertEqual() instead of assertDictEqual() * Updated from global requirements * Improve implied-role functional tests * Fix other-requirements.txt for deb based distros * Updated from global requirements * Remove unused LOG * Updated from global requirements * Improve docs for v3 regions * Add policy functional tests * Improve docs for v3 policies * Add service functional tests * Improve docs for v3 services * Use the adapter instead of the client in tests * Remove print in tests.functional.v3.test\_implied\_roles 3.2.0 ----- * Updated from global requirements * Update other-requirements.txt for Xenial * Update README to comply with Identity V3 * List system dependencies for running common tests * Follow up patch for Improve docs for v3 projects * Updated from global requirements * Improve docs for v3 projects * Add group functional tests * Updated from global requirements * Improve docs for v3 groups * Follow up patch for add domain functional tests * Add domain functional tests * Improve docs for v3 domains * Updated from global requirements * Use /v3/auth/projects and /v3/auth/domains * Handle EmptyCatalog exception in list federated projects * Updated from global requirements * PEP257: Ignore D203 because it was deprecated * Updated from global requirements * import warnings in doc/source/conf.py * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Remove unused iso8601 requirement * map fixtures to keystoneauth 3.1.0 ----- * Updated from global requirements * Update the home-page with developer documentation * Add users functional tests * Improve docs for v3 users * Trivial: ignore openstack/common in flake8 exclude list * Updated from global requirements * Updated from global requirements * Fixing D105 PEP257 * Fixing D200 PEP257 violation * Fixing D202 and D203 PEP257 violation * Fixing D204, D205, and D207 PEP257 violation * Fixing D208 PEP257 violation * httpclient: remove unused debug kwargs * Fixing D211 PEP257 violation * Fixing D301 PEP257 violation * Add federation related tests * [Trivial] Remove unnecessary executable privilge of unit test file * Replace tempest-lib with tempest.lib * Fix identity\_providers docstring * Updated from global requirements * Fallback if Git repository is absent * Fix D400 PEP257 violation * Fix D401 PEP257 violation * Updated example in README * Removing bandit.yaml in favor of defaults * Updated from global requirements * Updated from global requirements 3.0.0 ----- * Updated from global requirements * Allow seeing full token response when debug enabled * Enhance functional class to provide default info * Remove keystone bash completion scripts for Keystone * Remove doc references to the keystone CLI * remove CLI from keystoneclient * remove oslo-incubator apiclient * Update reno for stable/mitaka * Fix reference to ClientException * Update Client examples to use sessions * Change tests to pass session to Client * Update developer docs for keystoneauth session * Correct test running instructions * Document session as an argument to v3.Client * Link to AccessInfoV3 returned from get\_raw\_token\_from\_identity\_service * Tests stop using deprecated HTTPClient.get() 2.3.1 ----- * Revert "Support \`truncated\` flag returned by identity service" 2.3.0 ----- * Updated from global requirements * Support \`truncated\` flag returned by identity service * Updated from global requirements * Support creation of domain specific roles * Get revocation list with only audit ids * Add back a bandit tox job * Implied Roles 2.2.0 ----- * add release notes for deprecated auth bits * Updated from global requirements * Updated from global requirements * Make pep8 \*the\* linting interface * Handle exception on UnicodeDecodError in logging of request * Updated from global requirements * Deprecate adapter * Deprecate auth plugins from keystoneclient * Deprecate Session * Remove python 2.5 workaround * Update keyring requirements * Update translation setup * Bandit profile updates * Missing defaults in the create() method in the v2 ServiceManager * Remove Babel from requirements.txt * use positional library instead of utils * Replace TestResponse with requests\_mock * Use positional library instead of local code * Remove argparse from requirements * Adds an option to include names in role assignment lists * Updated from global requirements * Remove bandit tox environment * Mark password/secret options as secret 2.1.2 ----- 2.1.1 ----- * Revert "Support \`truncated\` flag returned by keystone" * Revert "Change default endpoint for Keystone v3 to public" * Address hacking check H405 2.1.0 ----- * add release notes for ksc 2.1.0 * Updated from global requirements * Updated from global requirements * Fix for the deprecated library function * Implements base classes for functional tests * Wrong usage of "a/an" * Remove "deprecated" internal method * Cleanup release note * remove keystoneclient.apiclient.exceptions * Support \`truncated\` flag returned by keystone * Change default endpoint for Keystone v3 to public * Updated from global requirements * Make tests run against original client and sessions * Seperate Client base test class * Removes MANIFEST.in as it is not needed explicitely by PBR * Deprecate the baseclient.Client * Replace textwrap with fast standard code * Docstring: Mark optional parameter as optional * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * move hacking to tests folder * remove venv bits from tools * Add include\_subtree to role\_list\_assignments call * Updated from global requirements * remove the default arguments "{}" * Updated from global requirements * remove oslo-incubator's memorycache * WebOb not needed after auth\_token removal * Deprecated tox -downloadcache option removed * Remove keystoneclient.middleware * Updated from global requirements * Updated from global requirements * Put py34 first in the env order of tox * Accept v2 params to v3 service create * Delete python bytecode before every test run 2.0.0 ----- * Remove hardcoded endpoint filter for update password * Add release notes for keystoneclient * Updated from global requirements * remove unnecessary FakeLog class in test code * No keystone Endpoint now gives a valid Error Message * Removes py26 support * Removes discover from test-reqs * Fixes warning for positional arg in project create * Updated from global requirements * Swap the order of username deprecation * Map keystoneclient exceptions to keystoneauth * Last sync from oslo-incubator * Updated from global requirements * Add missing end single quote * update incorrect docstring for regions * Iterate over copy of session.adapters keys in Python2/3 * Add docstring validation * Silence most of the deprecation spam * Pull the endpoint from the Session 1.8.1 ----- * Updated from global requirements * Updated from global requirements 1.8.0 ----- * Updated from global requirements * Replace repeated assertion with the loss * Mark abstractmethod bodies with nocover * Docstring spelling and function-vs-method fixes * pass on @abc.abstractmethods * Updated from global requirements * Fix typo that says V3 token only works for v2 * auto-generate release history * Updated from global requirements * Updated from global requirements * Redirect on 303 in SAML plugin * Make \_\_all\_\_ immutable * HTTPClient/region\_name deprecation test updates * Add shields.io version/downloads links/badges into README.rst * List creation could be rewritten as a list literal * Use dictionary literal for dictionary creation * Change ignore-errors to ignore\_errors * Updated from global requirements * Updated from global requirements * Move pot file for traslation * Use region\_id filter for List Endpoints * Identity plugin thread safety * Avoid message concatenation in error path 1.7.1 ----- * Adding back exception mapping for ConnectionError 1.7.0 ----- * Update path to subunit2html in post\_test\_hook * Deprecate create Discover without session * Mask passwords when logging the HTTP response * Updated from global requirements * Update deprecation text for Session properties * Proper deprecation for httpclient.USER\_AGENT * Deprecate create HTTPClient without session * Fix Accept header in SAML2 requests * Fixes missing socket attribute error during init\_poolmanager * Updated from global requirements * Expose token\_endpoint.Token as admin\_token * Proper deprecation for UserManager project argument * Proper deprecation for CredentialManager data argument * Deprecate create v3 Client without session * Deprecate create v2\_0 Client without session * Proper deprecation for Session.get\_token() * Deprecate use of cert and key * Proper deprecation for Session.construct() * Deprecate ServiceCatalog.get\_urls() with no attr * Deprecate ServiceCatalog(region\_name) * Updated from global requirements * Updated from global requirements * Updated from global requirements * Stop using .keys() on dicts where not needed * Inhrerit roles project calls on keystoneclient v3 * Deprecate openstack.common.apiclient * Move apiclient.base.Resource into keystoneclient * oslo-incubator apiclient.exceptions to keystoneclient.exceptions * Proper deprecation for HTTPClient session and adapter properties * Proper deprecation for HTTPClient.request methods * Proper deprecation for HTTPClient.tenant\_id|name * Proper deprecation for HTTPClient tenant\_id, tenant\_name parameters * Updated from global requirements * Clarify setting socket\_options * Remove check for requests version * Updated from global requirements * Fix tests passing user, project, and token * Proper deprecation for httpclient.request() * Proper deprecation for Dicover.raw\_version\_data unstable parameter * Proper deprecation for Dicover.available\_versions() * Proper deprecation for is\_ans1\_token * Proper deprecation for client.HTTPClient * Proper deprecation for Manager.api * Stop using Manager.api * Proper deprecation for BaseIdentityPlugin trust\_id property * Proper deprecation for BaseIdentityPlugin username, password, token\_id properties * Proper deprecations for modules * Use UUID values in v3 test fixtures * Proper deprecation for AccessInfo management\_url property * Proper deprecation for AccessInfo auth\_url property * Stop using deprecated AccessInfo.auth\_url and management\_url * Proper deprecation for AccessInfo scoped property * Proper deprecation for AccessInfo region\_name parameter * Deprecations fixture support calling deprecated function * Set reasonable defaults for TCP Keep-Alive * Updated from global requirements * Remove unused time\_patcher * Make OAuth testcase use actual request headers * Prevent attempts to "filter" list() calls by globally unique IDs * Add get\_token\_data to token CRUD * Updated from global requirements * py34 not py33 is tested and supported * Updated from global requirements * Remove confusing deprecation comment from token\_to\_cms * Fixes modules index generated by Sphinx * Updated from global requirements * Unit tests catch deprecated function usage * Switch from deprecated oslo\_utils.timeutils.strtime * Switch from deprecated isotime * Remove keystoneclient CLI references in README * Update README.rst and remove ancient reference * Remove unused images from docs * Updated from global requirements * Add openid connect client support * Stop using tearDown * Use mock rather than mox * Remove unused setUp from ClientTest * Updated from global requirements * Iterate over copy of sys.modules keys in Python2/3 * Use random strings for test fixtures * Stop using function deprecated in Python 3 * Use python-six shim for assertRaisesRegex/p * tox env for Bandit 1.6.0 ----- * Add EC2 CRUD credential support to v3 API * Cleanup fixture imports * Removes unused debug logging code 1.5.0 ----- * A Default CLI plugin * Fixed grammatical errors in the V2 Client API doc * Fixe example code in Using Sessions page * Add get\_communication\_params interface to plugins * Fix auth required message translation * Revert "Remove keystoneclient.middleware" * Revert "Remove unused fixtures" * Add docstrings for \`\`protocol\`\` parameter * Typo in openstack client help * Pass OS\_\* env vars fix for tox 2.0 * Remove unused fixtures * Updated from global requirements * Use 'mapping\_id' instead of 'mapping' in federation protocol tests * Use 'id' instead of 'protocol\_id' in federation protocol tests * Drop use of 'oslo' namespace package * Don't autodoc the test suite * Sync from oslo incubator * Removes temporary fix for doc generation * Ensure that failing responses are logged * add --slowest flag to testr * Prompt for password on CLI if not provided * Adapter version is a tuple * Remove keystoneclient.middleware * Document non-standard encoding of the PKI token 1.4.0 ----- * Add endpoint and service ids to fixtures * Uncap library requirements for liberty * Provide a means to get all installed plugins * Fix s3\_token middleware parsing insecure option * Make process\_header private * Fix tests to work with requests<2.3 * Increase minimum token life required * Update sample data with audit ids * pep8 fix for CMS * Inherited role domain calls on keystoneclient v3 * Add support to create ECP assertion based on a token * Add support to create SAML assertion based on a token * Allow requesting an unscoped Token * Support /auth routes for list projects and domains * Support discovery on the AUTH\_INTERFACE * Expose audit\_id via AccessInfo * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements 1.3.0 ----- * Return None for missing trust\_id in fixture * Improve feedback message in SSL error * Add a FederatedBase v3 plugin * Deprecate keystone CLI * Clean arguments in test\_federation.\*.test\_create() * Rename requests mock object in testing * Provide a generic auth plugin loader * Make non-import packages lazy * Extract BaseAuth out of Auth Plugin * Split v3 authentication file into module * Federation Service Providers CRUD operations * Allow passing logger object to request * Crosslink to other sites that are owned by Keystone * Implements subtree\_as\_ids and parents\_as\_ids * Fix time issue in AccessInfo test * Don't autodoc the test suite * Add OS-SIMPLE-CERT support for v3 * Updated from global requirements * Allow handling multiple service\_types * Import functional CLI tests from tempest * Creating parameter to list inherited role assignments 1.2.0 ----- * Updated from global requirements * Make post\_test\_hook.sh executable * Add default body for non-abstract empty methods * Create functional test base * Ignore all failures removing catalog when logging token * Using correct keyword for region in v3 * Move tests to the unit subdirectory * Make remove\_service\_catalog private * Docs for v3 credentials * Change hacking check to verify all oslo imports * Change oslo.i18n to oslo\_i18n * Add data to example data 1.1.0 ----- * Remove 404 link to novaclient in README * Hierarchical multitenancy basic calls * Workflow documentation is now in infra-manual * use right resource\_class to create resource instance * Basic AccessInfo plugin * Enable hacking rule E122 and H304 * Add get\_headers interface to authentication plugins * Add name parameter to NoMatchingPlugin exception * Change oslo.config to oslo\_config * Change oslo.serialization to oslo\_serialization * Switch from oslo.utils to oslo\_utils * Add validate token for v3 * Tests use keep\_blank\_values when parse\_qs * Fix typo in Ec2Signer class docstring * Add validate token for v2.0 * handles keyboard interrupt * make req\_ref doesn't require id * Updated from global requirements * Surface the user\_id and project\_id beyond the plugin * Configure TCP Keep-Alive for certain Sessions * Correct failures for check H238 * fix enabled parameter of update doesn't default to None * Enable hacking rule F821 * Fixes bootstrap tests * Add auth plugin params to doc * Add generic auth plugin documentation * Correct failures for check W292 * Move to hacking 0.10 * Updated from global requirements * don't log service catalog in every token response * Updated from global requirements * Use a test fixture for mocking time * Docstring usability improvements * token signing support alternative message digest * Allow fetching user\_id/project\_id from auth * add clear definition of service list * Fix a comment error in cms.py * Add get certificates for v2.0 * Updated service name to be optional in CLI * Reference identity plugins from \_\_init\_\_.py * Use textwrap instead of home made implementation * Allow v3 plugins to opt out of service catalog 1.0.0 ----- * Document the auth plugins that are loadable by name * Updated from global requirements * Add fetch revocations for v3 * Add fetch revocations for v2.0 * Fix up types within API documentation * Update requests-mock syntax * Expose version matching functions to the public * Take plugin params from ENV rather than default * Project ID in OAuth headers was missing * get\_endpoint should return the override * Pass all adapter parameters through to adapter * Correct documenting constructor parameters * Correct Session docstring * Add missing user-id option to generic.Password * duplicate auth-url option returned by BaseGenericPlugin * Replace magic numbers with named symbols * Fix importing config module and classmethod params * Removes confusing \_uuid property * Curl statements to include globoff for IPv6 URLs * Cleanup exception logging * Make keystoneclient use an adapter * Remove middleware architecture doc * Remove useless log message * Updated from global requirements * Updated from global requirements * I18n * Correct use of noqa * Sync oslo-incubator to 1fc3cd47 * Log the CA cert with the debug statement * Prevent AttributeError if no authorization 0.11.2 ------ * Use oslo\_debug\_helper and remove our own version * Updated from global requirements * set close\_fds=True in Popen * Cleanup docs - raises class * Fix mappings.Mapping docstring * Actually test interactive password prompt * Docstring cleanup for return type * Correct typos in man page * Docstrings should have :returns: everywhere * Use oslo.utils and oslo.serialization * Remove warning about management token * Document session usage first * Doc cleanup, make concepts links * Rename the client API docs * Correct typos in using-sessions * Warn that keystone CLI is pending deprecation * Reorder index links * Explicit complaint about old OpenSSL when testing * Log token with sha1 * Redact x-subject-token from response headers * Extracting common code to private method * Change cms\_sign\_data to use sha256 message digest * Enumerate Projects with Unscoped Tokens 0.11.1 ------ * Fix auth\_token for old oslo.config * Do not iterate action.choices if it is none 0.11.0 ------ * Update hacking to 0.9.x * Updated from global requirements * Add support for endpoint policy * Fix a doc\_string error * Handle federated tokens * SAML2 federated authentication for ADFS * Fix the condition expression for ssl\_insecure * Allow retrying some failed requests * Versioned Endpoint hack for Sessions * Stop using intersphinx * Pass kwargs to auth plugins * Sync with latest oslo-incubator * Change 'secrete' to 'secret' * fix typos * Work toward Python 3.4 support and testing * warn against sorting requirements * Version independent plugins * Expose auth methods on the adapter * Add version parameter to adapter * Allow providing an endpoint\_override to requests * fix EC2 Signature Version 4 calculation, in the case of POST * Fix test mistake with requests-mock * Allow passing None for username in v2.Password * Hash for PKIZ * Distinguish between name not provided and incorrect * Move fake session to HTTPClient * Allow providing a default value to CLI loading * Allow unauthenticated discovery * Remove cruft from setup.cfg * Unsort pbr and hacking in requirements files * Add v3scopedsaml entry to the setup.cfg * Fix handling of deprecated opts in CLI * Updated from global requirements * Revert "Add oslo.utils requirement" * Revert "Use oslo.utils" * Remove lxml as a forced depend * Allow passing user\_id to v2Password plugin * Make auth plugins dest save to os\_ * Allow registering individual plugin CONF options * Standardize AccessInfo token setting * Convert shell tests to requests-mock * Change unscoped token fallback to be session aware * Individual plugin CLI registering * Remove intersphinx mappings * Mark auth plugin options as secret * move attributes of v3.client.Client into alphabetical order * Handle invalidate in identity plugins correctly * Isolate get\_discovery function * Fixes import grouping * expose the revoke token for V3 * Use oslo.utils * Add oslo.utils requirement * Mark the keystoneclient s3\_token middleware deprecated * Add docs for how to create an OAuth auth instance * Control identity plugin reauthentication * Use token and discovery fixture in identity tests * Config fixture from oslo-incubator is not used * Use config fixture from oslo.config * List federated projects and domains * Redact tokens in request headers * Convert httpretty to requests-mock * Updated from global requirements * Add the 'auth' interface type * Use oslosphinx to generate doc theme * Add an example of using v3 client with sessions 0.10.1 ------ * Don't log sensitive auth data * Reorder the old compatibility arguments * Use keystoneclient.exceptions * Insert space between \`\`#\`\` and the comment * Rename saml2\_token\_url to token\_url * Enforce authenticated=False in saml2 plugin * Allow passing kwargs from managers to session * Scope unscoped saml2 tokens * Example JSON files should be human-readable 0.10.0 ------ * use embedded URLs for hyperlinks in the README * Only conditionally import working keyring * Calculate a suitable column width for positional arguments * Fix mistakes in token fixtures * add deprecation warning for auth\_token * SAML2 ECP auth plugin * remove useless part of error message * Test that tenant list function can use auth\_url * Add v2 Token manager authenticate tests * Use jsonutils to load adapter response * Provide an \_\_all\_\_ for auth module * Docstrings for usability * Add CRUD operations for Federated Protocols * Direct move of the revoke model from keystone server * Add tests without optional create endpoint params * Allow loading auth plugins from CLI * Plugin loading from config objects * Ensure no double slash in get token URL * Pass user and roles manager to tenant manager * Add profiling support to keystoneclient * Add V2 tenant user manager tests * Pass roles manager to user manager * Add V2 user roles tests * endpoint\_id and service\_id should be random uuid * Add CONTRIBUTING.rst * Modify oauth calls to expect urlencoded responses * Document authentication plugins * Add a fixture for Keystone version discovery * Sync with oslo-incubator fd90c34a9 * Session loading from CLI options * Session loading from conf * Keystoneclient create user API should have optional password * Use immutable arg rather mutable arg * Add trust users to AccessInfo and fixture * Add OAuth data to AccessInfo * Minor grammatical fix in doc * Updated from global requirements * Correcting using-api-v2.rst * Make parameters in EndpointManager optional * Add invalidate doc string to identity plugin * Session Adapters * Unversioned endpoints in service catalog * Update keystoneclient code to account for hacking 0.9.2 * Rename v3.\_AuthConstructor to v3.AuthConstructor * Add issued handlers to auth\_ref and fixtures * Add role ids to the AccessInfo * Doc build fails if warnings * Imports to fix build warnings * Updated from global requirements * auth\_token \_cache\_get checks token expired * Adjust Python 2.6 OSerror-on-EPIPE workaround * Using six.u('') instead of u'' * Added help text for the debug option * Session Documentation * Link to docstrings in using-api-v3 * Set the iso8601 log level to WARN * 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 0.9.0 ----- * Clean up oauth auth plugin code * Fix a misspelling in a comment * Sync with oslo-incubator caed79d * Fix attributes ordering at v3/client.py * Add endpoint handling to Token/Endpoint auth * Add support for extensions-list * auth\_token middleware hashes tokens with configurable algorithm * Remove left over vim headers * Add /role\_assignments endpoint support * Authenticate via oauth * Add description param to v3 service create/update * Fixed an aparent typo in the code * Auth Plugin invalidation * Updated from global requirements * Move DisableModuleFixture to utils * replace string format arguments with function parameters * Fixes an erroneous type check in a test * Mark keystoneclient as being a universal wheel * auth\_token hashes PKI token once * add docstr to v2 shell module regarding CLI deprecation * Compressed Signature and Validation * OAuth request/access token and consumer support for oauth client API * Add mailmap entry * Regions Management * Sync with oslo-incubator 2640847 * Discovery URL querying functions * Remove importutils from oslo config * Move auth\_token tests not requiring v2/v3 to new class * Cached tokens aren't expired * Move auth\_token cache pool tests out of NoMemcache * Make auth\_token return a V2 Catalog * Fix client fixtures * fixed typos found by RETF rules * Fix docstrings in keystoneclient * auth\_token configurable check of revocations for cached * Remove unused AdjustedBaseAuthTokenMiddlewareTest * Synced jsonutils from oslo-incubator * auth\_token test remove unused fake\_app parameter * Fix typo in BaseAuthTokenMiddlewareTest * Updated from global requirements * 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 0.8.0 ----- * CLI always configures logging * Create a V3 Token Generator * Implement endpoint filtering functionality on the client side * 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 * Updated from global requirements * Add service name to catalog * Hash functions support different hash algorithms * Add CRUD operations for Identity Providers * eliminate race condition fetching certs * Allow passing auth plugin as a parameter * 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 * Reuse module \`exceptions\` from Oslo * Updated from global requirements * Rename request\_uri to identity\_uri * Tests should use identity\_uri by default * Replace auth fragements with identity\_uri 0.7.1 ----- * Adds to Keystone to convert V2 endpoints to V3 * Remove releases.rst from keystone docs 0.7.0 ----- * Improve language in update\_password() validation error * 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 * Start using positional decorator * Fix passing get\_token kwargs to get\_access * add functional test for cache pool * Sync config fixture object from oslo.incubator * Add a positional decorator * add pooling for cache references * use v3 api to get certificates * Don't use a connection pool unless provided * Reference docstring for auth\_token fields * Docs link to middlewarearchitecture * Discover should support other services * Revert "Add request/access token and consumer..." * Revert "Authenticate via oauth" * Fix doc build errors * Generate module docs * document that --pass can be required * Authenticate via oauth * Add request/access token and consumer support for keystoneclient * Use AccessInfo in auth\_token middleware * Add 'methods' to all v3 test tokens * Handle Token/Endpoint authentication * Split sample PKI token generation * Updated from global requirements * Fix retry logic * Provide more data to AuthMethod plugins * Fix state modifying catalog tests * Remove reference to non-existent shell doc * increase default revocation\_cache\_time * Improve help strings * Make keystoneclient not log auth tokens * improve configuration help text in auth\_token * Log the command output on CertificateConfigError * Enforce scope mutual exclusion for trusts * Atomic write of certificate files and revocation list * Privatize auth construction parameters * Remove blank space after print * Set the right permissions for signing\_dir in tests * Capitalize Client API title consistently * Remove dependent module py3kcompat * 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 * 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 * Add Python 3 classifiers * Replace assertEqual(None, \*) with assertIsNone in tests * Ensure domains.list filtered results are correct * Test query-string for list actions with filter arguments * Use Resource class from Oslo * Fix keystone command man page * Add link to the v3 client api doc * Fix references to auth\_token in middlewarearchitecture doc 0.6.0 ----- * Don't use private last\_request variable * Python: Pass bytes to derive\_keys() * Make sure to unset all variable starting with OS\_ * Remove tox locale overrides * Python3: use six.moves.urllib.parse.quote instead of urllib.quote * Remove vim header * Python3: httpretty.last\_request().body is now bytes * Python3: fix test\_insecure * Sync openstack/common/memorycache.py with Oslo * cms: Use universal\_newlines=True in subprocess.Popen() * HTTPretty: Bump to 0.8.0 * Check for any monkeypatching * Python3: webob.Response.body must be bytes * Python 3: call functions from memcache\_crypt.py with bytes as input * Update my mailmap * Improve output of "keystone help discover" * Use requests library in S3 middleware * Python 3: make tests from v2\_0/test\_access.py pass * Sync apiclient from oslo * Create Authentication Plugins * Fix debug curl commands for included data * Add back --insecure option to CURL debug 0.5.1 ----- * 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 0.5.0 ----- * refactor handling of extension list * 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 * Fix discover command failed to discover keystone in ssl * Fix E12x warnings found by Pep8 1.4.6 * Fix typos in documents and comments * Consistently support kwargs across all v3 CRUD Manager ops * Using common method 'bool\_from\_string' from oslo strutils * Python 3: set webob.Response().body to a bytes value * Remove test\_print\_{dict,list}\_unicode\_without\_encode * Tests use cleanUp rather than tearDown * Sort items in requirement related files * Respect region name when processing domain URL * Python 3: fix the \_calc\_signature\_\* functions * Adjust import items according to hacking import rule * Replace assertTrue with explicit assertIsInstance * Sync with global requirements * Fix discover command failed to read extension list issue * Clarify roles validation error messages * Fix incorrect assertTrue usage * Make assertQueryStringIs usage simpler 0.4.2 ----- * auth\_token tests use assertIs/Not/None * Updated from global requirements * Python 3: Use HTTPMessage.get() rather than HTTPMessage.getheader() * auth\_token tests close temp file descriptor * Tests cleanup temporary files * Use install\_venv from oslo to fix no post\_process issue * Removes use of timeutils.set\_time\_override * Saner debug log message generation * Controllable redirect handling * Revert "Whitelist external netaddr requirement" * Sync strutils from oslo * 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 * Debug env for tox * Whitelist external netaddr requirement * Prevent dictionary size from changing while iterating over its items * Do not try to call decode() on a text string * 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 * Move redirect handling to session * Remove debug specific handling * Fix missed management\_url setter in v3 client * Add service catalog to domain scoped token fixture * Update requirements * Change assertEquals to assertIsNone * Avoid meaningless comparison that leads to a TypeError * HTTPretty: update to 0.7.1 * Python3: replace urllib by six.moves.urllib * Remove the 'cmp' keyword from a call to 'sort()' * Make \_get\_utf8\_value Python3 compliant * Don't install pre-release software with tox * Sync global requirements to pin sphinx to sphinx>=1.1.2,<1.2 * Allow commit title messages to end with a period * Sync with latest module from oslo * 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 * Bump hacking to 0.8 * Add workaround for OSError raised by Popen.communicate() * Use assertIn where appropriate * Updates .gitignore * Updates .gitignore * Extract a base Session object * Reorganize Service Catalog * 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 * Fix trustee/trustor definitions * Revert "Merge "Avoid returning stale token via auth\_token property"" * "publicurl" should be required on endpoint-create * Update the management url for every fetched token * Remove service type restriction from keystone client help text * Add testresources test requirement * Fix python3 incompatible use of urlparse * Update tox.ini to usedevelop * Make HACKING.rst DRYer and turn into rst file * Quote URL in curl output to handle query params * Fix curl debug output for requests with a body * Add --insecure to curl output if required * Convert revocation list file last modified to UTC * Improved error message on connection failure * PEP 8 Public and internal interfaces * python3: Work around httpretty issue * Remove unused simplejson requirement * Migrate the keystone.common.cms to keystoneclient * Avoid returning stale token via auth\_token property * Remove SERVICE\_TOKEN and SERVICE\_ENDPOINT env vars * Apply six for metaclass * Make ROOTDIR determination more robust 0.4.1 ----- * Replace OpenStack LLC with OpenStack Foundation * Add AssertRequestHeaderEqual test helper and make use of it * Sync jsonutils from oslo * python3: Refactor dict for python2/python3 compat * Updated from global requirements * python3: Make iteritems py3k compat 0.4.0 ----- * Normalize datetimes to account for tz * assertEquals is deprecated, use assertEqual (H602) * Fix H202 assertRaises Exception * Refactor for testability of an upcoming change * Fixes print error for keystone action with non-English characters * Allow v2 client authentication with trust\_id * Fix misused assertTrue in unit tests * Add auth\_uri in conf to avoid unnecessary warning * Require oslo.config 1.2.0 final * Move tests in keystoneclient * Change Babel to a runtime requirement * Allow blank to email in user-update * Set example timestamps to 2038-01-18T21:14:07Z * Replace HttpConnection in auth\_token with Requests * Convert tests to HTTPretty and reorganize * Support client generate literal ipv6 auth\_uri base on auth\_host * Log user info in auth\_token middleware * Decode the non-english username str to unicode * Deprecation warning should be 'pending' * Deprecation warning for the CLI * Don't need to init testr explicitly * Allow Hacking 0.7.x or later * Remove testcase test\_invalid\_auth\_version\_request * Correct keyword args in test cases * python3: Use from future import unicode\_literals * Fixing potential NameErrors * Modify keyring tests to test authentication * Fix and enable gating on F811 * Fix and enable gating on F841 * Remove duplicate method in AccessInfo * remove the UUID check for userids * Standardize base.py with novaclient * Fix and enable gating on H302: only import modules * Fix License Headers and Enable Gating on H102 * Replace auth\_token middleware tests with httpretty * Add domain attributes to accessinfo * Support older token formats for projects in accessinfo * Use OSLO jsonutils instead of json module * python3: Transition to mox3 instead of mox * Sync py3kcompat from oslo-incubator * Update oslo.config * clearer error when authenticate called without auth\_url 0.3.2 ----- * Add unittests for exceptions.EmptyCatalog * Allow configure the number of http retries * Restore client.py for backward compatibility * Initial Trusts support * Add importutils and strutils from oslo * Synchronize code from oslo * Add apiclient.exceptions hierarchy * Use hashed token for invalid PKI token cache key * Move flake8 option from run\_tests.sh to tox.ini * Extract test token data from auth\_token middleware * Make auth\_token middleware fetching respect prefix * Fix and enable Gating on H404 * Move all opens in auth\_token to be in context * Refactor verify signing dir logic * Fixes files with wrong bitmode * flake8: enable H201, H202, H802 * Adds support for passing extra tenant attributes to keystone * Add a get\_data function to Service Catalog * Extract basic request call * Rename client.py to httpclient.py * Updated from global requirements * Don't cache tokens as invalid on network errors * Fix test\_request\_no\_token\_dummy cms dependency * Fix a typo in fetch\_revocation\_list * Make TestResponse properly inherit Response * auth\_uri (public ep) should not default to auth\_\* values (admin ep) * Adds help in keystone\_authtoken config opts * python3: Add basic compatibility support * Pass the default\_project\_id when managing User * Use flake8 in run\_tests.sh and updated ignore flake8 rules with tox.ini * flake8: fix alphabetical imports and enable H306 * Ec2Signer : Allow signature verification for older boto versions * Correct mis-spell in comments * Reorganize url creation * Add -u option in run\_tests.sh * Drop webob from auth\_token.py * no logging on cms failure * Client V3 shouldn't inherit V2 * Add discover to test-requirements * rm improper assert syntax * Update openstack-common.conf format * Fix and enable gating on H403 * Fix and enable gating on H402 * Raise key length defaults * Use ServiceCatalog.factory, the object has no \_\_init\_\_ * Ec2Signer : Modify v4 signer to match latest boto * Sync install\_venv\_common from oslo * Flake8 should ignore build folder * Fix auth\_token.py bad signing\_dir log message * Add name arguments to keystone command 0.3.1 ----- * Fix and enable H401 * List groups by domain in keystoneclient * Unmock requests when testing complete 0.3.0 ----- * Use Python 3.x compatible print syntax * Fix the cache interface to use time= by default * Implements v3 auth client * Change memcache config entry name in Keystone to be consistent with Oslo * Fix memcache encryption middleware * Python-2.6 compatibility for tests/test\_keyring.py * Remove endpoint.name attribute from v3 manager (bug 1191152) * Provide keystone CLI man page * Log cms\_verify issues as warnings (not errors) * Cleanup shell's authentication check * Use AuthRef for some client fields * Fix optional keyring support, add basic keyring tests 0.2.5 ----- * Fix unused imports(flake8 F401, F999) * Fix line continuations (flake8 E125, E126) * Fix --version to output version * python3: Introduce py33 to tox.ini * Add find() method to CrudManager 0.2.4 ----- * Check Expiry * Enumerate ignored flake8 rules * Missing command descriptions for 'token-get' and 'endpoint-get' * Suggestion of a new arguments display in the help, to reflect required ones Fix bug 1182130 * Rename requires files to standard names * Default signing\_dir to secure temp dir (bug 1181157) * Only add logging handlers if there currently aren't any * Pass memcache\_servers as array * Allow secure user password update * Make ManagerWithFind abstract and fix TokenManager * Migrate to flake8 * Migrate to pbr * change "int(marker)" to "marker" on user list pagination * Use testr instead of nose * Perform oslo-incubator code sync * Securely create signing\_dir (bug 1174608) * Added Conflict Exception to the exception code map * Refactor v3 API to support filtering * Revert "Use TokenManager to get token" * Restore compatibility with PrettyTable < 0.7.2 * Remove duplicate test definitions * Use TokenManager to get token * Pass json object when invoking exception handler * modify mistake in comment * Ec2Signer: Initial support for v4 signature verification * adding notes about dealing with exceptions in the client * Fix v3 with UUID and memcache expiring * Convert requests.ConnectionError to ClientException * Restrict prettytable to >=0.6,<0.8 * Allow keystoneclient to work with older keystone installs * Config value for revocation list timeout 0.2.3 ----- * Cache tokens using memorycache from oslo * Make keystone client handle the response code 300 * 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 * Switch to final 1.1.0 oslo.config release * Fix auth-token middleware to understand v3 tokens * help text improvements * Switch to oslo.config * update v3 domains - remove public/private namespace * Work better in server env with no keyrings * Remove test dep on name of dir (bug 1124283) * Sync memorycache and timeutils from oslo * Improve error message for missing endpoint * Update oslo-config version * Fix selef to self in class * Save test\_auth\_token\_middleware from unlimited recursion * Use oslo-config-2013.1b3 * Added missing unit tests for shell.py * Allow configure auth\_token http connect timeout * Fix debug with requests * Allow requests up to 0.8 and greater * sync README with "keystone help" * Use install\_venv\_common.py from oslo * Fix incomplete sentence in help * Update .coveragerc * Pin requests module more strictly * Treat HTTP code 400 and above as error * Update requests requirements * Mark password config options with secret * Implements token expiration handling * fix discrepancies seen in domain and credential, v3 - bug 1109349 * Fix how python 2.6 doesn't have assertDictEqual * If you specify the --debug argument, it doesn't show the body of a POST request. The body (string rep) is at 'data' in the kwargs dict. 'body' was deleted prior to this call * Fix STALE\_TOKEN\_DURATION usage * Factorize endpoint retrieval in access * Take region\_name into account when calling url\_for * Remove useless code * Fix thinko in self.middleware.cert\_file\_missing * Remove useless import * Restore Python 2.6 compatibility * Allow request timeout to be specified * Remove assertDictEqual for python 2.6 compatibility * Add name arguments to keystone command * Blueprint memcache-protection: enable memcache value encryption/integrity check * Make WebOb version specification more flexible * Warning message is not logged for valid token-less request 0.2.2 ----- * Use os.path to find ~/keystone-signing (bug 1078947) * Remove iso8601 dep in favor of openstack.common * Move iso8601 dependency from test- to pip-requires * Pin requests to >=0.8.8 * Use testtools instead of unittest for base classes * Add support for user groups 0.2.1 ----- * Make it possible to debug by running module * remove unused import * Bug 1052674: added support for Swift cache * Add file 'ChangeLog' to MANIFEST.in * Fix keystone \*-list order by 'name' * Use requests module for HTTP/HTTPS * Print to stderr when keyring module is missing * Prevent an uncaught exception from being rasied * modify ca-certificate default value * Spelling: compatibile->compatible * URL-encode user-supplied tokens (bug 974319) * Fix middleware logging for swift * Fix keystoneclient user-list output order * Misspelling error in README.rst * Rename --no\_cache to --os\_cache * Make use\_keyring False by default * bug-1040361: use keyring to store tokens * Don't try to split a list of memcache servers * Drop hashlib/hmac from pip-requires * Add --version CLI opt and \_\_version\_\_ module attr * Add Ec2Signer utility class to keystoneclient * Add command to allow users to change their own password * updating PEP8 to 1.3.3 * Correct a misspelled in comments * Remove Policy.endpoint\_id reference * Fix scoped auth for non-admins (bug 1081192) 0.2.0 ----- * Throw validation response into the environment * fixes auth\_ref initialization error * Update README and CLI help * Add auth-token code to keystoneclient, along with supporting files * 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) * removing repeat attempt at authorization in client * Check for auth URL before password (bug 1076235) * check creds before token/endpoint (bug 1076233) * Warn about bypassing auth on CLI (bug 1076225) * fixes 1075376 * Fix keystoneclient so swift works against Rackspace Cloud Files * HACKING compliance: consistent usage of 'except' * Update --os-\* error messages * Replace refs to 'Keystone API' with 'Identity API' * Don't log an exception for an expected empty catalog * Add OpenStack trove classifier for PyPI * add a new HTTPClient attr for setting the original IP * Fixes https connections to keystone when no CA certificates are specified * use mock context managers instead of decorators+functions * Ensure JSON isn't read on no HTTP response body * Added 'service\_id' column to endpoint-list * Useful error msg when missing catalog (bug 949904) * bootstrap a keystone user (e.g. admin) in one cmd * Enable/disable services/endpoints (bug 1048662) * v3 Domain/Project role grants * Fixed httplib2 mocking (bug 1050091, bug 1050097) * v3 List projects for a user * v3 Credential CRUD * v3 User CRUD * v3 Project CRUD * v3 Role CRUD * v3 Domain CRUD * v3 Policy CRUD * v3 Endpoint CRUD * v3 Service CRUD * change default wrap for tokens from 78 characters to 0 * v3 Client & test utils * Manager for generic CRUD on v3 * virtualenv quite installation for zypper * updating base keystoneclient documentation * updating keystoneclient doc theme * enabling i18n with Babel * pep8 1.3.1 cleanup * Allow empty description for tenants * Add wrap option to keystone token-get for humans * switching options to match authentication paths * Fixes setup compatibility issue on Windows * Handle "503 Service Unavailable" exception * removing deprecated commandline options * Require httplib2 version 0.7 or higher * Fixed httplib2 mocking (bug 1050091, bug 1050097) * Allow serialization impl to be overridden * Add generic entity.delete() * Add support for HEAD and PATCH * Don't need to lazy load resources loaded from API 0.1.3 ----- * fixing pep8 formatting for 1.0.1+ pep8 * Fix PEP8 issues * splitting http req and resp logging also some pep8 cleanup in shell.py * Change underscores in new cert options to dashes * Add nosehtmloutput as a test dependency 0.1.2 ----- * Add '--insecure' commandline argument * If no password in env or command line, try prompting * Install test-requires in development venv * add keystone bash-completion * Replace obsolete option in README * 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 * Don't call PrettyTable add\_row with a tuple * Change CLI options to use dashes 0.1.1 ----- * Add post-tag versioning * decoding json only on 2xx success response bug 1007661 * Do not display None in pretty tables for fields with no value 0.1.0 ----- * Drop support for legacy OS args * Skip argparse when injecting requirements * Move unittest2 dependency * Fix coverage job. Turns out you need coverage * Update to latest openstack.common setup code * Move docs to doc * fix bug lp:936168,format output * pep8 1.1 changes and updates * Updated Sphinx documentation * Fix Tenant.update() for enabled=False * Change --user to --user\_id and --role to --role\_id in the keystone client for consistency * Remove printt * Auto generate AUTHORS for python-keystoneclient * Require service\_id for endpoint-create (bug 987457) * Removed unused imports and variables * Include last missing files in tarball * fix parameter name error in exapmle * Drop support for OS --tenant\_id (bug 960977) * Open Folsom * Useful messages for missing auth data (bug 946297) * Updated tox.ini to work properly with Jenkins * Implement user-get based on tenant-get (bug 940272) * Backslash continuations (python-keystoneclient) * Split user-role-list from user-list * Change CLIAuth arg names * enabled treated as string (bug 953678) * CLI shows help without args (bug 936398) * fix bug 950685,make update user password works * Add endpoint commands help text * List roles for user on CLI (bug 932282) * prevent keyerrors when accessing optional keys * Removed ?fresh=nonsense (bug 936405) * Make ec2-credentials-\* commands work properly for non-admin user * Remove trailing whitespaces in regular file * Endpoints: Add create, delete, list support * Clean up EC2 CRUD * Fix --tenant\_id corner case with ec2-create-creds command * Improve usability of CLI * Help output tweaks, Vol I * Move --version to --identity\_api\_version * Remove internal '-' from flag names * Fix inconsistient method names and add tests * Added condition requirement to argparse * Add tenant commands to cli * Display token and service catalog for user * Restores proper PUT method for user update now that KSL supports it * Add license file to the tarball * Fixes user update methods * Use unittest2 instead of unittest * Fix conflicts with shell args for subcommands * Allow --token and --endpoint to bypass catalog * Blueprint cli-auth: common cli args * Correct tenant update HTTP method * Added delete token * Updates client to work with keystone essex roles API routes * Enabling/disabling users should use OS-KSADM extension (bug 922394) * Add limit and marker to user\_list and tenant\_list * Support for version and extension discovery * Implementing a minimal, but useful CLI * Adjust version number to match other deliveries * update ec2 crud responses we test against * support ec2 crud calls * Install a good version of pip in the venv * Modify tox.ini file to do the standard thigns * Added in common test, venv and gitreview stuff * log when no service catalog * update comment to be tenant\_name * should have had tenant\_name * use full name for args in readme * finish removing project\_id * update test env shell * Fix the tests * remove X-Auth-Project-Id, re-add auth by token support (most tests pass) * pep8 * set the management\_url from the service\_catalog * more work on standardizing project\_id * typo in comments * remove print statements and uncomment exceptions * more work on standardization of cliauth * remove user\_id as you shouldn't auth using it * initial pass to cliauth blueprint * Improved error message when unable to communicate with keystone * Improved logging/error messages * adding myself to authors * switching back per docs * fixing up the VerifyAll() bits * more pep8 cleanup * pep8 cleanup * Updated the docs a little bit * Project ID always treated as a string * Cleans up the data returned for a token a little * Fixed a typo... "API" should've been "CLI". Thanks termie. ;-) * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/HACKING.rst0000664000175000017500000000121600000000000020004 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 ======= python-keystoneclient 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=1740753711.0 python_keystoneclient-5.6.0/LICENSE0000664000175000017500000002717400000000000017226 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=1740753785.2710035 python_keystoneclient-5.6.0/PKG-INFO0000644000175000017500000000705500000000000017310 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: python-keystoneclient Version: 5.6.0 Summary: Client Library for OpenStack Identity Home-page: https://docs.openstack.org/python-keystoneclient/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 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: pbr>=2.0.0 Requires-Dist: debtcollector>=1.2.0 Requires-Dist: keystoneauth1>=3.4.0 Requires-Dist: oslo.config>=5.2.0 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.serialization>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: requests>=2.14.2 Requires-Dist: stevedore>=1.20.0 Requires-Dist: packaging>=20.4 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/README.rst0000664000175000017500000000461300000000000017701 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/bindep.txt0000664000175000017500000000113700000000000020212 0ustar00zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed by tests; # see https://docs.openstack.org/infra/bindep/ for additional information. gettext libssl-dev [platform:dpkg] openssl-devel [platform:rpm] dbus-devel [platform:rpm] dbus-glib-devel [platform:rpm] libdbus-1-dev [platform:dpkg] libdbus-glib-1-dev [platform:dpkg] libffi-dev [platform:dpkg] libffi-devel [platform:rpm] libsasl2-dev [platform:dpkg] libxml2-dev [platform:dpkg] libxslt1-dev [platform:dpkg] python3-all-dev [platform:dpkg] cyrus-sasl-devel [platform:rpm] libxml2-devel [platform:rpm] python3-devel [platform:rpm] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1950037 python_keystoneclient-5.6.0/doc/0000775000175000017500000000000000000000000016753 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/.gitignore0000664000175000017500000000000700000000000020740 0ustar00zuulzuul00000000000000build/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/Makefile0000664000175000017500000000617000000000000020417 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/python-keystoneclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-keystoneclient.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." ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/requirements.txt0000664000175000017500000000032100000000000022233 0ustar00zuulzuul00000000000000# These are needed for docs generation openstackdocstheme>=2.2.1 # Apache-2.0 sphinx>=2.0.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD reno>=3.1.0 # Apache-2.0 lxml>=3.4.1 # BSD fixtures>=3.0.0 # Apache-2.0/BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1950037 python_keystoneclient-5.6.0/doc/source/0000775000175000017500000000000000000000000020253 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/source/conf.py0000664000175000017500000001634100000000000021557 0ustar00zuulzuul00000000000000# python-keystoneclient 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 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 = ['sphinxcontrib.apidoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'openstackdocstheme', ] 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 = ['keystoneclient.'] # 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 = 'python-keystoneclientdoc' # -- sphinxcontrib.apidoc configuration -------------------------------------- apidoc_module_dir = '../../keystoneclient' apidoc_output_dir = 'api' apidoc_excluded_paths = [ 'fixture', 'tests', ] # -- 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-python-keystoneclient.tex', 'python-keystoneclient Documentation', 'OpenStack', '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 # 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 # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', 'maxlistdepth': 10, } keystoneauth_url = 'https://docs.openstack.org/keystoneauth/latest/' intersphinx_mapping = { 'python': ('https://docs.python.org/', None), 'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None), 'keystoneauth1': (keystoneauth_url, None), } # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/python-keystoneclient' openstackdocs_bug_project = 'python-keystoneclient' openstackdocs_bug_tag = '' openstackdocs_pdf_link = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/source/index.rst0000664000175000017500000000266200000000000022122 0ustar00zuulzuul00000000000000======================================================== Python bindings to the OpenStack Identity API (Keystone) ======================================================== This is a client for OpenStack Identity API. There's a Python API for :doc:`Identity API v3 ` and :doc:`v2 ` (the :mod:`keystoneclient` modules). Contents: .. toctree:: :maxdepth: 1 using-api-v3 using-sessions using-api-v2 api/modules Related Identity Projects ========================= In addition to creating the Python client library, the Keystone team also provides `Identity Service`_, as well as `WSGI Middleware`_. .. _`Identity Service`: https://docs.openstack.org/keystone/latest/ .. _`WSGI Middleware`: https://docs.openstack.org/keystonemiddleware/latest/ Release Notes ============= Read also the `Keystoneclient Release Notes `_. Contributing ============ Code is hosted `on OpenDev`_. Submit bugs to the Keystone project on `Launchpad`_. Submit code to the ``openstack/python-keystoneclient`` project using `Gerrit`_. .. _on OpenDev: https://opendev.org/openstack/python-keystoneclient .. _Launchpad: https://launchpad.net/python-keystoneclient .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox``. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/source/using-api-v2.rst0000664000175000017500000001120500000000000023225 0ustar00zuulzuul00000000000000======================= Using the V2 client API ======================= Introduction ============ The main concepts in the Identity v2 API are: * tenants * users * roles * services * endpoints The V2 client API lets you query and make changes through managers. For example, to manipulate tenants, you interact with a ``keystoneclient.v2_0.tenants.TenantManager`` object. You obtain access to managers via attributes of the ``keystoneclient.v2_0.client.Client`` object. For example, the ``tenants`` attribute of the ``Client`` class is a tenant manager:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> keystone.tenants.list() # List tenants You create a valid ``keystoneclient.v2_0.client.Client`` object by passing a :class:`~keystoneauth1.session.Session` to the constructor. Authentication and examples of common tasks are provided below. You can generally expect that when the client needs to propagate an exception it will raise an instance of subclass of ``keystoneclient.exceptions.ClientException`` Authenticating ============== There are two ways to authenticate against keystone: * against the admin endpoint with the admin token * against the public endpoint with a username and password If you are an administrator, you can authenticate by connecting to the admin endpoint and using the admin token (sometimes referred to as the service token). The token is specified as the ``admin_token`` configuration option in your keystone.conf config file, which is typically in /etc/keystone:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> token = '012345SECRET99TOKEN012345' >>> endpoint = 'http://192.168.206.130:35357/v2.0' >>> auth = v2.Token(auth_url=endpoint, token=token) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) If you have a username and password, authentication is done against the public endpoint. You must also specify a tenant that is associated with the user:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> username='adminUser' >>> password='secretword' >>> tenant_name='openstackDemo' >>> auth_url='http://192.168.206.130:5000/v2.0' >>> auth = v2.Password(username=username, password=password, ... tenant_name=tenant_name, auth_url=auth_url) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) Creating tenants ================ This example will create a tenant named *openstackDemo*:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> keystone.tenants.create(tenant_name="openstackDemo", ... description="Default Tenant", enabled=True) Creating users ============== This example will create a user named *adminUser* with a password *secretword* in the openstackDemo tenant. We first need to retrieve the tenant:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> tenants = keystone.tenants.list() >>> my_tenant = [x for x in tenants if x.name=='openstackDemo'][0] >>> my_user = keystone.users.create(name="adminUser", ... password="secretword", ... tenant_id=my_tenant.id) Creating roles and adding users =============================== This example will create an admin role and add the *my_user* user to that role, but only for the *my_tenant* tenant: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> role = keystone.roles.create('admin') >>> my_tenant = ... >>> my_user = ... >>> keystone.roles.add_user_role(my_user, role, my_tenant) Creating services and endpoints =============================== This example will create the service and corresponding endpoint for the Compute service:: >>> from keystoneclient.v2_0 import client >>> keystone = client.Client(...) >>> service = keystone.services.create(name="nova", service_type="compute", ... description="Nova Compute Service") >>> keystone.endpoints.create( ... region="RegionOne", service_id=service.id, ... publicurl="http://192.168.206.130:8774/v2/%(tenant_id)s", ... adminurl="http://192.168.206.130:8774/v2/%(tenant_id)s", ... internalurl="http://192.168.206.130:8774/v2/%(tenant_id)s") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/source/using-api-v3.rst0000664000175000017500000001462100000000000023233 0ustar00zuulzuul00000000000000======================= Using the V3 Client API ======================= Introduction ============ The main concepts in the Identity v3 API are: * :py:mod:`~keystoneclient.v3.credentials` * :py:mod:`~keystoneclient.v3.domain_configs` * :py:mod:`~keystoneclient.v3.domains` * :py:mod:`~keystoneclient.v3.endpoints` * :py:mod:`~keystoneclient.v3.groups` * :py:mod:`~keystoneclient.v3.policies` * :py:mod:`~keystoneclient.v3.projects` * :py:mod:`~keystoneclient.v3.regions` * :py:mod:`~keystoneclient.v3.role_assignments` * :py:mod:`~keystoneclient.v3.roles` * :py:mod:`~keystoneclient.v3.services` * :py:mod:`~keystoneclient.v3.tokens` * :py:mod:`~keystoneclient.v3.users` The :py:mod:`keystoneclient.v3.client` API lets you query and make changes through ``managers``. For example, to manipulate a project (formerly called tenant), you interact with a :py:class:`keystoneclient.v3.projects.ProjectManager` object. You obtain access to managers through attributes of a :py:class:`keystoneclient.v3.client.Client` object. For example, the ``projects`` attribute of a ``Client`` object is a projects manager:: >>> from keystoneclient.v3 import client >>> keystone = client.Client(...) >>> keystone.projects.list() # List projects While it is possible to instantiate a :py:class:`keystoneclient.v3.client.Client` object (as done above for clarity), the recommended approach is to use the discovery mechanism provided by the :py:class:`keystoneclient.client.Client` class. The appropriate class will be instantiated depending on the API versions available:: >>> from keystoneclient import client >>> keystone = ... client.Client(auth_url='http://localhost:5000', ...) >>> type(keystone) One can force the use of a specific version of the API, either by using the ``version`` keyword argument:: >>> from keystoneclient import client >>> keystone = client.Client(auth_url='http://localhost:5000', version=(2,), ...) >>> type(keystone) >>> keystone = client.Client(auth_url='http://localhost:5000', version=(3,), ...) >>> type(keystone) Or by specifying directly the specific API version authentication URL as the auth_url keyword argument:: >>> from keystoneclient import client >>> keystone = ... client.Client(auth_url='http://localhost:5000/v2.0', ...) >>> type(keystone) >>> keystone = ... client.Client(auth_url='http://localhost:5000/v3', ...) >>> type(keystone) Upon successful authentication, a :py:class:`keystoneclient.v3.client.Client` object is returned (when using the Identity v3 API). Authentication and examples of common tasks are provided below. You can generally expect that when the client needs to propagate an exception it will raise an instance of subclass of :class:`keystoneclient.exceptions.ClientException`. Authenticating Using Sessions ============================= Instantiate a :py:class:`keystoneclient.v3.client.Client` using a :py:class:`~keystoneauth1.session.Session` to provide the authentication plugin, SSL/TLS certificates, and other data:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', ... user_id='myuserid', ... password='mypassword', ... project_id='myprojectid') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) For more information on Sessions refer to: `Using Sessions`_. .. _`Using Sessions`: using-sessions.html Getting Metadata Responses ========================== Instantiating :py:class:`keystoneclient.v3.client.Client` using `include_metadata=True` will cause manager response to return :py:class:`keystoneclient.base.Response` instead of just the data. The metadata property will be available directly to the :py:class:`keystoneclient.base.Response` and the response data will be available as property `data` to it. >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', ... user_id='myuserid', ... password='mypassword', ... project_id='myprojectid') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess, include_metadata=True) >>> resp = keystone.projects.list() >>> resp.request_ids[0] req-1234-5678-... >>> resp.data [, , ...] Non-Session Authentication (deprecated) ======================================= The *deprecated* way to authenticate is to pass the username, the user's domain name (which will default to 'Default' if it is not specified), and a password:: >>> from keystoneclient import client >>> auth_url = 'http://localhost:5000' >>> username = 'adminUser' >>> user_domain_name = 'Default' >>> password = 'secreetword' >>> keystone = client.Client(auth_url=auth_url, version=(3,), ... username=username, password=password, ... user_domain_name=user_domain_name) A :py:class:`~keystoneauth1.session.Session` should be passed to the Client instead. Using a Session you're not limited to authentication using a username and password but can take advantage of other more secure authentication methods. You may optionally specify a domain or project (along with its project domain name), to obtain a scoped token:: >>> from keystoneclient import client >>> auth_url = 'http://localhost:5000' >>> username = 'adminUser' >>> user_domain_name = 'Default' >>> project_name = 'demo' >>> project_domain_name = 'Default' >>> password = 'secreetword' >>> keystone = client.Client(auth_url=auth_url, version=(3,), ... username=username, password=password, ... user_domain_name=user_domain_name, ... project_name=project_name, ... project_domain_name=project_domain_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/doc/source/using-sessions.rst0000664000175000017500000002027700000000000024006 0ustar00zuulzuul00000000000000============== Using Sessions ============== Introduction ============ The :py:class:`keystoneauth1.session.Session` class was introduced into keystoneclient as an attempt to bring a unified interface to the various OpenStack clients that share common authentication and request parameters between a variety of services. The model for using a Session and auth plugin as well as the general terms used have been heavily inspired by the `requests `_ library. However neither the Session class nor any of the authentication plugins rely directly on those concepts from the requests library so you should not expect a direct translation. Features -------- - Common client authentication Authentication is handled by one of a variety of authentication plugins and then this authentication information is shared between all the services that use the same Session object. - Security maintenance Security code is maintained in a single place and reused between all clients such that in the event of problems it can be fixed in a single location. - Standard discovery mechanisms Clients are not expected to have any knowledge of an identity token or any other form of identification credential. Service and endpoint discovery are handled by the Session and plugins. Sessions for Users ================== The Session object is the contact point to your OpenStack cloud services. It stores the authentication credentials and connection information required to communicate with OpenStack such that it can be reused to communicate with many services. When creating services this Session object is passed to the client so that it may use this information. A Session will authenticate on demand. When a request that requires authentication passes through the Session the authentication plugin will be asked for a valid token. If a valid token is available it will be used otherwise the authentication plugin may attempt to contact the authentication service and fetch a new one. An example from keystoneclient:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', ... username='myuser', ... password='mypassword', ... project_id='proj', ... user_domain_id='domain') >>> sess = session.Session(auth=auth, ... verify='/path/to/ca.cert') >>> ks = client.Client(session=sess) >>> users = ks.users.list() As clients adopt this means of operating they will be created in a similar fashion by passing the Session object to the client's constructor. Migrating keystoneclient to use a Session ----------------------------------------- By using a session with a keystoneclient Client we presume that you have opted in to new behavior defined by the session. For example authentication is now on-demand rather than on creation. To allow this change in behavior there are a number of functions that have changed behavior or are no longer available. For example the :py:meth:`keystoneclient.httpclient.HTTPClient.authenticate` method used to be able to always re-authenticate the current client and fetch a new token. As this is now controlled by the Session and not the client this has changed, however the function will still exist to provide compatibility with older clients. Likewise certain parameters such as ``user_id`` and ``auth_token`` that used to be available on the client object post authentication will remain uninitialized. When converting an application to use a session object with keystoneclient you should be aware of the possibility of changes to authentication and authentication parameters and make sure to test your code thoroughly. It should have no impact on the typical CRUD interaction with the client. Sharing Authentication Plugins ------------------------------ A session can only contain one authentication plugin however there is nothing that specifically binds the authentication plugin to that session, a new Session can be created that reuses the existing authentication plugin:: >>> new_sess = session.Session(auth=sess.auth, verify='/path/to/different-cas.cert') In this case we cannot know which session object will be used when the plugin performs the authentication call so the command must be able to succeed with either. Authentication plugins can also be provided on a per-request basis. This will be beneficial in a situation where a single session is juggling multiple authentication credentials:: >>> sess.get('https://my.keystone.com:5000/v3', auth=my_auth_plugin) If an auth plugin is provided via parameter then it will override any auth plugin on the session. Sessions for Client Developers ============================== Sessions are intended to take away much of the hassle of dealing with authentication data and token formats. Clients should be able to specify filter parameters for selecting the endpoint and have the parsing of the catalog managed for them. Authentication -------------- When making a request with a session object you can simply pass the keyword parameter ``authenticated`` to indicate whether the argument should contain a token, by default a token is included if an authentication plugin is available:: >>> # In keystone this route is unprotected by default >>> resp = sess.get('https://my.keystone.com:5000/v3', authenticated=False) Service Discovery ----------------- In OpenStack the URLs of available services are distributed to the user as a part of the token they receive called the Service Catalog. Clients are expected to use the URLs from the Service Catalog rather than have them provided. In general a client does not need to know the full URL for the server that they are communicating with, simply that it should send a request to a path belonging to the correct service. This is controlled by the ``endpoint_filter`` parameter to a request which contains all the information an authentication plugin requires to determine the correct URL to which to send a request. When using this mode only the path for the request needs to be specified:: >>> resp = session.get('/v3/users', endpoint_filter={'service_type': 'identity', 'interface': 'public', 'region_name': 'myregion'}) ``endpoint_filter`` accepts a number of arguments with which it can determine an endpoint url: - ``service_type``: the type of service. For example ``identity``, ``compute``, ``volume`` or many other predefined identifiers. - ``interface``: the network exposure the interface has. This will be one of: - ``public``: An endpoint that is available to the wider internet or network. - ``internal``: An endpoint that is only accessible within the private network. - ``admin``: An endpoint to be used for administrative tasks. - ``region_name``: the name of the region where the endpoint resides. The endpoint filter is a simple key-value filter and can be provided with any number of arguments. It is then up to the auth plugin to correctly use the parameters it understands. The session object determines the URL matching the filter and append to it the provided path and so create a valid request. If multiple URL matches are found then any one may be chosen. While authentication plugins will endeavour to maintain a consistent set of arguments for an ``endpoint_filter`` the concept of an authentication plugin is purposefully generic and a specific mechanism may not know how to interpret certain arguments and ignore them. For example the :py:class:`keystoneauth1.identity.generic.token.Token` plugin (which is used when you want to always use a specific endpoint and token combination) will always return the same endpoint regardless of the parameters to ``endpoint_filter`` or a custom OpenStack authentication mechanism may not have the concept of multiple ``interface`` options and choose to ignore that parameter. There is some expectation on the user that they understand the limitations of the authentication system they are using. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1790037 python_keystoneclient-5.6.0/examples/0000775000175000017500000000000000000000000020024 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1990037 python_keystoneclient-5.6.0/examples/pki/0000775000175000017500000000000000000000000020607 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1990037 python_keystoneclient-5.6.0/examples/pki/certs/0000775000175000017500000000000000000000000021727 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/certs/cacert.pem0000664000175000017500000000257700000000000023706 0ustar00zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIID4TCCAsmgAwIBAgIUD+MH2KCtZgLgFWNghxF+xZQZx98wDQYJKoZIhvcNAQEL BQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAG A1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsMCEtl eXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQw EgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0yMjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1 MzUxOFowgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES MBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsM CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn MRQwEgYDVQQDDAtTZWxmIFNpZ25lZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAL/+AsUcqyF2b/3gaHGtUZx+mReX2QMv+gXmW2KUj1CTmSTxGXCaeoJ5 N7PA6BeBD/HJVqTgCo/oNuHmOgtYrgRngyWpABItt9ONRmTCr2AvA23AZIjfUdwR ZceRHf67H6N1NOttr8IFkuQFhTAKuRHJGcXNqNMJrNv2v5ha3GNeAhxZd965ok9B GSd+hvibjZ2mDBZ8kiJ9BGf53TDie/zg+q5CkgqLArgR30pGCe+ZLXPLrhekpyet BR3guKTV2PMCeIh7Yg/uTJMe22qZ87M6Q1DosSKC/1l+/1ArBve6msc8JEElnc32 HJ7NuTTJreZKEvPmUI2oTcdvokWXtRsCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB /zANBgkqhkiG9w0BAQsFAAOCAQEAmdWRPzpI5pAoVn9GX1KNgiN/e9oCpnHYIofV fX7i70OBYOwBoyQhFqniHtGH4uqYIxGJdbDtHzsSYCSV1mGqHhK+kStLy4MULUUV cNM5yDYDPEtjgJy7G90z/ksX5WuXQgktx9N8emdI6yH8C1b6sHMtHcfnb6O0waMH HQ9QpZFQapwuIjeWU0zRDFZkdEAkx6wfVuoMhHOjy1WRAuIOL2ELa6h0GL2d+bmw x4Xpyi4X7pgixz3l/9Kfc6VdVrEy4H2bhldUeZ0WjzvMdYaw953+C/5YAfFYanCH en9BebSKQMv8QI0OrNyTefMXuxWvcKSOWjQVfRk1ckz6aIrfSw== -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/certs/signing_cert.pem0000664000175000017500000000245600000000000025114 0ustar00zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpTCCAo0CAREwDQYJKoZIhvcNAQELBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQK DAlPcGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0y MjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1MzUxOFowgY8xCzAJBgNVBAYTAlVTMQsw CQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3Rh Y2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMREwDwYDVQQDDAhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAJ7Gc/CGy82PWOmlGD+C8d6Kw3Q1ZodOz9EduhXecfLN +zHXd9Qd35f5hHf4c8j0yAxI8qfeabjnOefEkoHC4W+DTTsUwq1x7FvAPHbTncSH zePqBE8wKp4pfFuZAhMxP55nTJC/N8t1M1lUqYTANgTCAystyOJuLuMc03UBqO8b OGzCJsAdcsBPpdYkfycrWv5ZosdaHf3OmakPtWymjSPQ8/lM4x5Fm5GaoYUqb9mo busMp0te7MMkzWYilSqZBWHx7dsGR7HoN4zMqalttC0inJGc0wnusNrkeb3ieuXw U6T3V3pE3yTTuHy6HcZZd/8m3O/L9F1odzDnUH10PjkCAwEAATANBgkqhkiG9w0B AQsFAAOCAQEAiSaPtpERflBUDYtRrAPVEyM3K9DJyZ8pv1vQxCU/h4ZNttVWsRdC gqdZg8nYSLj81ZwU1OATQhjXjGn9/mYAIzbm+HH1TMJDWqmnkSblAHGPZmswKmga /Cns8PsgsLcMV9BA38lyBhVtgBn4QgsG9EUvscZvVUnxevgqg3a/tlfpPf7fvbmC Efcq3liI/l+wxv4O3ET3V6rBZsmTUMNrIIhqFcicynUy3NIIRO3mL92se9X81Jpj YxHtMt+RakM0P7yRYL2hjgQW2srssGlMt9U/OIEZQKJVBH85qYuoBAcXC7Y6xRy1 LvQc4IKf3X4hmqZC7jhBIQAAbaDZTn8peg== -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/certs/ssl_cert.pem0000664000175000017500000000245600000000000024257 0ustar00zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIDpjCCAo4CARAwDQYJKoZIhvcNAQELBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQK DAlPcGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZDAgFw0y MjAzMDcxNTM1MThaGA8yMDgwMDgyOTE1MzUxOFowgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3Rh Y2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQD1i9+ydZSypNAkkVXdzIqZ8E62cqH7i0JGVGBuGdH8 ZVF3MDcbi8VwqfNRWoWn9mrJUp5HYDV9t5WXz25Ej4EnqlJ3WLZvC1e+ldDIInmi ULic3iIAgrWbumU3XNLHska/smoVJuLIuFUxEfRpwGOpguOzAO1M6BKCSwr+TBLY JZxc3F7v1vtwNhisyE5S2H6Q49K0UXHTPjp+fLZAHQ5+Yxqwf0KAJqAD3vMo8Ewx XlgJu8pQyjjxwtrwnN2WHYoJGt/OOdkLBbzdupWH9CGcxeVc5hSJ1hEKuYS8AOZI eH6q2MKwT+QiepBsVfuy1JFt4raLht/RcX/WR8lIAzoFAgMBAAEwDQYJKoZIhvcN AQELBQADggEBAArxwP6I5XXIl3Dhkkt6gegRNc1vYWPEIDkKqggnvntZAOZwavVQ kiydT09io82SjD3qv/PQFH+N1KkTCIgYreHQpCQaWMvkpCD2iEcu9R75p4rnZMR2 NwIlj4BHvXIo9ET5dkhUUxzUGK7eIymNEoMWMF6OGlQhK1FV3Tvjum0sqLOyKOgr NFxDv7qFzoKfqjY3lfb9yqO7xC1t3CZSOsBLIaUQ9SBoRJ11UNYGq9ZXHNF3cCbC PyE1TgVjNEvWBBRY0ofGoPmdqrTe2oZ6rAFKf0aWJ1qIq+umePp9R8ZwWLonbxQ4 0nqaUI5AOAdsRpUJHGvW0mmMALYjHT+tF8U= -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2030036 python_keystoneclient-5.6.0/examples/pki/cms/0000775000175000017500000000000000000000000021371 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_revoked.json0000664000175000017500000000575700000000000026162 0ustar00zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "revoked_username1", "roles_links": [ "role1", "role2" ], "id": "revoked_user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "revoked_username1" } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_revoked.pem0000664000175000017500000001162700000000000025763 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIOVQYJKoZIhvcNAQcCoIIORjCCDkICAQExDTALBglghkgBZQMEAgEwggxaBgkq hkiG9w0BBwGgggxLBIIMR3sNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDM4LTAxLTE4VDIxOjE0 OjA3WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInJldm9rZWRfdXNlcm5hbWUx IiwNCiAgICAgICAgICAgICJyb2xlc19saW5rcyI6IFsNCiAgICAgICAgICAgICAg ICAicm9sZTEiLA0KICAgICAgICAgICAgICAgICJyb2xlMiINCiAgICAgICAgICAg IF0sDQogICAgICAgICAgICAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsDQogICAg ICAgICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAg ICAgICAgICAgICAiaWQiOiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2 MzEiLA0KICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAg ICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAg ICAgICAgImlkIjogImYwM2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwN CiAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAg ICAgICAgfQ0KICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInJl dm9rZWRfdXNlcm5hbWUxIg0KICAgICAgICB9DQogICAgfQ0KfQ0KMYIBzjCCAcoC AQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTES MBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sxETAPBgNVBAsM CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn MRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEwDQYJKoZIhvcN AQEBBQAEggEAGUm9+Jb4P4dO23cAg39q0vVDFPkiPxgxakKE+g4d7VSI7Krt5ypB iXo4mHbLqM28zrxgCBxnq2ZhhGzk/qhVWadYWUQQ9FjUBna06Cbd5clGpP8Kp2yk +SRydFZAw9jUviditNhZA7Nhl8Qzu6T9TB+jPI2y1PY/XXebN97dQqmc+QMGVfzz ssU+89ASfki8vEDJWxn2RtxjaXPKeWFUw/qrvj1RkDdI3d22dOTR9p0q7Y9CKLam Y1DkosGbBqUF8hTtJMXIfHTLh5Zp+zz1dlsY0FR9kxLKxKya9OG3ACyidL7ewM9r pEz2NQztAoi3Guxj6793G0Sfgb0ZCTGcaA== -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_revoked.pkiz0000664000175000017500000000404100000000000026147 0ustar00zuulzuul00000000000000PKIZ_eJylVkt3ozgT3etXzD6nTwBjJ17MQjyMRSzZYAxIOwMJIB524gePX_8Jk0xn0n2me77xxlCC4ureW1X69k38NNNC5A8db4ebbwAjhHaQ2k8HhrJrTKAT6wcRczxd1w1Jh47Z6h5caunuzUixbnFd10rJ0rev1hZFE2A45hLuRbBQzTRlT8-dnVGFlPEE5-tce0C1e90r_gXxQyrWjvGEyCxwX2joiHWYA8xhg3OpwVupXS-cDnuHlhiHhsiHfKXDnIVZsw_tMu7QDOmowwZWVx5sV56p-gZqwZqb0prDSZCjE9LtI9OHB-0mshacBdk1stwyHtckFkyzqHZGZJV_oYF9Aiy4BaS49svhi_tghJZYwwNTKVTKAm9vCcS9XA5bEdsqo2pxSRbzCxiC7w8ULCQ8rsomscprlAsk1lSOrHb-sm3ES4KXmh2p4hs0dLPImtdDMhBMTrmAVsTW_BjVbj8EhxgN3PM-mPq7orkh2i9dKTYO11VvdqRTmxWHF8GXCkgfK6sJbVc9lShnFVZYThUs497pSbBTqUcbVpFqbZQ55WLFSzKUD4jskinlFdyg6nbHguQYKdNNVO3ykYupxMJh3-0_ogADjP8fhSYDWrVHKvtbb5TvkCzdZp0_XrGHJrd96mq75umE9PSacPNKuJuTivassjntdz0gBpaZl2WEaxVVqLoO7Jxw2hLFzF98aVBHSOY2kVJekiV5iazysh9dGoVCHSA0ncbWbtS-mp-SQesBXiVME3yJ1yLh8u-qgX8p2xTzohv4-lACDFL8FyXAzzNr8u-RW3Rg7aGB3d8i7DNf-0DOmLLLwQBVFMaZbW9fqkUVXqhYGPwv6v9TwjHR0C-YJR-jckQH_ll7Z0B3wdtHhVhIYVw4oCKceFjCvV-uLVMB2GKc8XRKK6QQbk7oWJnvhKo3uHHl1_tgfvGU6bvEApHldwL5Cfi-jW81XmVSsoSzVffYYh4PKIR05mxtiCamzxW8VX9qdfArr_9KDfBv9vvjdu05uMkj-htbatfBOLE8P4n_t1sTXZyTQaVkWTbvKvFIkZskdP-yO_jwe1TNlSHjSh8b5l8Jx0QitiiioLx85Qx8JQ2LEiVeLLaDROxHRXZfFAGfJfmVIj9J3oBE9PZ9QH5VhTI2YCNqpRP3f7M9-D3fizlQu8dkWeRfrP8GWFj2iTW_MEHg-KLfsxC9z8Xh-vNAsUvRXN6G2ZiEYk68q_DRHMQY8_tQaY9RdR7nQ2d3kdJ-DJ7xOreTzxMMCJ8rkXIu2WIux4rffRpltxe-y1gWI8G0EVYuqJdVwly9mM7OlHI7I71oqnxRYS9WqJeIxorbr70xFr2ReeZHqd8G8VDOFTZIxSxTZTzLcI-kdYA66sWiPlDLhGVJJWwrmviPQ9YWk8nadaiWky_sdixkw8GiCCfficSC6JdQatN0-SRONlqbIg1AT9eOhq7V3HzCMLWgvDM1F2vEM1cYFuN9hnXfx63eQ1tLia_B1IMF0bCLGmBCaviOszSb0kuC6eU5ZGJ071qTQ2d8-ODpu3kjZoGXiEPHvjddDB9vifUWI7BV_Gk8ca-ilbe2B7mWFq9ZkVvzRtJ0xwwW1bl8Dokk2n3pWLdE_S1RN73GVdyChQG345ewp8ukjCya7pSyjiq_gOnwtdjStqc1bNAew--nM3E406Czg0ATZMAFn0pmu102GdE2eWhrLybzSqvOEc8n8LJlq0g9L06bbtIfD1acv21OyvLk6kb3Bp4QtCYpT6PzDLZP8n5Wwf1dc7w7blCXcsuzZEWPC3y_UFf1RZVbQ1XWj538TKM7PF89WkDG98-hu9laucfd3RVqao5fpSe-Wbjqw7qfxcfkGDMyu3cbVWXO9m55ThBaeQQnC1p6BKiBVOuHh24fsocHLV3fvzqlVlPJC-zjYfase-fr9IyuxdWfNefEhnpyj9y7N_rcsOeFaGOgxmdovBYUBqbjPhQPrSvL-LHbrifzqzQld_3GOLGNUt_Npe40zarJpFyJOb905oi60SsEslMv7oOYuA9v_Cl5aJhHZhMEX7B9-BPcjtMmMb4frf8Hm6bNOA==././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped.json0000664000175000017500000000573100000000000025770 0ustar00zuulzuul00000000000000{ "access": { "token": { "expires": "2038-01-18T21:14:07Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "user_name1" } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped.pem0000664000175000017500000001156700000000000025604 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIOPwYJKoZIhvcNAQcCoIIOMDCCDiwCAQExDTALBglghkgBZQMEAgEwggxEBgkq hkiG9w0BBwGgggw1BIIMMXsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDM4LTAxLTE4VDIxOjE0 OjA3WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg ICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xl MSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAg ICAgICAgICAgICJpZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMi OiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQi OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgICAgICB9LA0K ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogImYw M2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAg ICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAgICAgfQ0KICAgICAg ICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAg ICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgGA1UEBRMBNTEL MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxEjAQ BgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3RvbmUxJTAjBgkqhkiG9w0B CQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMMC1NlbGYgU2lnbmVk AgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQBYn04AtTif863EsRze H4qwhQwcDMEy9+tqm6Pof1ysl7wnqtyeJbUaEmljzYOeiYvcI9tOAj/beTpxbU3A 6vo0XkPt82Fdn3tPu2Rbr5cmCWkqRUAazEXBwfwPFBeA01Th2yxtTNUeiZJyjbcO 6DfeXeQSQWkblB375+2RQNGOuEbU1JwC8sErk8eTetA419fh+tn5m3h/FhHRiWGo 2NWy9HRx0OjCnnjNtIWY++QbVLQS7lty/f3E1c3l8ebGhBXleEmWbpp1zUi9e5oK 0NlVB+nwiw9qkzAgX5ForSaFGkGlHjjAtoIs/i3DgP+3ET/pZkislu6NpiXfklY4 o35g -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped.pkiz0000664000175000017500000000375500000000000026000 0ustar00zuulzuul00000000000000PKIZ_eJylVkmTmzgUvutXzN2VCotxm0MObMbQSG52ixtLG5DBdrttC_j1I3An6XRSk8wMVS6jJ_H0vfe97dMn9qiGaaG_NOiPi08AWpa1KbH9eEys6pYjxc21I5M9Dhp7ck1xjU4LlLVahme9hJpJNE3d56bmv5i-lYlAd421kjIhKY2yxNxzb1dYQE0uwnpTqw_WwbulQnS1yLFke6dcRHwSezu8ddm-UgNIFAprjkKf6zYrt4fBsUP6kSL-WDuaUifbiqZbu8l7a2FpVg91OHcCpXMCYx7pVgc2xOA2RBHj2nq1NPuUaONBm2bmiiRxdctMr8nve1wSS1V2cO_I2uiKY_sVJPEk4PJD1Iw3pvEdWmGOByRuKzR76E8K2JpvRlOYWU3Wrq7FSr6CUfh2YJ9sEcnbhhZmc8tqhsSU-Mzs5J1P2UfML4fkhIVIx1uvykz5MCoDsfhaM2jMr_IpO3jDKBxlOPYuaSxF4Z5OiNK1x-X68eYMRo_6OXWIcmX-mgM05IIj4s4ZMIdJ0kIhqbEAeTi4A4rDOQ4wTVrUbvSmxoTtBEVl1SMiu0mE5gYmqJrdJ3FxygTpKWvD-u4LiUu2o93dP6IAI4z_jkLlAW67E-YjP7jTdyzWHt3UyxsMLHGyU5t3G1KKaMC3ghg3RLwatXhIWpvgIRwA0iGfBFWFiNpiAc83sV0jgjskGPUu4kZ2GGUezYTmWqzRLjOba3qP0mzL2AGMUyk3wzv3rfxajFyP8FoWNPEH-YEpXP_IGviXtEmQ7PvRX1-ZACMV_4cJ8GvNKv9nzt33YBNYo3f_yGHv_ZXGfJUIYQ1GqCwxLok_3XRgWXjFbGOMf5b_7xTeFY31IjH5U9bc0YF_5t4d0V2hvxSQaQkJYRHQIoICyMEhajamIQBoJiQhpYRbS0DEEPE9M98cOp_g5m10SGP5GgjSG8UMkRn1DPkriCIbTjneVlyxVhZOv-wgyUcUjDpjsdFZEdNkAfrzX4Y6-F2s_44N8G_s_dlcWwYTPay-JWv1NgZOzsuv7P88FdHVpRhZKtYNfWOJZAJPi633LdzB13jPWlkYNTravWB-U3hXxGSrfRY3148-Ax-dBlmKoiBn5lhM9jMj4QdGwHtKfsfIL5RTULDansbod1nIQ12hLFd6tv4h7MGfxT3rAwfvVKz39YfQP4Nk2wyFKV8T5sD7h9GQbK23vji-v28o03o3KQiMSRnIWbVhDeUHBKxQsJYWfi0a43tvNdz71sfnQtSPXQu8daU-E7rmO2XN_u5MTFnY7nFQtSyQBkhcCRO7QgOrn2TVwiAXAA4KVkRh97EOTsgYzLe0_npzC3XUJqYxT0hVwcHiwCa2ehzkLBesLmHhiVoWoqxg_9xQ30w58MV7R4Lv9ky3d-yAvAtMTcmPtCzXplIaKrTMPfs9Q_dINQXrkeuuDGrw0H2lQHMngWlQOwoHw4HK3lT40NBUqLmc0RlEcdUSRaqSB1qE-KyVpIIFHTPPh6pigulwBe1AVJusQRyO0Rl6BtXppNgxaOV8ozowGqjBb_MRG49soHg4ZjOQlIveLWsjJRsVHe6KnFbuk8EIoWpNqJQOOqEQvSa1GqRxcWXDicYUGFSlePVI57pSHanuvp_YDFV1FTZ8GcrR8fnhBZ6Ka4w_z1waumEnBQICw9PlSQwpmuOXQEtDY1bOTjj9LPl8uh4cdbkwrm2OWkvkdNecc8q88Qq03ivo7FKXPokgTtSDB88PHJnrPsGz2usVbDw-n8O63D4f3dNgPKk7a_biR4EmIrWSU4srF7vhtnDD54cdmMmZX1mv-7amy94ZxKBOLw9pcsj4gX19SR_oSrDzM5JO9SyfyVJcbZ6e02A2S-OLTC4FkJf-jddP9bBYPl1m5Voqs3WnWhDXnVq-SMre4j9_3qsCryp28xwfnzDPZaQ4s5GkWyzkGNyiy9Z9sG_4Obsttd3jUhXFonIWc_9Y53m3ibLSDg2P1fIjuZ1Z3WnDTVPBLjy2p8H98gVME7OB9O_T89-mTsWe././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped_expired.json0000664000175000017500000000573100000000000027510 0ustar00zuulzuul00000000000000{ "access": { "token": { "expires": "2010-06-02T14:47:34Z", "issued_at": "2002-01-18T21:14:07Z", "id": "placeholder", "tenant": { "id": "tenant_id1", "enabled": true, "description": null, "name": "tenant_name1" } }, "serviceCatalog": [ { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "volume", "name": "volume" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1" } ], "type": "image", "name": "glance" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a" } ], "type": "compute", "name": "nova" }, { "endpoints_links": [], "endpoints": [ { "adminURL": "http://127.0.0.1:35357/v2.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" } ], "type": "identity", "name": "keystone" } ], "user": { "username": "user_name1", "roles_links": [ "role1", "role2" ], "id": "user_id1", "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "name": "user_name1" } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped_expired.pem0000664000175000017500000001156700000000000027324 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIOPwYJKoZIhvcNAQcCoIIOMDCCDiwCAQExDTALBglghkgBZQMEAgEwggxEBgkq hkiG9w0BBwGgggw1BIIMMXsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDEwLTA2LTAyVDE0OjQ3 OjM0WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICJwbGFjZWhvbGRlciIsDQogICAg ICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJpZCI6ICJ0ZW5h bnRfaWQxIiwNCiAgICAgICAgICAgICAgICAiZW5hYmxlZCI6IHRydWUsDQogICAg ICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogbnVsbCwNCiAgICAgICAgICAgICAg ICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAgICAgICAgICB9DQogICAgICAg IH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIHsN CiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzX2xpbmtzIjogW10sDQogICAgICAg ICAgICAgICAgImVuZHBvaW50cyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0K ICAgICAgICAgICAgICAgICAgICAgICAgImFkbWluVVJMIjogImh0dHA6Ly8xMjcu MC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIs DQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSIs DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxVUkwiOiAiaHR0cDov LzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2 MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJwdWJsaWNVUkwiOiAiaHR0 cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODli YjY2MTdhIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAg XSwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJ2b2x1bWUiLA0KICAgICAgICAg ICAgICAgICJuYW1lIjogInZvbHVtZSINCiAgICAgICAgICAgIH0sDQogICAgICAg ICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtdLA0K ICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJodHRw Oi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg InJlZ2lvbiI6ICJyZWdpb25PbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAg ImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAg ICAgICAgICAgICAgICAgICAgICAicHVibGljVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6OTI5Mi92MSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAg ICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UiLA0KICAgICAg ICAgICAgICAgICJuYW1lIjogImdsYW5jZSINCiAgICAgICAgICAgIH0sDQogICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50c19saW5rcyI6IFtd LA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJhZG1pblVSTCI6ICJo dHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog InJlZ2lvbk9uZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJuYWxV UkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1 ZThhNjBmY2Y4OWJiNjYxN2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInB1 YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNj NTM0MzVlOGE2MGZjZjg5YmI2NjE3YSINCiAgICAgICAgICAgICAgICAgICAgfQ0K ICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgInR5cGUiOiAiY29t cHV0ZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAibm92YSINCiAgICAgICAg ICAgIH0sDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgImVuZHBvaW50 c19saW5rcyI6IFtdLA0KICAgICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQog ICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJh ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YyLjAiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w LjE6MzUzNTcvdjIuMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAicHVibGlj VVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6NTAwMC92Mi4wIg0KICAgICAgICAgICAg ICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAi dHlwZSI6ICJpZGVudGl0eSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAia2V5 c3RvbmUiDQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJ1c2Vy Ijogew0KICAgICAgICAgICAgInVzZXJuYW1lIjogInVzZXJfbmFtZTEiLA0KICAg ICAgICAgICAgInJvbGVzX2xpbmtzIjogWw0KICAgICAgICAgICAgICAgICJyb2xl MSIsDQogICAgICAgICAgICAgICAgInJvbGUyIg0KICAgICAgICAgICAgXSwNCiAg ICAgICAgICAgICJpZCI6ICJ1c2VyX2lkMSIsDQogICAgICAgICAgICAicm9sZXMi OiBbDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAiaWQi OiAiZjAzZmRhOGY4YTMyNDliMmE3MGZiMWYxNzZhN2I2MzEiLA0KICAgICAgICAg ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgICAgICB9LA0K ICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogImYw M2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAg ICAgICAgIm5hbWUiOiAicm9sZTIiDQogICAgICAgICAgICAgICAgfQ0KICAgICAg ICAgICAgXSwNCiAgICAgICAgICAgICJuYW1lIjogInVzZXJfbmFtZTEiDQogICAg ICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCBnjEKMAgGA1UEBRMBNTEL MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxEjAQ BgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3RvbmUxJTAjBgkqhkiG9w0B CQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMMC1NlbGYgU2lnbmVk AgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQCFiZDCR4kvDWvNVo3l 306T6uMi+CuLklTr9msrA4rhRDROZ6N8y/0TrpnBInhJC9A5OGnRgiAPFrg3ksLP ONqqtdQSgNLnzVjauJzVvejzOIYtnp6quQzxy+B1xS/QX+8ODgxz8PcvFszWDvBx qDi/q3XAU2fvdRkq96WBGkqjAOb1pxHA1WbOklcjAm/PL5qgcFc+aryiVvPVBjFy 1KfOZjncXtDBB0Bz5+7MxAxej7LhRZ/eqlK2A/mn2vIlvPKLTGdfuQ10aIBtJ5lW cP2miCjk179e2OU71eJdpJk1bFBXNoNbeu7dhI6/W65SKo/EbEgLO07NXW4qqcVQ vnt9 -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_scoped_expired.pkiz0000664000175000017500000000375100000000000027514 0ustar00zuulzuul00000000000000PKIZ_eJylVtuSmzgQfddX7PtUKiDsGfOQBy4yhrHEcLd4MzAGZLA99pjb16_Ak2QySW2yu66iDC3RnO5zulufPvGfigyT_KVhb3z4BLBpmnZOrcdjbBZNShQn1Y7c9jho_JdqioM6zVdWah6c9RxrBtM0dZ8amvdieGYiAd1BK2XLjSxHeU6F594qKCRVKuHSLtUH8-A2WxheTXbM-doplYgYR-6Obhy-rpQAM6XFpdBiT-jspdNj_9gR_dgS8ViuNaWMN0W73VhV2pv3pmb2WEft2lcgv_pQRwKwmSPZDAtRaV5MzTrF2rjRahNjyeKoaBLDrdLbmhBH8yI5ODdkdXilkXUBcTQZhPQQVuMXt9ENWmaMG-bCBlZ77E0O-LNYjaHwsKqkXl6zpXwFo_Ftwz7eEJbWVZsZVZOUHIkxFxOjk3dey1_ieTnEJwpDnW7cIjHkw-gMRNKl5NB4XuVTcnCH0TjaaOS-bqN5GOzbCdF25QqpfmzWA-pJP2vXTLnyfM0AGVK4lmi3HqhAWVxjGJcUYhEPzkCiYEZ92sY1qW29KinjK35WmOWIyKpiWDVggqpZfRxlpwTOn5I6KG-5mAvxZoy7-0cUYITx31GoIqB1d6Ji6Pk3-o7Zym3tctFg35SmOLVZZ7NcIgNtMoYawtyS1HSIa4vRIRgA0bEY-0VBmFpTSGd2ZJWE0Y5AVO5CYWSHU-a2Cayu2YrsEqO6bm8qTTacHcA5nadGcOO-li_ZyPUIr-aiiT7YD9zh6kfWwL-kbY7Zvh_z9ZUJMFLxf5gAv_asin-W3H0PbN8cs_tHCXufr20kFjEMSjBC5YXxGnvTlw68Cq-UL4z65_X_zuHN0dgvYkM8JdUNHfhn7p0R3RV7C0gME8aMK6AmjPhYwENY2QaCABsxi1k-p7UJCUMSvVXmW0JnE9y0Dg_bSL76cP5GMUdkhD1HfgFhaOGpxutCyFbK_bpfdJilIwpOHbq3dd7ENBlib_ZLqYPfaf13bIB_E-_P4VoymOjh_S1eqc0onFSUL_z_PDXR5Ws2spStqvaNJZZAsc027je5g696T2oZjh7X2q1hfnN4c8Rty30SVdePOQMfk4Z5iRI_5eGY3PYzI8EHRsB7Sn7HyC-ctyDjvX0bkd9VoYh1peW10vPnH2QP_kz3fA4c3FO22pcfpH8G8aYaMkO-xjyBtxfDId6Yb3NxvH8_UKbn3eTAR5MzkPJuwwfKDwh4o-AjLfjaNMb73qyE96NPTGHYj1MLvE2lPoFd9Z2yan9LJm25bPfUL2oupAEzZ06ZVZCB90-2rLGfQkD9jDdR3H3sgxMyDvOtrL9-ucY6qWMDzWJWFHgw-XSOzJ76Ka8Fs4u5PEnNJcob9s8D9S2Ug5i9TyT4Hs_09Y5vkHe-oSnpsc3zlaHkSMWmsefXM3aOraZQPXScJWqRiJ1LCzRnMhiotcJgQGus7A1FDJCmYs0RUIeY4qg5CVUl9bWQiEk9n2dcdDw8D6uKAabNBbZ8Sa2Sigg0ImfsolZvJ8dr1Bbrb1T7qMIa_nY-4scjCygujfgZaJ5KbpPUoZKMjg43R-ta7uMBBVg1J1RKh9cBDC9xqfrbKLvyw4nGHaBWbenysZ3pSnFsdef9iQ2pqqPwwxdShifbKSSRDulneAkzL_1s95acEXAHC8PNXUdeP_Qrz9qeXvW6znWfvOrq4irIun8XtKKPVnfijJEHMs9DT9RWUmE9KjynDFjbJfPxS7Mx0iWWFs9RWj46L3Z3V587XM1PWwNCXoTJ2UNDv1d9vvuz9tLXV_kxDWYYgqHYd8_N6XzoH2b-xWpw0y_gJtAsxErjRb5G4RbKz_YBO2VeLXPDpyxSbVs8N2ZTBVgCiztMAyErXrdbb7N_Me8fcjVnq9dBsKTF4alod0-SuyzE3LNe81ZdFU6TCv48WWXN-hB7O_B8DmemaSyebLh7CO6kaJFerYooCIa2zek7UjVGq6ck30Okq4_3s0OV2WpyLwkwwsqXL2A6MSOifz89_w1E1sSW././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_unscoped.json0000664000175000017500000000132600000000000026327 0ustar00zuulzuul00000000000000{ "access": { "token": { "expires": "2112-08-17T15:35:34Z", "issued_at": "2002-01-18T21:14:07Z", "id": "01e032c996ef4406b144335915a41e79" }, "serviceCatalog": {}, "user": { "username": "user_name1", "roles_links": [], "id": "c9c89e3be3ee453fbf00c7966f6d3fbd", "roles": [ { "id": "359da42d31c04437a32812aeb79e9c0b", "name": "role1" }, { "id": "581af19726fa4af5bda745789ab2bf2b", "name": "role2" } ], "name": "user_name1" } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_unscoped.pem0000664000175000017500000000336100000000000026140 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIE/gYJKoZIhvcNAQcCoIIE7zCCBOsCAQExDTALBglghkgBZQMEAgEwggMDBgkq hkiG9w0BBwGgggL0BIIC8HsNCiAgICAiYWNjZXNzIjogew0KICAgICAgICAidG9r ZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMTEyLTA4LTE3VDE1OjM1 OjM0WiIsDQogICAgICAgICAgICAiaXNzdWVkX2F0IjogIjIwMDItMDEtMThUMjE6 MTQ6MDdaIiwNCiAgICAgICAgICAgICJpZCI6ICIwMWUwMzJjOTk2ZWY0NDA2YjE0 NDMzNTkxNWE0MWU3OSINCiAgICAgICAgfSwNCiAgICAgICAgInNlcnZpY2VDYXRh bG9nIjoge30sDQogICAgICAgICJ1c2VyIjogew0KICAgICAgICAgICAgInVzZXJu YW1lIjogInVzZXJfbmFtZTEiLA0KICAgICAgICAgICAgInJvbGVzX2xpbmtzIjog W10sDQogICAgICAgICAgICAiaWQiOiAiYzljODllM2JlM2VlNDUzZmJmMDBjNzk2 NmY2ZDNmYmQiLA0KICAgICAgICAgICAgInJvbGVzIjogWw0KICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgImlkIjogIjM1OWRhNDJkMzFjMDQ0 MzdhMzI4MTJhZWI3OWU5YzBiIiwNCiAgICAgICAgICAgICAgICAgICAgIm5hbWUi OiAicm9sZTEiDQogICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICB7 DQogICAgICAgICAgICAgICAgICAgICJpZCI6ICI1ODFhZjE5NzI2ZmE0YWY1YmRh NzQ1Nzg5YWIyYmYyYiIsDQogICAgICAgICAgICAgICAgICAgICJuYW1lIjogInJv bGUyIg0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIF0sDQogICAgICAg ICAgICAibmFtZSI6ICJ1c2VyX25hbWUxIg0KICAgICAgICB9DQogICAgfQ0KfQ0K MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD VQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sx ETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu c3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEw DQYJKoZIhvcNAQEBBQAEggEAYIiuAmAxllZ5FdGlJWu0bxAsxwFb114lZAq2/mju v2Hu9505N4TbkJJI++ojNwv1extCAL0H7jmM2nbPovPa0R6RVDRgh1R03E+uLNld rBXyiTeZh2OrbmoXdHAXdJRnBkLIfjDXISY5qN/yJhsr3g6djekGnkWzZfwtwjyb qBbVVB9oK9p5svd85jiOcGlcBxuBhWeqwZiYTzyDtJ2SuwtvBaIDQICyS9VtGI+F NzqtdUHw/vxsQqOq6tR4Qf32wtZnUS+komQWX3uJXwYpSZHomuGPbs0XolGXYm8D fM/7R9AH6CUlmBmq/WVQdEMMv9VADaP9yHuGvM8w3f6ZvA== -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_token_unscoped.pkiz0000664000175000017500000000234100000000000026331 0ustar00zuulzuul00000000000000PKIZ_eJxdVNmWqjoQfecr7nuvs5pBW30MIWKiCSKT5A1QZqQdEOTrb7DPHVmLIVWVqr03Vfn1S1w6MjH7A1JnWvySKMZGa4dk23KcPxMG7AS2wlaVEILZDAIbDdAFGz3zbkZGoTnZo5kJnavp4FiTDBttQCSMfImyzIzPL5KHKqsTjRZWoS_w5fCMVL_DZZsJ33eiMYUHhzQ82sIPComWoKeF3FNHHqy1_aJuOzCj7ZnSFjsICn7M--hI6uSFvzDEwo9eOxfMdi7SfAMpklVSRdxyUOA7huSbw3dgTwOvpyMpLbdSeRDKzABqWCLxpiNzq4EFSBYxmmQ5ZDVVSlT_dWrqknssP5nre6wmbwqp02f44o_8iH9Tmr5JFwZKPdGSfhvSuFk_uIvesJNmdedHlsZm3UU_WsTHKVFTV9Mm3NB5OGZz7rJCEo-au7ZCVV5woUc4JnNW8oY19sgbUuFiQkCesemP0-ZAuxdR8CMgHb25xE3BRQTTgPbMsEemopGW2UCbdR2WiahSl9TEb2RvlM6kEXnF6lBTQV_aQcHrL2ilN6PBuqFupVGBInQPOS_9QhTRmOFpllHnYUkEUlK8kTXzXIoD7w3nzdvFRerL09_4W6T_a5QelRUNsf6aGioJoSQ6rc8iu8_4bIAlwHrGfB14LnC9AY6A_KxDF9S-S-17D-3Q8G0bo54YtoscierABIqH9IEST_O7-FKrYSD4HXCPwDt4i_p6n5h-52kH0aX3Ablg_5P47koQPerzkcmxOheieD3u_z0Xlb7O-Y0f6_Fkrjru6c8pUfKTqIs1cpHowe5R9q5koP7h8mBo8Jp9c5GQC0boP4MEmJ5V17wqzFUv64L-WgLAEROnxy40lpf0Fg28fjkQr5bP1g0-Qt-trp_4RXab1bDZlQvFPkffLGmr62wLSLTYS9b2MhKrIzeowvOwgP35VOcfVbq7nLEefJzqb1nfpgf1dh0sdWM-QmW3WJwrvzBhh9bFl0R2kU3U3eqYwodf6ddV2OfzzAwWt0fqJI62dwS7fUn0wd2q22tM5LzxcyqjjMNZt46kpJtbY5PjW218jXuv1s3ncAZhvFAeymKFu2I8xOuxSZf-vH76-_IKeLy0WKvq7Hgbv6A0dJ-seV45vZufmqwrsZ68hlxvo2ZBFrkV55blvzbp_nOZmZes3LycHQkPVlzKz9N2OXxJS00ui6s_aPNNIDjeWsvY-vuP9mOuIel96iFm_HMC_gm2VKmF././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_revoked.json0000664000175000017500000001214400000000000026556 0ustar00zuulzuul00000000000000{ "token": { "catalog": [ { "endpoints": [ { "id": "3b5e554bcf114f2483e8a1be7a0506d1", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "54abd2dc463c4ba4a72915498f8ecad1", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "70a7efa4b1b941968357cc43ae1419ee", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "5707c3fc0a294703a3c638e9cf6a6c3a", "type": "volume", "name": "volume" }, { "endpoints": [ { "id": "92217a3b95394492859bc49fd474382f", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "f20563bdf66f4efa8a1f11d99b672be1", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "375f9ba459a447738fb60fe5fc26e9aa", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "id": "15c21aae6b274a8da52e0a068e908aac", "type": "image", "name": "glance" }, { "endpoints": [ { "id": "edbd9f50f66746ae9ed11dc3b1ae35da", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "9e03c46c80a34a159cb39f5cb0498b92", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "1df0b44d92634d59bd0e0d60cf7ce432", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "2f404fdb89154c589efbc10726b029ec", "type": "compute", "name": "nova" }, { "endpoints": [ { "id": "a4501e141a4b4e14bf282e7bffd81dc5", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "3d17e3227bfc4483b58de5eaa584e360", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "8cd4b957090f4ca5842a22e9a74099cd", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "id": "c5d926d566424e4fba4f80c37916cde5", "type": "identity", "name": "keystone" } ], "issued_at": "2002-01-18T21:14:07Z", "expires_at": "2038-01-18T21:14:07Z", "audit_ids": ["ZzzZ2ZZYqT8OzfUVvrjEITQ", "cCCCCCctTzO1-XUk5STybw"], "project": { "enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "user": { "name": "revoked_username1", "id": "revoked_user_id1", "domain": { "id": "domain_id1", "name": "domain_name1" } }, "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "methods": [ "password" ] } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_revoked.pem0000664000175000017500000001752100000000000026372 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIW/gYJKoZIhvcNAQcCoIIW7zCCFusCAQExDTALBglghkgBZQMEAgEwghUDBgkq hkiG9w0BBwGgghT0BIIU8HsNCiAgICAidG9rZW4iOiB7DQogICAgICAgICJjYXRh bG9nIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJlbmRwb2lu dHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAg ICAgICAgICJpZCI6ICIzYjVlNTU0YmNmMTE0ZjI0ODNlOGExYmU3YTA1MDZkMSIs DQogICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImFkbWluIiwN CiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAu MTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAg ICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiNTRhYmQyZGM0NjNjNGJhNGE3Mjkx NTQ5OGY4ZWNhZDEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj ZSI6ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjog Imh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZj Zjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjog InJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAg ICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjcwYTdl ZmE0YjFiOTQxOTY4MzU3Y2M0M2FlMTQxOWVlIiwNCiAgICAgICAgICAgICAgICAg ICAgICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAg ICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2Zi Y2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAg ICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9 DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQiOiAiNTcw N2MzZmMwYTI5NDcwM2EzYzYzOGU5Y2Y2YTZjM2EiLA0KICAgICAgICAgICAgICAg ICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAidm9s dW1lIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiOTIyMTdhM2I5NTM5NDQ5Mjg1OWJj NDlmZDQ3NDM4MmYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj ZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAg ICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwN CiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAg ImlkIjogImYyMDU2M2JkZjY2ZjRlZmE4YTFmMTFkOTliNjcyYmUxIiwNCiAgICAg ICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAiaW50ZXJuYWwiLA0KICAg ICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjky OTIvdjEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJyZWdp b25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAg ICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzNzVmOWJhNDU5 YTQ0NzczOGZiNjBmZTVmYzI2ZTlhYSIsDQogICAgICAgICAgICAgICAgICAgICAg ICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQogICAgICAgICAgICAgICAgICAgICAg ICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAg ICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAg ImlkIjogIjE1YzIxYWFlNmIyNzRhOGRhNTJlMGEwNjhlOTA4YWFjIiwNCiAgICAg ICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIsDQogICAgICAgICAgICAgICAgIm5h bWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAg ICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAg ICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiZWRiZDlmNTBmNjY3 NDZhZTllZDExZGMzYjFhZTM1ZGEiLA0KICAgICAgICAgICAgICAgICAgICAgICAg ImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAi dXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQz NWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJy ZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAg ICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQi OiAiOWUwM2M0NmM4MGEzNGExNTljYjM5ZjVjYjA0OThiOTIiLA0KICAgICAgICAg ICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAg ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3NC92 MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAgICAg ICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAg ICAgICAgICAgICAgICAiaWQiOiAiMWRmMGI0NGQ5MjYzNGQ1OWJkMGUwZDYwY2Y3 Y2U0MzIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJw dWJsaWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6ICJodHRwOi8v MTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2 NjE3YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lv bk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIF0s DQogICAgICAgICAgICAgICAgImlkIjogIjJmNDA0ZmRiODkxNTRjNTg5ZWZiYzEw NzI2YjAyOWVjIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJjb21wdXRlIiwN CiAgICAgICAgICAgICAgICAibmFtZSI6ICJub3ZhIg0KICAgICAgICAgICAgfSwN CiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0K ICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAi aWQiOiAiYTQ1MDFlMTQxYTRiNGUxNGJmMjgyZTdiZmZkODFkYzUiLA0KICAgICAg ICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAg ICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6MzUzNTcv djMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lvbiI6ICJSZWdpb25P bmUiDQogICAgICAgICAgICAgICAgICAgIH0sDQogICAgICAgICAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzZDE3ZTMyMjdiZmM0 NDgzYjU4ZGU1ZWFhNTg0ZTM2MCIsDQogICAgICAgICAgICAgICAgICAgICAgICAi aW50ZXJmYWNlIjogImludGVybmFsIiwNCiAgICAgICAgICAgICAgICAgICAgICAg ICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTozNTM1Ny92MyIsDQogICAgICAgICAg ICAgICAgICAgICAgICAicmVnaW9uIjogIlJlZ2lvbk9uZSINCiAgICAgICAgICAg ICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAg ICAgICAgICAgICAgImlkIjogIjhjZDRiOTU3MDkwZjRjYTU4NDJhMjJlOWE3NDA5 OWNkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVi bGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEy Ny4wLjAuMTo1MDAwL3YzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdp b24iOiAiUmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAg ICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAiaWQiOiAiYzVkOTI2ZDU2NjQy NGU0ZmJhNGY4MGMzNzkxNmNkZTUiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjog ImlkZW50aXR5IiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrZXlzdG9uZSIN CiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgImlzc3VlZF9hdCI6 ICIyMDAyLTAxLTE4VDIxOjE0OjA3WiIsDQogICAgICAgICJleHBpcmVzX2F0Ijog IjIwMzgtMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImF1ZGl0X2lkcyI6IFsi Wnp6WjJaWllxVDhPemZVVnZyakVJVFEiLCAiY0NDQ0NDY3RUek8xLVhVazVTVHli dyJdLA0KICAgICAgICAicHJvamVjdCI6IHsNCiAgICAgICAgICAgICJlbmFibGVk IjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6IG51bGwsDQogICAg ICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLA0KICAgICAgICAgICAgImlk IjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRvbWFpbiI6IHsNCiAgICAg ICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQogICAgICAgICAgICAgICAg Im5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAgICAgfQ0KICAgICAgICB9 LA0KICAgICAgICAidXNlciI6IHsNCiAgICAgICAgICAgICJuYW1lIjogInJldm9r ZWRfdXNlcm5hbWUxIiwNCiAgICAgICAgICAgICJpZCI6ICJyZXZva2VkX3VzZXJf aWQxIiwNCiAgICAgICAgICAgICJkb21haW4iOiB7DQogICAgICAgICAgICAgICAg ImlkIjogImRvbWFpbl9pZDEiLA0KICAgICAgICAgICAgICAgICJuYW1lIjogImRv bWFpbl9uYW1lMSINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwNCiAgICAgICAg InJvbGVzIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICJpZCI6 ICJmMDNmZGE4ZjhhMzI0OWIyYTcwZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAg ICAgICAgIm5hbWUiOiAicm9sZTEiDQogICAgICAgICAgICB9LA0KICAgICAgICAg ICAgew0KICAgICAgICAgICAgICAgICJpZCI6ICJmMDNmZGE4ZjhhMzI0OWIyYTcw ZmIxZjE3NmE3YjYzMSIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAicm9sZTIi DQogICAgICAgICAgICB9DQogICAgICAgIF0sDQogICAgICAgICJtZXRob2RzIjog Ww0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAgICBdDQogICAgfQ0KfQ0K MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD VQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlPcGVuU3RhY2sx ETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu c3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglghkgBZQMEAgEw DQYJKoZIhvcNAQEBBQAEggEAh4ESJgQ+2tuZrQdOUGXKi5uO56bbX5c15WdhPcHA gXK4TUXC8Gb0pTyAGXXWkHwErf+7NHrZbA5Y8zqTor8qjig+tT8Ywh82lzY+mXOE HgnkKNoGUmgv1auJuECjysLHX6T5c0AUOxPSBvCQtvmGl33/riX5/gM+D/Dkptul iEOGXZc/S8qoOJE/SoJnFhimbJ7BuECKO8euTXeQrpKVyAULTm0RSQogWMTlXHzk hY71+Qn0dpp4ZCQTKaFO2u6aYQ2fjJ3BlH8UK0edIUJ4cHqKfMm/TCiIHQ/jbEOp cy83wzZMoDNK+7r/fxdUz7CJA1r2LrbWJMOhDVgKIYE6TA== -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_revoked.pkiz0000664000175000017500000000560100000000000026562 0ustar00zuulzuul00000000000000PKIZ_eJydWEt3ozgT3etXfPucPgPCpOPFLHgZ47FEwDws7QxODELYTvzA8OunsJ3uPDrT6a9z-uSE4FLdqntvlfLtG_wzHdej_7PIrP_hGyKeR4qGTf7ZcK845tQIcmsDzx5sy7LHgWUEzsmKjLG5ip_tFbFcYVnWNnCt2ZM78zIN2YEzNhbwcBM7q9WT-dBOlAzvZVZ6t954V2ZpoizcYZW38PNoV-buqMu15TGvg3I-a1bIW0-OmZt0ntisUm1XLtKg9Euj5MLoeB0WvssGLCIttWVJakcjLi9Jyk604wXFHkakc8qpZZRZPdrzGZxiTdoMnySZTYZTy_zu1bLqg3s1awjmFYuK2nedjohAZ2JSINqZNROjmkQ5ZtGypIKcvLKBD-hFlsbnbPJ6uOORVz4myg4OkA9jc5vXSTfHIwWdowuvId1qT2xnT6IiJsK5JVFwS-zl4hxsbUJWW8m0Ht6rrNahRJD6YTkabrl9gcLd4Z6l8tC_AAXdcusMq8qwWixS_RFq9CZDdC7Y9UNzfH548taXVCF4CQU-n7YcT1Q-6z8YyhzTdjE3lUU6PJwhZOtkl1lvcS_d5MBSXXkXVLB5WGTucP3SNcRTvcrd4TZbh69aqSt8PqlZSuWlA6Mq62Gd65G02QXWZjkOG4BwdySRp02FcSDW4OSLlUY7dlwK50hFWNKaAR_g5C7uqE1UHhUFFdA5zAZ-OikRFUAKfCkgtGbd47pUeCI5lsesGh6AH33614J6HROJpFGssJrWiESOwoWn-DaVQJATq2ONRYZKbF69ItMBatLyeiSuZOshyxxqhgBPH13N6-ZcvMU4VHJ7c5x2TkvbQXOGFm0GtMvxVGOnaccUJngNrCwZJipQOehoGgPfWcMhJR84zwT8KloWl6JdoZQXmvN0uc2wfp_V8Rk2ehEPjcKC1UHLXaJQAV_upKAuiEdUJxoFei8qntKiJ9wj8KEnWQ8D5TUvGL5yfpwAcaT4Vbs-6xb6ars-6xb6j3aB9h2Np6B71zsxUSkkqrAPwSkGiDbAgQ5CG6Xk0K7eXUBdeu5eqQwSXqaqvAjnqj4Ra6BQAR0QELz1o0BDBCIRDF9dIf2UQh8czDpavPeEHwF7TYDVvUgA_b8aeCkq-lnVClLyeg38Ca11RITXVxf4XamkqxRqQyA71llNFD_lFbVzBdyq5eWvaY1e8_qLtNaBXG1P6x4a-h1Vf9q819CIdawOawpaoG5Sg0MXqPd4kgJVUw_TblJCb99Q9XdMRZ9T9WtFRe_NgnZJDdQtaF_IKFBAxp0P06inNY8g7c7DPJIFu5IPvWbfIlULjt9iJ1EiiBgVLI0xE54GCh1w11FJHTdgPBj5bqwTu4AXyPsRt87c0aHHf60J2HzYZBjaOCb9gMn6OqH3hWJpuF-kg3Ow5XyyuzCyUJZj43ba3p2IyPsaQUudW9_ONUStISazwQer-qpTod_2Pw1LbsuaRib0n2nU5iBjULDtnMC9OgSTGR6Agbif9_8qMphUzQdo6DNsX4WG_tSFX6D5aQwLB1EQrckA3KWD_oL7SsEE0blI4Luh-FFR-v1i8R_URn_mwkFP7QOZ3WHwSTA2gADzTdCIgOaTfrRhWKIEFyvwAxCXcDR2ofoVyuC68lx0EWFdoremOaq4MEtqhxWkDj4ZVgAL2mhK4gYQHDwTUwm233prdXmeTMuxbK7UFbDGNMt5-M6JJzW1DQVWvtK3-ykVQsYrHey-ZJ3TwJbmgUiM1k8T8d6Js3qI2Y8JnRz42Dz2nLgsnfuzvaF3Y7vgrrqFFn7F2jqonYpoC4RpPxYqflWoN5BqR6GRceqnEklhMjERShKFvecNSL9c1Lzm9qrnufoyRD7Oi4szg_R36Gsc6Ckca-DE3Xu29p44-4yuBAcwM2LYi70-MxiowYBgT_XdUNI0KVgUDxB1YZwL44-c-HWm6G2qIBDbALqSj04sfz3eAII3YDhQ-tFGO0MnLsgXO6pvBy2LvLZ3YNBA44PQuPVxDYAdKZSQ9nY5rt7gZ11ypjO3Y9BE0AJUYGO_NzFQLwH7B1bWtEI8it-78TOfy27p9qm-nJh0fO5dV_3wmKWj7cuV6MeW9nNjl7BgnjGChanXvl8_JIfnZ5cF9HIoernm8Dk_LnBSzbX-tMn1xddT68M757sDuq7xxTKFOvQXj-vQ8OT29kFu2lRueZ4Eg0jb1knCcV5vR7MkDC_0pjaC6WcHmCrJeHtPZipL0p0aq6HO1vn5Wge0hWteIvloWCwv87OFVrdT0MM0cgYosT3ov6P4wtBS2EIeI9cy8k2zWo1dY-WYxHMr-P9Agk1jGcxOgmDkNDAbg11jBcxG8MB1mkkSd86UGJVrqLFjmcQKFOfkCCMwVzQxjTyyEqpmta4vQUCgxBkxjfO7yCrIJNJMmUmqgNqeSeg0dnM-aeo0xfRHSyNHEov8uPLCjXdihCxFUFU916BNdWJkfaD1JdC0Hra8c2JieueTjBOZxjjZ8dKMFunywFO4V8NhyGzY6J9mYBvFprGD15dwxzQDA-7TjtGOxMIPR9FjE37XlON2OLSOB7sjD972Dj3vk-d2KBcPs-i21Uf3T1YNbT7l2DUf13g_cx-GOztaP5Uj9n3HlcmoCOybMtQmpSKD5BQhO1wbnuf8VQxq81BFdydp7pVVfCrNm25YBtSST48zfdt8V7ARb2Ot3DaMTjl_rr2tk_ylofomW_jOatpsRxv_xr9ZVVYs75RsIBUdVHqzte-8gzmYPVZW87zOHruTtDb5Kdb8h0X2qHkomd3WT8Pd6GCnj3KCT-U8NZXn440q0yM7TXLn4K6G08aLk0qtd_e3M3pj4XEi56UbYWMNV1823R3Nm6ySHdnj1nkw_MhV8NPqefKPqdLh-AFnnXW_N-82BSmq2VgeqvvWHAyCv_9G5z-CONT--QeRfwEmaLr4././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_scoped.json0000664000175000017500000001212400000000000026372 0ustar00zuulzuul00000000000000{ "token": { "methods": [ "password" ], "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "issued_at": "2002-01-18T21:14:07Z", "expires_at": "2038-01-18T21:14:07Z", "audit_ids": ["VcxU2JYqT8OzfUVvrjEITQ", "qNUTIJntTzO1-XUk5STybw"], "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": true, "description": null, "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "id": "3b5e554bcf114f2483e8a1be7a0506d1", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "54abd2dc463c4ba4a72915498f8ecad1", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "70a7efa4b1b941968357cc43ae1419ee", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "5707c3fc0a294703a3c638e9cf6a6c3a", "type": "volume", "name": "volume" }, { "endpoints": [ { "id": "92217a3b95394492859bc49fd474382f", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "f20563bdf66f4efa8a1f11d99b672be1", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "375f9ba459a447738fb60fe5fc26e9aa", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "id": "15c21aae6b274a8da52e0a068e908aac", "type": "image", "name": "glance" }, { "endpoints": [ { "id": "edbd9f50f66746ae9ed11dc3b1ae35da", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "9e03c46c80a34a159cb39f5cb0498b92", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "1df0b44d92634d59bd0e0d60cf7ce432", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "2f404fdb89154c589efbc10726b029ec", "type": "compute", "name": "nova" }, { "endpoints": [ { "id": "a4501e141a4b4e14bf282e7bffd81dc5", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "3d17e3227bfc4483b58de5eaa584e360", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "8cd4b957090f4ca5842a22e9a74099cd", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "id": "c5d926d566424e4fba4f80c37916cde5", "type": "identity", "name": "keystone" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_scoped.pem0000664000175000017500000001747100000000000026214 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIW7gYJKoZIhvcNAQcCoIIW3zCCFtsCAQExDTALBglghkgBZQMEAgEwghTzBgkq hkiG9w0BBwGgghTkBIIU4HsNCiAgICAidG9rZW4iOiB7DQogICAgICAgICJtZXRo b2RzIjogWw0KICAgICAgICAgICAgInBhc3N3b3JkIg0KICAgICAgICBdLA0KICAg ICAgICAicm9sZXMiOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAg ImlkIjogImYwM2ZkYThmOGEzMjQ5YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAg ICAgICAgICAgICAibmFtZSI6ICJyb2xlMSINCiAgICAgICAgICAgIH0sDQogICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgImlkIjogImYwM2ZkYThmOGEzMjQ5 YjJhNzBmYjFmMTc2YTdiNjMxIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJy b2xlMiINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwNCiAgICAgICAgImlzc3Vl ZF9hdCI6ICIyMDAyLTAxLTE4VDIxOjE0OjA3WiIsDQogICAgICAgICJleHBpcmVz X2F0IjogIjIwMzgtMDEtMThUMjE6MTQ6MDdaIiwNCiAgICAgICAgImF1ZGl0X2lk cyI6IFsiVmN4VTJKWXFUOE96ZlVWdnJqRUlUUSIsICJxTlVUSUpudFR6TzEtWFVr NVNUeWJ3Il0sDQogICAgICAgICJwcm9qZWN0Ijogew0KICAgICAgICAgICAgImlk IjogInRlbmFudF9pZDEiLA0KICAgICAgICAgICAgImRvbWFpbiI6IHsNCiAgICAg ICAgICAgICAgICAiaWQiOiAiZG9tYWluX2lkMSIsDQogICAgICAgICAgICAgICAg Im5hbWUiOiAiZG9tYWluX25hbWUxIg0KICAgICAgICAgICAgfSwNCiAgICAgICAg ICAgICJlbmFibGVkIjogdHJ1ZSwNCiAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6 IG51bGwsDQogICAgICAgICAgICAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiDQogICAg ICAgIH0sDQogICAgICAgICJjYXRhbG9nIjogWw0KICAgICAgICAgICAgew0KICAg ICAgICAgICAgICAgICJlbmRwb2ludHMiOiBbDQogICAgICAgICAgICAgICAgICAg IHsNCiAgICAgICAgICAgICAgICAgICAgICAgICJpZCI6ICIzYjVlNTU0YmNmMTE0 ZjI0ODNlOGExYmU3YTA1MDZkMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAi aW50ZXJmYWNlIjogImFkbWluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1 cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4 YTYwZmNmODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdp b24iOiAicmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAg ICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAi NTRhYmQyZGM0NjNjNGJhNGE3MjkxNTQ5OGY4ZWNhZDEiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgImludGVyZmFjZSI6ICJpbnRlcm5hbCIsDQogICAgICAgICAg ICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82 NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAg ICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAg ICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAg ICAgICAgICAgImlkIjogIjcwYTdlZmE0YjFiOTQxOTY4MzU3Y2M0M2FlMTQxOWVl IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZhY2UiOiAicHVibGlj IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4w LjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwN CiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0K ICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAg ICAgICAgICAgICAiaWQiOiAiNTcwN2MzZmMwYTI5NDcwM2EzYzYzOGU5Y2Y2YTZj M2EiLA0KICAgICAgICAgICAgICAgICJ0eXBlIjogInZvbHVtZSIsDQogICAgICAg ICAgICAgICAgIm5hbWUiOiAidm9sdW1lIg0KICAgICAgICAgICAgfSwNCiAgICAg ICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAg ICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAi OTIyMTdhM2I5NTM5NDQ5Mjg1OWJjNDlmZDQ3NDM4MmYiLA0KICAgICAgICAgICAg ICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAg ICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsDQog ICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAg ICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAgICAgICAgew0KICAg ICAgICAgICAgICAgICAgICAgICAgImlkIjogImYyMDU2M2JkZjY2ZjRlZmE4YTFm MTFkOTliNjcyYmUxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmZh Y2UiOiAiaW50ZXJuYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgInVybCI6 ICJodHRwOi8vMTI3LjAuMC4xOjkyOTIvdjEiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInJlZ2lvbiI6ICJyZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAg IH0sDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAg ICAgICJpZCI6ICIzNzVmOWJhNDU5YTQ0NzczOGZiNjBmZTVmYzI2ZTlhYSIsDQog ICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogInB1YmxpYyIsDQog ICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6 OTI5Mi92MSIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogInJl Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAg IF0sDQogICAgICAgICAgICAgICAgImlkIjogIjE1YzIxYWFlNmIyNzRhOGRhNTJl MGEwNjhlOTA4YWFjIiwNCiAgICAgICAgICAgICAgICAidHlwZSI6ICJpbWFnZSIs DQogICAgICAgICAgICAgICAgIm5hbWUiOiAiZ2xhbmNlIg0KICAgICAgICAgICAg fSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAiZW5kcG9pbnRzIjog Ww0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAg ICAiaWQiOiAiZWRiZDlmNTBmNjY3NDZhZTllZDExZGMzYjFhZTM1ZGEiLA0KICAg ICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6ICJhZG1pbiIsDQogICAg ICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3 NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwNCiAgICAg ICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAicmVnaW9uT25lIg0KICAgICAg ICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAg ICAgICAgICAgICAgICAgICAiaWQiOiAiOWUwM2M0NmM4MGEzNGExNTljYjM5ZjVj YjA0OThiOTIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFjZSI6 ICJpbnRlcm5hbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 dHA6Ly8xMjcuMC4wLjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNm ODliYjY2MTdhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWdpb24iOiAi cmVnaW9uT25lIg0KICAgICAgICAgICAgICAgICAgICB9LA0KICAgICAgICAgICAg ICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAiMWRmMGI0 NGQ5MjYzNGQ1OWJkMGUwZDYwY2Y3Y2U0MzIiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgImludGVyZmFjZSI6ICJwdWJsaWMiLA0KICAgICAgICAgICAgICAgICAg ICAgICAgInVybCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsDQogICAgICAgICAgICAgICAgICAg ICAgICAicmVnaW9uIjogInJlZ2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAg fQ0KICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgImlkIjogIjJm NDA0ZmRiODkxNTRjNTg5ZWZiYzEwNzI2YjAyOWVjIiwNCiAgICAgICAgICAgICAg ICAidHlwZSI6ICJjb21wdXRlIiwNCiAgICAgICAgICAgICAgICAibmFtZSI6ICJu b3ZhIg0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAg ICAgICAiZW5kcG9pbnRzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAg ICAgICAgICAgICAgICAgICAgICAiaWQiOiAiYTQ1MDFlMTQxYTRiNGUxNGJmMjgy ZTdiZmZkODFkYzUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgImludGVyZmFj ZSI6ICJhZG1pbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAidXJsIjogImh0 dHA6Ly8xMjcuMC4wLjE6MzUzNTcvdjMiLA0KICAgICAgICAgICAgICAgICAgICAg ICAgInJlZ2lvbiI6ICJSZWdpb25PbmUiDQogICAgICAgICAgICAgICAgICAgIH0s DQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAg ICJpZCI6ICIzZDE3ZTMyMjdiZmM0NDgzYjU4ZGU1ZWFhNTg0ZTM2MCIsDQogICAg ICAgICAgICAgICAgICAgICAgICAiaW50ZXJmYWNlIjogImludGVybmFsIiwNCiAg ICAgICAgICAgICAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMToz NTM1Ny92MyIsDQogICAgICAgICAgICAgICAgICAgICAgICAicmVnaW9uIjogIlJl Z2lvbk9uZSINCiAgICAgICAgICAgICAgICAgICAgfSwNCiAgICAgICAgICAgICAg ICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogIjhjZDRiOTU3 MDkwZjRjYTU4NDJhMjJlOWE3NDA5OWNkIiwNCiAgICAgICAgICAgICAgICAgICAg ICAgICJpbnRlcmZhY2UiOiAicHVibGljIiwNCiAgICAgICAgICAgICAgICAgICAg ICAgICJ1cmwiOiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3YzIiwNCiAgICAgICAg ICAgICAgICAgICAgICAgICJyZWdpb24iOiAiUmVnaW9uT25lIg0KICAgICAgICAg ICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAg ICAiaWQiOiAiYzVkOTI2ZDU2NjQyNGU0ZmJhNGY4MGMzNzkxNmNkZTUiLA0KICAg ICAgICAgICAgICAgICJ0eXBlIjogImlkZW50aXR5IiwNCiAgICAgICAgICAgICAg ICAibmFtZSI6ICJrZXlzdG9uZSINCiAgICAgICAgICAgIH0NCiAgICAgICAgXSwN CiAgICAgICAgInVzZXIiOiB7DQogICAgICAgICAgICAiZG9tYWluIjogew0KICAg ICAgICAgICAgICAgICJpZCI6ICJkb21haW5faWQxIiwNCiAgICAgICAgICAgICAg ICAibmFtZSI6ICJkb21haW5fbmFtZTEiDQogICAgICAgICAgICB9LA0KICAgICAg ICAgICAgIm5hbWUiOiAidXNlcl9uYW1lMSIsDQogICAgICAgICAgICAiaWQiOiAi dXNlcl9pZDEiDQogICAgICAgIH0NCiAgICB9DQp9DQoxggHOMIIBygIBATCBpDCB njEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQH DAlTdW5ueXZhbGUxEjAQBgNVBAoMCU9wZW5TdGFjazERMA8GA1UECwwIS2V5c3Rv bmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNV BAMMC1NlbGYgU2lnbmVkAgERMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASC AQAKyFuKuktH9YR3TfKG4NAEBXDkEAkWA+fVWREyEnYoCYE2YDiqrVRwpFqR6p0V PGRUPcDLTtPw+6p+ywNbP3F3AU0LNqjs3zl8HxvHij91CzZOQIykUcX4ToLJhBAD shZzSucjk5y9zTLN4bl+AX/NJ/GYT2chFVjYJUW+sbIVoT5u0V9K602OV9OAyvpY a0YzFmnEzplkg2U0qzZjx5b143ASQnaCtaf4rK7HCOIz11I6aaL3yhGSxnqE6yyA 9FdYOwB14E1GWeLVx0xeM8D/qMesqANsk0WQ19LnvKB7ry5EnDCGeBRYTMR+u0B2 gnCYSRs9m+gg1rL889d53c/q -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/auth_v3_token_scoped.pkiz0000664000175000017500000000550500000000000026403 0ustar00zuulzuul00000000000000PKIZ_eJylWEl34rwS3etXvH2fPu0BElh6wthBcmw8IO2wnWDLNiRh8PDrXwlId4bu70u_lyxycKBUt-reWyW-f4cf3bId8h8DL8WL7wg7Ds5b6t7tmFOcMqL5mbGDZ2vTMEzbNzTf6oxQm-ub6MXcYMPmhmHsfNtYPttLJ1WR6VtzbQ0Pt5G12Tx1D70rpcqhTkvnxpnvyzSJpbU9rbIeXs_2ZWbPhkzNT1njl6tlu0HO1j2ldjw4fLdJ1H25TvzSK7WScW1gTVB4Nh3REPfErEvcWCq2WYkT2pGBFURxFIQHq1wYWpk2swNbwimG26dKV-OlO10Y-q3T1JUI7jS0xQqraFg0nm0NmPtjyt0CkUFvKJ81OMwUGuYl4bhzyhY-MC7SJDpnkzXTPQud8jGW9nBA_TDXn7ImHlbKTELn6Nxp8bA5YNM64LCIMLducOjfYDNfn4NtdcjqqaaqgCeyCk5pMnsSdUKiUD9x29MDTerjSqkrvHTEaUeayPUFwvVD9fT87AJRKxFLxgVtupoZoupBnyeR-ODT-bXhSuL_6TZ4hEM-Qcvt-IhoMpZWyvnh9Q1BnSmkX690aZ1Mj-L0dBvv0_kZP6eroEjt6fa1ayKDKrOnT3DKm1aOJbZyG5qQa_qzKgVol3rEfXrJbpfPgxZ55eSEQ0ddcO2IjVHn8Y1KBnrKuXUiPChJQ4EPcPIQDcTEMguLgnDonEJHXuKWiHAghXLhArRm-5o2EKxmSn1Kq-mRXQp6rYszUB7XJIwk2pAG4dCSGHckzyQ1EKSjTaTSUJOxyao3ZDpCwXrWzPiVbAJynUFBEeAR0eWsac-VXc8DKTN3p8Vg9aQftWdo4W5EhkxZqLRbDFSinDXAypIqWAYq-wNJIuA7bRmk5AHnKYd_hXlxKdoVSnmhOUvyp1QZ36dNdIaNXklEwgD44PfMxhLh8Gu7BbFBPLzqSOiPhahYQgpmWuUjqBBUe4aBsoYVVLlyfh6XqV3z37XrT91CX23Xn7qF_qFdoH1LZQno3nY6yisJh5XiQXCiAEQT4EAHoY11zaBdwl2cbTDO7CvPQcK5ENKZ3ldP4JEKCuXQAQ7Bey_0VYQhElbgdyhqLyHQB0uhAyk-Cec14BY0AQp-lQD6XzXwWlT0q6oVpOQIDfwNrccIc0dUF_hdyXioJGJCIDMa0wZLXsIqYmYSuFXPyt_TGr3l9RdpPQZy9YLWAhr6N6r-snmnJSEdaBM0BLRA7LgBhy6Q8HicAFUTRyGDW0Jv31H135iK_kzVrxUVfTQLMsQNULcgopChL4GMBw-mkaA1CyHtwVFYWBf0Sj70ln3rRC6Y8h47DmOO-aygSaRQ7qig0BGzLRk3UQvGoyDPjsbYLOAN-OOI26b27CjwX2tSp03Qpgq0cY7FgElFndDHQtEkOKyT0TlYvnL3F0YWUj7Xbhb9pMM8EzWCllo3npmpiBhTBS9Hn6zqq06F_rX_SVAys25IqEP_qUpMBjIGBZtWB-41IJjM8AAMxP5z_68ig5nYfoKG_oTtq9DQ37rwKzQviWDhwBIiDR6BuwzQX3DfmlOOx4zH8FeTvLAoPbFY_AO10d-5sC-ofcTLiQI-CcYGEGC-cRJi0HwsRpsCSxRnfAN-AOLilkovVL9CGV1XnosuQmVco_emOasY10tiBhWkDj4ZVAAL2qjX2PYhOHimQmqw_d7Zyvl5MuXzur1Sl6eK3Oar4IMTuw0xNQlWvtIzxZQKIOPNGOy-pIPVwpbmgEi03kti_tGJ02aq0J8TOj6yuX4SnLgsnYezvaEPY7tgtiy2r69Y2wC1kxHpgTD950JFbwr1DlJjSSTUOjGVcAKTifKgxmEgPG-ExXLRsIaZG8Fz-XWIfJ4XF2cG6e_R1zggKByp4MTDR7YKT1z-ia5Y8WFmRLAXOyIzGKj-CCuO7NlBTZK4oGE0QsSGcc61v3Lit5mi96mCQEwN6Io_O3H9-_EGEJwRVXxJjDYyaGNsg3wVS_ZMv6eh0wsHBg20HgiNGZ_XANiRghrSfsrn1Tv8dIjPdGZmBJrwe4AKbBR7EwX1YrB_YGVDKsTC6KMbv7BVPeS2SPX1xHhgK-fTqi9ajP6fVV8ciq6nypkS9--39ivzzqe7l3V_e97YizwByLPpE4P5gMSAcGrGH2ZRv6zrLjaL-4eGxfGW9esqduPZdTZG4zi26juoV_RQTbpFXMTrIQ5RPK_LvHfP2l6vyJAncSXuQj-vQqbz-6vQVp5i6uioh4ukllFxwWw3a7_dsFFncM3RNyTWtSjUwqgzBs29vIZpWMch9vet4VMz9n0HWa1r-qG1xLpma3Jk6R12IzU-pttaoQlc_wKntbTzm--str7P4JoTqbAWK_vOCrV7dIm8Dw3rUD-sCNxax1DlqHXeXYcrHcbPr_ZG-kkEyiAQgkjHVHW3OPBba3M-ybTaQ8iSrnFm5IlBQAaIrHf3Z43om-q5qEobTVtJB_wzTVtCHfQyJe6ErBKVzTaj52ktJzfLb5v18ZmC1e32cXCI5qRZzslkMg823voBTYYq2m8GfXVblmo0dM3LjN3xqVkYCzr6sV1KyTqii1l6WDem8cKauebfL3da5hH1YX0kB3S3s8JH95Yrw_PIcQ-yjZenh4leSlL5GLTsx2EXLshhkdaPVjtbG_2tGo-Ml930B6tGP4y9h0aBP1TLYtZNb8udfW9NW0t2T1M6dLvxMdzcDMroZlOOlIexdFw6M4Ybgp-rKB-k7Y2fHb4hecCPk-Tbg_zUV3f7LNaH2_HtbLr99qwVnTxedF1u3DkvvgwjZpJr_SLQ_Uh6Zg-LZnFaqgnaNo8_3Nq_XZh-86yufGsepL68H-fDj6bFE6dcNhN0_rLDIuavLz7-Cz0ItdI=././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/revocation_list.der0000664000175000017500000000000000000000000025257 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/revocation_list.json0000664000175000017500000000051100000000000025465 0ustar00zuulzuul00000000000000{"revoked": [{"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}, {"expires": "2112-08-14T17:58:48Z", "id": "db98ed2af6c6707bec6dc6c6892789a0"}, {"expires": "2112-08-14T17:58:48Z", "id": "15ce05fd491b79791068ed80a9c7f5e7"}]}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/revocation_list.pem0000664000175000017500000000226400000000000025304 0ustar00zuulzuul00000000000000-----BEGIN CMS----- MIIDVwYJKoZIhvcNAQcCoIIDSDCCA0QCAQExDTALBglghkgBZQMEAgEwggFcBgkq hkiG9w0BBwGgggFNBIIBSXsicmV2b2tlZCI6IFt7ImV4cGlyZXMiOiAiMjExMi0w OC0xNFQxNzo1ODo0OFoiLCAiaWQiOiAiZGI5OGVkMmFmNmM2NzA3YmVjNmRjNmM2 ODkyNzg5YTAifSwgeyJleHBpcmVzIjogIjIxMTItMDgtMTRUMTc6NTg6NDhaIiwg ImlkIjogIjE1Y2UwNWZkNDkxYjc5NzkxMDY4ZWQ4MGE5YzdmNWU3In0sIHsiZXhw aXJlcyI6ICIyMTEyLTA4LTE0VDE3OjU4OjQ4WiIsICJpZCI6ICJkYjk4ZWQyYWY2 YzY3MDdiZWM2ZGM2YzY4OTI3ODlhMCJ9LCB7ImV4cGlyZXMiOiAiMjExMi0wOC0x NFQxNzo1ODo0OFoiLCAiaWQiOiAiMTVjZTA1ZmQ0OTFiNzk3OTEwNjhlZDgwYTlj N2Y1ZTcifV19MYIBzjCCAcoCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYT AlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU3Vubnl2YWxlMRIwEAYDVQQKDAlP cGVuU3RhY2sxETAPBgNVBAsMCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlz dG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDDAtTZWxmIFNpZ25lZAIBETALBglg hkgBZQMEAgEwDQYJKoZIhvcNAQEBBQAEggEAVsD6Zy+bXz9D9ZJ32CbG4ggTisgs 4vZw9xemVMQ9J8+3iLWE2Z+NrqO1T4Q81iu9cKZ/zTFuu99B5X9ZHE57QLrOd+Jb Ld3E8HYVj8ZpGQfXAcq3ybIkT2SeEddQmW+MlHHkkfu41D1dQ8jgkIcbLjpeRCFk GA5Zj6Xdnozos/qMLqd1o4ufJ+dQAeFH/fviLkS5fiTPK6GmhqkwBQgffkOHTo/S xZQeq7GhFKijg5p60AZRc1Q+WlrmkY9W0FrX7bt6iJodWBc2YSEZ5Mr7BpvIu5JH rg282z/D4Xwj+USzn7jvqZFlmkG9o6s6YKjsrIpnPFH92EWv0+D5NuOuOA== -----END CMS----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/cms/revocation_list.pkiz0000664000175000017500000000222100000000000025471 0ustar00zuulzuul00000000000000PKIZ_eJx9VElzszgUvPMr5p5KBbPY5vAdtGAsYokAYr0ZbIvdSXDM8usHO1Uzl6lRlQ6v1dVPr6vrvb4uB5oWYX8h6j-KV4kSgvmQ2O_XlBT3nAE3R9cFczFCYB4QcM0RcbCHIvjGgiKrWvBwsJD_ZfkkUyXsmntwXMBANoXY2efJntI4vR-VsCbVVURqX6ZxMRxju8knsiaITJSb04ED7cBNWQqxqTpVoDmVq0Ul6QmyP1P0INp1UtVaGrlTEiVKMicqxacyjaiSWvRRaw4nquTgpqDINg4IbkgbarnVLD-gpVOCklbmSEt5cJA8sp07svm6cvBVdnbX8oBAeYzcUnoSeVilHKzS1pUdvivZXKsONwdWFU2KxZDwpmJKskp5Xl78QSxjNuc9_MzbcJYec5KKjJSTG8XiRrkXUJ6vGRdrhosjKQdB2ubpB2m90uEPUbtIq7RiVT5ITLGbZE7r5S6A0GmVa05kDqSTe7L_fwMf_kn_bSAZWcQaisM2xa5OI7KMlOuUA8WxwtrBsHAiqqZV2Ehsso04lkch9u9LJuAoCAQcwU-MYFeZ7xQIC6wCE3oUMm4eKKh_68X6MKSjhGZgQ8FCCAQHNYPUI4MJEhy67t4cGn6K9J9znBaZFYxmBdxf7pWjwBjSSOfSydpVx9n0KNg-ldFIia-Eeq5696wNRpuDCor6q6hLyxhkiFwz2rW35hwzOVP0RnKtp9L8FJr0e97m4w4D_7cT5WjFmsxKRKA0XdaGNRCPZrkF_d4BAzlKFMj_5HqJNQRuAODiBbA6rf6eRvvnxNOEXlRFvHdXtj-D2MuMDbqiuOSiVyTx85Y18dtloKfvw9Y6COXzJ_HkTQxFddXXd9pjQ0uJNxW5v2p2t9K4n939bRN_buvM2zZSl43GpXcKOgb7g9eN5bU8A4Ovpvpjm96TUC0SdI5rkhQfAmsNAKBlX4aRjtDz1bw3JfzxEs-rlyC587XbvrHI-6k20ZK7S3cmcCP4-qCX330gf90ocs9fRD31H4bl95O2t-_QkyAks7u5mNRDFgc4q7W2eVnj8cVudd_ZyuxedvOIKkdd3nLTWn26qmeFZqeKaRbKUer7oxdoU54lAAHDeNeDGd38aisaK94dV3k3akrnd8ohu9gfK_pHeu4hk-F_d9Lf2fB_ww==././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/gen_cmsz.py0000664000175000017500000001017300000000000022770 0ustar00zuulzuul00000000000000#!/usr/bin/python # 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 from oslo_serialization import jsonutils from keystoneclient.common import cms from keystoneclient import utils CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) def make_filename(*args): return os.path.join(CURRENT_DIR, *args) def generate_revocation_list(): REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked'] revoked_list = [] for token in REVOKED_TOKENS: with open(make_filename('cms', '%s.pkiz' % name), 'r') as f: token_data = f.read() id = utils.hash_signed_token(token_data.encode('utf-8')) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) with open(make_filename('cms', '%s.pem' % name), 'r') as f: pem_data = f.read() token_data = cms.cms_to_token(pem_data).encode('utf-8') id = utils.hash_signed_token(token_data) revoked_list.append({ 'id': id, "expires": "2112-08-14T17:58:48Z" }) revoked_json = jsonutils.dumps({"revoked": revoked_list}) with open(make_filename('cms', 'revocation_list.json'), 'w') as f: f.write(revoked_json) encoded = cms.pkiz_sign(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f: f.write(encoded) encoded = cms.cms_sign_data(revoked_json, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) with open(make_filename('cms', 'revocation_list.pem'), 'w') as f: f.write(encoded) CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem') SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem') SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem') EXAMPLE_TOKENS = ['auth_token_revoked', 'auth_token_unscoped', 'auth_token_scoped', 'auth_token_scoped_expired', 'auth_v3_token_scoped', 'auth_v3_token_revoked'] # Helper script to generate the sample data for testing # the signed tokens using the existing JSON data for the # MII-prefixed tokens. Uses the keys and certificates # generated in gen_pki.sh. def generate_der_form(name): derfile = make_filename('cms', '%s.der' % name) with open(derfile, 'w') as f: derform = cms.cms_sign_data(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME, cms.PKIZ_CMS_FORM) f.write(derform) for name in EXAMPLE_TOKENS: json_file = make_filename('cms', name + '.json') pkiz_file = make_filename('cms', name + '.pkiz') with open(json_file, 'r') as f: string_data = f.read() # validate the JSON try: token_data = jsonutils.loads(string_data) except ValueError as v: raise SystemExit('%s while processing token data from %s: %s' % (v, json_file, string_data)) text = jsonutils.dumps(token_data).encode('utf-8') # Uncomment to record the token uncompressed, # useful for debugging # generate_der_form(name) encoded = cms.pkiz_sign(text, SIGNING_CERT_FILE_NAME, SIGNING_KEY_FILE_NAME) # verify before writing cms.pkiz_verify(encoded, SIGNING_CERT_FILE_NAME, CA_CERT_FILE_NAME) with open(pkiz_file, 'w') as f: f.write(encoded) generate_revocation_list() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/gen_pki.sh0000775000175000017500000001321200000000000022561 0ustar00zuulzuul00000000000000#!/bin/bash # 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. # These functions generate the certificates and signed tokens for the tests. DIR=`dirname "$0"` CURRENT_DIR=`cd "$DIR" && pwd` CERTS_DIR=$CURRENT_DIR/certs PRIVATE_DIR=$CURRENT_DIR/private CMS_DIR=$CURRENT_DIR/cms function rm_old { rm -rf $CERTS_DIR/*.pem rm -rf $PRIVATE_DIR/*.pem } function cleanup { rm -rf *.conf > /dev/null 2>&1 rm -rf index* > /dev/null 2>&1 rm -rf *.crt > /dev/null 2>&1 rm -rf newcerts > /dev/null 2>&1 rm -rf *.pem > /dev/null 2>&1 rm -rf serial* > /dev/null 2>&1 } function generate_ca_conf { echo ' [ req ] default_bits = 2048 default_keyfile = cakey.pem default_md = sha256 prompt = no distinguished_name = ca_distinguished_name x509_extensions = ca_extensions [ ca_distinguished_name ] serialNumber = 5 countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone emailAddress = keystone@openstack.org commonName = Self Signed [ ca_extensions ] basicConstraints = critical,CA:true ' > ca.conf } function generate_ssl_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = sha256 prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = localhost emailAddress = keystone@openstack.org ' > ssl_req.conf } function generate_cms_signing_req_conf { echo ' [ req ] default_bits = 2048 default_keyfile = keystonekey.pem default_md = sha256 prompt = no distinguished_name = distinguished_name [ distinguished_name ] countryName = US stateOrProvinceName = CA localityName = Sunnyvale organizationName = OpenStack organizationalUnitName = Keystone commonName = Keystone emailAddress = keystone@openstack.org ' > cms_signing_req.conf } function generate_signing_conf { echo ' [ ca ] default_ca = signing_ca [ signing_ca ] dir = . database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/certs/cacert.pem serial = $dir/serial private_key = $dir/private/cakey.pem default_days = 21360 default_crl_days = 30 default_md = sha256 policy = policy_any [ policy_any ] countryName = supplied stateOrProvinceName = supplied localityName = optional organizationName = supplied organizationalUnitName = supplied emailAddress = supplied commonName = supplied ' > signing.conf } function setup { touch index.txt echo '10' > serial generate_ca_conf mkdir newcerts } function check_error { if [ $1 != 0 ] ; then echo "Failed! rc=${1}" echo 'Bailing ...' cleanup exit $1 else echo 'Done' fi } function generate_ca { echo 'Generating New CA Certificate ...' openssl req -x509 -newkey rsa:2048 -sha256 -days 21360 -out $CERTS_DIR/cacert.pem -keyout $PRIVATE_DIR/cakey.pem -outform PEM -config ca.conf -nodes check_error $? } function ssl_cert_req { echo 'Generating SSL Certificate Request ...' generate_ssl_req_conf openssl req -newkey rsa:2048 -sha256 -keyout $PRIVATE_DIR/ssl_key.pem -keyform PEM -out ssl_req.pem -outform PEM -config ssl_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function cms_signing_cert_req { echo 'Generating CMS Signing Certificate Request ...' generate_cms_signing_req_conf openssl req -newkey rsa:2048 -sha256 -keyout $PRIVATE_DIR/signing_key.pem -keyform PEM -out cms_signing_req.pem -outform PEM -config cms_signing_req.conf -nodes check_error $? #openssl req -in req.pem -text -noout } function issue_certs { generate_signing_conf echo 'Issuing SSL Certificate ...' openssl ca -in ssl_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/10.pem -out $CERTS_DIR/ssl_cert.pem check_error $? echo 'Issuing CMS Signing Certificate ...' openssl ca -in cms_signing_req.pem -config signing.conf -batch check_error $? openssl x509 -in $CURRENT_DIR/newcerts/11.pem -out $CERTS_DIR/signing_cert.pem check_error $? } function check_openssl { echo 'Checking openssl availability ...' which openssl check_error $? } JSON_FILES="${CMS_DIR}/auth_token_revoked.json ${CMS_DIR}/auth_token_unscoped.json ${CMS_DIR}/auth_token_scoped.json ${CMS_DIR}/auth_token_scoped_expired.json ${CMS_DIR}/revocation_list.json ${CMS_DIR}/auth_v3_token_scoped.json ${CMS_DIR}/auth_v3_token_revoked.json" function gen_sample_cms { for json_file in $JSON_FILES do openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem} done } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2030036 python_keystoneclient-5.6.0/examples/pki/private/0000775000175000017500000000000000000000000022261 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/private/cakey.pem0000664000175000017500000000325000000000000024060 0ustar00zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC//gLFHKshdm/9 4GhxrVGcfpkXl9kDL/oF5ltilI9Qk5kk8RlwmnqCeTezwOgXgQ/xyVak4AqP6Dbh 5joLWK4EZ4MlqQASLbfTjUZkwq9gLwNtwGSI31HcEWXHkR3+ux+jdTTrba/CBZLk BYUwCrkRyRnFzajTCazb9r+YWtxjXgIcWXfeuaJPQRknfob4m42dpgwWfJIifQRn +d0w4nv84PquQpIKiwK4Ed9KRgnvmS1zy64XpKcnrQUd4Lik1djzAniIe2IP7kyT HttqmfOzOkNQ6LEigv9Zfv9QKwb3uprHPCRBJZ3N9hyezbk0ya3mShLz5lCNqE3H b6JFl7UbAgMBAAECggEAVCmMm03S8utRcrBB+LsqkHiqsb3+AriwWI+/tbo8DO12 78vFBCij1bg/o8vHsi4AiFRjaAlSd/0queJLxZeNSR77TbIE9vMVp2ZB2n/Bk19o mF8Dc0C6SMdTn6VMydLLrsL9fMrrhhkdaFnHJeU9db97Tcu22zRdk1taZ/ZEsEXN 5Ij4GSaylemUsdi2GKWCydDje3M60rGRWiVlMAsgyLhGRjBS0dfi2VRSbjE/Mi+d vrHGWyKfh/Dgmt5XdljubZE6fbogXtksBl/dvHytQIyYddSTYHWJoCHb0DhBV25V /m5SNTRoBeRtTy9s7UdMxTR9mpPscWCAonCZVh3nQQKBgQDqwE80+CaYInrNlZIM 6rX3sdHgcnlP8a2gSRfkmpTQbf5os6jHBwBp3UAqTpKZd6mjrbXXel4Hj8ccZMW9 SqReQJiJwENxhvT5Bz7YGERZbVGvchXeeQBjhOy8D+Smv85I+F+cP/7efutrzFgf M5mwlic5z58ijJlloGzgP4GquwKBgQDRXuJFzfbjY7DS0vgoz5Gh8GTfB/EPZkNL ULt22Zz6coMpZ+en8HBrc7gSlAjvf3C4MAon344VIaW6bK1tx4amuJVK3gbbk7Kg 9YOkXUg60WaBeX9zed+eljV3LIcgRJ10Zu5rJ8ZCLgp1DTF1kh7afmqyufU5828b vOOpN+5pIQKBgQC7qpGnntn7tVTHFVN00A44vgcyj1E7/9D12nknYAyns8c2nKnI smg6OY4aREYeOfN7zlsYr9KL6P0cTdNmyE0urCVFulYwY9tjWc97oarCcwpiX6nr +H+/D3zRu0Lnq16WJzkICIEQDhbWTr4D85Rh/yfMp5ZoYE4hWGaxvxNCEQKBgQCL bD4N8fworGhB3E95DdCTIDxr8SPr91N0wgw0NvG8Lal+Vz0CrrCOPX8kkAPrSNhN L2Bz8QDyvXdZT6ml4yqdt2ljc7rpWc+oNBY3zA6fbHZwXfIrecsaFjkAZVyOdmLL 8wdtwAzcYUCBdgmrm2SEZ46x+fd9Ychplj2coCxZQQKBgEtdtXU93fZ6vUUTN6Rj DbWQPxktPClfBvxEr7jfdMLO33UziVQU9ZoXpj5fplWFtjLw09W1bNa6VYKGkcVK ZYCHni/J440p7v9NvHZHX8kqYkFvzs0djFXzkLTzSKk6ErDMjvWUcQq7IvB3JLNo xrS0SJBTBskGUKX/1OEP69B1 -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/private/signing_key.pem0000664000175000017500000000325000000000000025272 0ustar00zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCexnPwhsvNj1jp pRg/gvHeisN0NWaHTs/RHboV3nHyzfsx13fUHd+X+YR3+HPI9MgMSPKn3mm45znn xJKBwuFvg007FMKtcexbwDx2053Eh83j6gRPMCqeKXxbmQITMT+eZ0yQvzfLdTNZ VKmEwDYEwgMrLcjibi7jHNN1AajvGzhswibAHXLAT6XWJH8nK1r+WaLHWh39zpmp D7Vspo0j0PP5TOMeRZuRmqGFKm/ZqG7rDKdLXuzDJM1mIpUqmQVh8e3bBkex6DeM zKmpbbQtIpyRnNMJ7rDa5Hm94nrl8FOk91d6RN8k07h8uh3GWXf/Jtzvy/RdaHcw 51B9dD45AgMBAAECggEAVYj/6LIVlTYGZkiEmaKHfqYuyaoDBB3XIwbquuFNbcq9 6onzihhV3l+Tl7YHWllUdBnQb9MIDY6zyUJC0xkTramEr7Ftd1cKSBt192Xldnza 1E+75pVCQFaFIit5zLEZXtKzkr8Q5dDLyvIrKNMLxuBmKJrPv/wv0jYzTLOKONUO BRxEb2c13lvq/RqT4xpxU8wadk/tC4N1tvLGdUnq/+1+GcmswwChBPnYafY+pzF3 ylyoQUtirIZ0TAkSBwZZGkZC137OPs6CmbBqxLcPT2swUSQpUSAglwG7PklRAW1u 2Qin1gka7J3l6erIYfeJ87C7day+3+uGtlYxplMRIQKBgQDJobPdn/YZakdI4ZaH YNL67qtO5A410vb0Sm6UXWASAyfRq7nMG1CyWvlfTpPTeao9bBD0oKKIteW7RTo5 mSozpKOt2Rgb3auyOkuswFbSxRuufgRDayUnyYMaHfhfp/pHEpXg/aiwNckiZ6Mi iOwIlLN+ytyzadfXZSrlPtVpvQKBgQDJlnD1hnOtWRgWeB4uUXzJPa9MJBK7JLRW +FSMrUxssw14vScmLO3kV/pN1J++FMkq7Y5ZmOpy34sGc3iMaM+H4JuNCvP5SwtG 626rOm5enH2sKd4BubiZcln5zrjgw/RV/a7aQQep4cQpaEgNoyTAu6BuU0fid6xc jVQJAXnILQKBgQDII7YB2vHRMGkpsqJUJovFgHqSiFSCoLF4sxkoM7dUqcUwniCC tOpY32yAaeLaGv4ckdQSvhAXW1Z5mLG+0oXNVTMTMVZ48oOnGa5b/18vP2/GuFdL BGORJrj3h6AucvI+8ffLqH10yy6m8/A+K2L+8Xtp87s2a21P5J+7ha8YkQKBgB4m fBqc02xX6PxjVtBCq9FFgpR2yL5ozPg9CBhKSyXu2dL3J4XULnh6mBtP89xwK25a PXI1Jsurl5WNa7hEbNW7yEgeHUNp7/PZfqHpiVxpN3qqgGPtrSh2K/Lq8kfbxw2d dat7EnRcKgSvbidsATE6XtJhblz23TayhKEcMWS5AoGBAItYfoHpgnQXk1gXal6e incHFVTgrUQlDKZ5UQn5/HvK1Gb3mAtPbLWJZ2Ej70lW5BQnBrCLI8188Z6PJY3w 8KJ4T2KOu+9qClnezLy2A+bZ+MU+XZrVv+65vjJ5oGhARmRLmYlasBmxwUEEKqlO ovIe7UVLD9CyaB+MFxFpFwE6 -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/private/ssl_key.pem0000664000175000017500000000325000000000000024435 0ustar00zuulzuul00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD1i9+ydZSypNAk kVXdzIqZ8E62cqH7i0JGVGBuGdH8ZVF3MDcbi8VwqfNRWoWn9mrJUp5HYDV9t5WX z25Ej4EnqlJ3WLZvC1e+ldDIInmiULic3iIAgrWbumU3XNLHska/smoVJuLIuFUx EfRpwGOpguOzAO1M6BKCSwr+TBLYJZxc3F7v1vtwNhisyE5S2H6Q49K0UXHTPjp+ fLZAHQ5+Yxqwf0KAJqAD3vMo8EwxXlgJu8pQyjjxwtrwnN2WHYoJGt/OOdkLBbzd upWH9CGcxeVc5hSJ1hEKuYS8AOZIeH6q2MKwT+QiepBsVfuy1JFt4raLht/RcX/W R8lIAzoFAgMBAAECggEAQbeB0z1s4rMBkgfjt0z6+2A5cNMVT0FiJ3iFpnH6pVZo i0G4PgMWgKS7nlZf1yg4RFF8UxYIuvDbdJnrpSXTJ06Ka66uhOHARh3KlwXDEBIS lslMyF4zRM6KMFsDfrbUAJI7mhWiNJ5BDrUDeRookkGZt1rUJ/UknwJ+mri5gmdo UYyHuelIcVZfX6mkDCKrVHCCNbdnUgWOkXtPi25n4lqjolwEeBx5y4cu4z6tkqxM 2hcnKUyj0YKScnhnTqprkbiMpPST4y8I190Q3Eo0Q8w9hvgEUa1xezfVzICScUjG VXid7aRyq7sXGmCMWyMUAvc4+5/qanreEU1686CrMQKBgQD7FDKV1pcWGJMoL5VI xiJ8jpDpnRIL940732ODjCQRgH4K9u40OmZ36W880q6o5ZXLLoWQIYZf5Hs1lq7f 3djqHFqqXtW7P7JU3mW5n/ejbMHlxra4uoTkjR6wUF+g2kW2y4ysI/t8HxSeaVjO E4cJfoaNufgmgpeKa1QnLrMpqwKBgQD6W+qgx7w2dBXSkYBDhjDoRrCwmqOuWKwG DMTDrlbu2j7hag6Xdy92CqjGAYXfhny4bpCkFLRjyrDZS4a/cObGKYYG4jVXTYfj 5cqLUiLmQFrZiK0uA7ek+qMp06FJX45iyECJlXX4gNU8e/WMfujlFLwjZ+72znQE iTMW5xxbDwKBgEBa1vRtAmDZf66HM75peqFucVpPtjZ3By5XfcxT+VK7GpN442lj pqwJm0d9wOLtpc1kaTuePDEMAUClFMGwvU6UYfDVSfcqxmzWbEB97h1nXPOmUWNb +4ARY9JRZ5F1IPVPiwj8WBNibAiGfAqmGrCmS5q8FgzY4DrMc89vOuDtAoGBAJfe aB6t6ssxcgdwwdi0LzjHoOkQdVgObBOjbTyypgNwGpLMrhtNblnxr12lkNr+Duwm DdGqyZ57VvoJaaz5xNPSXn4QfIEAA/3H6CzJX2hDA5lP4pW2JZGLhKybtwv2Tj43 8YZERvK+3Bs7qsFWPtqv0Ey+AGRw6knSHE65VScbAoGBAPUwAUmBZ6F22M6ftNL7 G/qZJsP6OmDSyvJDN9SXiLvbpXQVd6RrZrni6xzwKWAFncFMVo+HWmeAavM8zxsc a/XVSVvRj0kwg94HotHCF7/nHqYIqyF7hLZipdIphVGXx+c8dbKeVg0fgTnLRkzx tbxgs3HWa6pgQvxtVAN4Jl3W -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/examples/pki/run_all.sh0000775000175000017500000000143500000000000022605 0ustar00zuulzuul00000000000000#!/bin/bash -x # 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. # This script generates the crypto necessary for the SSL tests. . gen_pki.sh check_openssl rm_old cleanup setup generate_ca ssl_cert_req cms_signing_cert_req issue_certs gen_sample_cms cleanup ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2070036 python_keystoneclient-5.6.0/keystoneclient/0000775000175000017500000000000000000000000021246 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/__init__.py0000664000175000017500000000450700000000000023365 0ustar00zuulzuul00000000000000# Copyright 2012 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. """The python bindings for the OpenStack Identity (Keystone) project. A Client object will allow you to communicate with the Identity server. The recommended way to get a Client object is to use :py:func:`keystoneclient.client.Client()`. :py:func:`~.Client()` uses version discovery to create a V3 or V2 client depending on what versions the Identity server supports and what version is requested. Identity V2 and V3 clients can also be created directly. See :py:class:`keystoneclient.v3.client.Client` for the V3 client and :py:class:`keystoneclient.v2_0.client.Client` for the V2 client. """ import importlib import sys import pbr.version __version__ = pbr.version.VersionInfo('python-keystoneclient').version_string() __all__ = ( # Modules 'generic', 'v2_0', 'v3', # Packages 'access', 'client', 'exceptions', 'httpclient', 'service_catalog', ) class _LazyImporter(object): def __init__(self, module): self._module = module def __getattr__(self, name): # NB: this is only called until the import has been done. # These submodules are part of the API without explicit importing, but # expensive to load, so we load them on-demand rather than up-front. lazy_submodules = [ 'access', 'client', 'exceptions', 'generic', 'httpclient', 'service_catalog', 'v2_0', 'v3', ] if name in lazy_submodules: return importlib.import_module('keystoneclient.%s' % name) # Return module attributes like __all__ etc. return getattr(self._module, name) sys.modules[__name__] = _LazyImporter(sys.modules[__name__]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/_discover.py0000664000175000017500000002657000000000000023607 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. """The passive components to version discovery. The Discover object in discover.py contains functions that can create objects on your behalf. These functions are not usable from within the keystoneclient library because you will get dependency resolution issues. The Discover object in this file provides the querying components of Discovery. This includes functions like url_for which allow you to retrieve URLs and the raw data specified in version discovery responses. """ import logging import re from keystoneclient import exceptions from keystoneclient.i18n import _ _LOGGER = logging.getLogger(__name__) def get_version_data(session, url, authenticated=None): """Retrieve raw version data from a url.""" headers = {'Accept': 'application/json'} resp = session.get(url, headers=headers, authenticated=authenticated) try: body_resp = resp.json() except ValueError: # nosec(cjschaef): raise a DiscoveryFailure below pass else: # In the event of querying a root URL we will get back a list of # available versions. try: return body_resp['versions']['values'] except (KeyError, TypeError): # nosec(cjschaef): attempt to return # versions dict or query the endpoint or raise a DiscoveryFailure pass # Most servers don't have a 'values' element so accept a simple # versions dict if available. try: return body_resp['versions'] except KeyError: # nosec(cjschaef): query the endpoint or raise a # DiscoveryFailure pass # Otherwise if we query an endpoint like /v2.0 then we will get back # just the one available version. try: return [body_resp['version']] except KeyError: # nosec(cjschaef): raise a DiscoveryFailure pass err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text msg = _('Invalid Response - Bad version data returned: %s') % err_text raise exceptions.DiscoveryFailure(msg) def normalize_version_number(version): """Turn a version representation into a tuple.""" # trim the v from a 'v2.0' or similar try: version = version.lstrip('v') except AttributeError: # nosec(cjschaef): 'version' is not a str, try a # different type or raise a TypeError pass # if it's an integer or a numeric as a string then normalize it # to a string, this ensures 1 decimal point try: num = float(version) except Exception: # nosec(cjschaef): 'version' is not a float, try a # different type or raise a TypeError pass else: version = str(num) # if it's a string (or an integer) from above break it on . try: return tuple(map(int, version.split('.'))) except Exception: # nosec(cjschaef): 'version' is not str (or an int), # try a different type or raise a TypeError pass # last attempt, maybe it's a list or iterable. try: return tuple(map(int, version)) except Exception: # nosec(cjschaef): 'version' is not an expected type, # raise a TypeError pass raise TypeError(_('Invalid version specified: %s') % version) def version_match(required, candidate): """Test that an available version satisfies the required version. To be suitable a version must be of the same major version as required and be at least a match in minor/patch level. eg. 3.3 is a match for a required 3.1 but 4.1 is not. :param tuple required: the version that must be met. :param tuple candidate: the version to test against required. :returns: True if candidate is suitable False otherwise. :rtype: bool """ # major versions must be the same (e.g. even though v2 is a lower # version than v3 we can't use it if v2 was requested) if candidate[0] != required[0]: return False # prevent selecting a minor version less than what is required if candidate < required: return False return True class Discover(object): CURRENT_STATUSES = ('stable', 'current', 'supported') DEPRECATED_STATUSES = ('deprecated',) EXPERIMENTAL_STATUSES = ('experimental',) def __init__(self, session, url, authenticated=None): self._data = get_version_data(session, url, authenticated=authenticated) def raw_version_data(self, allow_experimental=False, allow_deprecated=True, allow_unknown=False): """Get raw version information from URL. Raw data indicates that only minimal validation processing is performed on the data, so what is returned here will be the data in the same format it was received from the endpoint. :param bool allow_experimental: Allow experimental version endpoints. :param bool allow_deprecated: Allow deprecated version endpoints. :param bool allow_unknown: Allow endpoints with an unrecognised status. :returns: The endpoints returned from the server that match the criteria. :rtype: list """ versions = [] for v in self._data: try: status = v['status'] except KeyError: _LOGGER.warning('Skipping over invalid version data. ' 'No stability status in version.') continue status = status.lower() if status in self.CURRENT_STATUSES: versions.append(v) elif status in self.DEPRECATED_STATUSES: if allow_deprecated: versions.append(v) elif status in self.EXPERIMENTAL_STATUSES: if allow_experimental: versions.append(v) elif allow_unknown: versions.append(v) return versions def version_data(self, **kwargs): """Get normalized version data. Return version data in a structured way. :returns: A list of version data dictionaries sorted by version number. Each data element in the returned list is a dictionary consisting of at least: :version tuple: The normalized version of the endpoint. :url str: The url for the endpoint. :raw_status str: The status as provided by the server :rtype: list(dict) """ if kwargs.pop('unstable', None): kwargs.setdefault('allow_experimental', True) kwargs.setdefault('allow_unknown', True) data = self.raw_version_data(**kwargs) versions = [] for v in data: try: version_str = v['id'] except KeyError: _LOGGER.info('Skipping invalid version data. Missing ID.') continue try: links = v['links'] except KeyError: _LOGGER.info('Skipping invalid version data. Missing links') continue version_number = normalize_version_number(version_str) for link in links: try: rel = link['rel'] url = link['href'] except (KeyError, TypeError): _LOGGER.info('Skipping invalid version link. ' 'Missing link URL or relationship.') continue if rel.lower() == 'self': break else: _LOGGER.info('Skipping invalid version data. ' 'Missing link to endpoint.') continue versions.append({'version': version_number, 'url': url, 'raw_status': v['status']}) versions.sort(key=lambda v: v['version']) return versions def data_for(self, version, **kwargs): """Return endpoint data for a version. :param tuple version: The version is always a minimum version in the same major release as there should be no compatibility issues with using a version newer than the one asked for. :returns: the endpoint data for a URL that matches the required version (the format is described in version_data) or None if no match. :rtype: dict """ version = normalize_version_number(version) version_data = self.version_data(**kwargs) for data in reversed(version_data): if version_match(version, data['version']): return data return None def url_for(self, version, **kwargs): """Get the endpoint url for a version. :param tuple version: The version is always a minimum version in the same major release as there should be no compatibility issues with using a version newer than the one asked for. :returns: The url for the specified version or None if no match. :rtype: str """ data = self.data_for(version, **kwargs) return data['url'] if data else None class _VersionHacks(object): """A container to abstract the list of version hacks. This could be done as simply a dictionary but is abstracted like this to make for easier testing. """ def __init__(self): self._discovery_data = {} def add_discover_hack(self, service_type, old, new=''): """Add a new hack for a service type. :param str service_type: The service_type in the catalog. :param re.RegexObject old: The pattern to use. :param str new: What to replace the pattern with. """ hacks = self._discovery_data.setdefault(service_type, []) hacks.append((old, new)) def get_discover_hack(self, service_type, url): """Apply the catalog hacks and figure out an unversioned endpoint. :param str service_type: the service_type to look up. :param str url: The original url that came from a service_catalog. :returns: Either the unversioned url or the one from the catalog to try. """ for old, new in self._discovery_data.get(service_type, []): new_string, number_of_subs_made = old.subn(new, url) if number_of_subs_made > 0: return new_string return url _VERSION_HACKS = _VersionHacks() _VERSION_HACKS.add_discover_hack('identity', re.compile('/v2.0/?$'), '/') def get_catalog_discover_hack(service_type, url): """Apply the catalog hacks and figure out an unversioned endpoint. This function is internal to keystoneclient. :param str service_type: the service_type to look up. :param str url: The original url that came from a service_catalog. :returns: Either the unversioned url or the one from the catalog to try. """ return _VERSION_HACKS.get_discover_hack(service_type, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/access.py0000664000175000017500000006150400000000000023067 0ustar00zuulzuul00000000000000# Copyright 2012 Nebula, Inc. # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import warnings from oslo_utils import timeutils from keystoneclient.i18n import _ from keystoneclient import service_catalog # gap, in seconds, to determine whether the given token is about to expire STALE_TOKEN_DURATION = 30 class AccessInfo(dict): """Encapsulates a raw authentication token from keystone. Provides helper methods for extracting useful values from that token. """ @classmethod def factory(cls, resp=None, body=None, region_name=None, auth_token=None, **kwargs): """Factory function to create a new AccessInfo object. Create AccessInfo object given a successful auth response & body or a user-provided dict. .. warning:: Use of the region_name argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. """ if region_name: warnings.warn( 'Use of the region_name argument is deprecated as of the ' '1.7.0 release and may be removed in the 2.0.0 release.', DeprecationWarning) if body is not None or len(kwargs): if AccessInfoV3.is_valid(body, **kwargs): if resp and not auth_token: auth_token = resp.headers['X-Subject-Token'] # NOTE(jamielennox): these return AccessInfo because they # already have auth_token installed on them. if body: if region_name: body['token']['region_name'] = region_name return AccessInfoV3(auth_token, **body['token']) else: return AccessInfoV3(auth_token, **kwargs) elif AccessInfoV2.is_valid(body, **kwargs): if body: if region_name: body['access']['region_name'] = region_name auth_ref = AccessInfoV2(**body['access']) else: auth_ref = AccessInfoV2(**kwargs) else: raise NotImplementedError(_('Unrecognized auth response')) else: auth_ref = AccessInfoV2(**kwargs) if auth_token: auth_ref.auth_token = auth_token return auth_ref def __init__(self, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, region_name=self._region_name) @property def _region_name(self): return self.get('region_name') def will_expire_soon(self, stale_duration=None): """Determine if expiration is about to occur. :returns: true if expiration is within the given duration :rtype: boolean """ stale_duration = (STALE_TOKEN_DURATION if stale_duration is None else stale_duration) norm_expires = timeutils.normalize_time(self.expires) # (gyee) should we move auth_token.will_expire_soon() to timeutils # instead of duplicating code here? soon = (timeutils.utcnow() + datetime.timedelta( seconds=stale_duration)) return norm_expires < soon @classmethod def is_valid(cls, body, **kwargs): """Determine if processing valid v2 or v3 token. Validates from the auth body or a user-provided dict. :returns: true if auth body matches implementing class :rtype: boolean """ raise NotImplementedError() def has_service_catalog(self): """Return true if the authorization token has a service catalog. :returns: boolean """ raise NotImplementedError() @property def auth_token(self): """Return the token_id associated with the auth request. To be used in headers for authenticating OpenStack API requests. :returns: str """ return self['auth_token'] @auth_token.setter def auth_token(self, value): self['auth_token'] = value @auth_token.deleter def auth_token(self): try: del self['auth_token'] except KeyError: # nosec(cjschaef): 'auth_token' is not in the dict pass @property def expires(self): """Return the token expiration (as datetime object). :returns: datetime """ raise NotImplementedError() @property def issued(self): """Return the token issue time (as datetime object). :returns: datetime """ raise NotImplementedError() @property def username(self): """Return the username associated with the auth request. Follows the pattern defined in the V2 API of first looking for 'name', returning that if available, and falling back to 'username' if name is unavailable. :returns: str """ raise NotImplementedError() @property def user_id(self): """Return the user id associated with the auth request. :returns: str """ raise NotImplementedError() @property def user_domain_id(self): """Return the user's domain id associated with the auth request. For v2, it always returns 'default' which may be different from the Keystone configuration. :returns: str """ raise NotImplementedError() @property def user_domain_name(self): """Return the user's domain name associated with the auth request. For v2, it always returns 'Default' which may be different from the Keystone configuration. :returns: str """ raise NotImplementedError() @property def role_ids(self): """Return a list of user's role ids associated with the auth request. :returns: a list of strings of role ids """ raise NotImplementedError() @property def role_names(self): """Return a list of user's role names associated with the auth request. :returns: a list of strings of role names """ raise NotImplementedError() @property def domain_name(self): """Return the domain name associated with the auth request. :returns: str or None (if no domain associated with the token) """ raise NotImplementedError() @property def domain_id(self): """Return the domain id associated with the auth request. :returns: str or None (if no domain associated with the token) """ raise NotImplementedError() @property def project_name(self): """Return the project name associated with the auth request. :returns: str or None (if no project associated with the token) """ raise NotImplementedError() @property def tenant_name(self): """Synonym for project_name.""" return self.project_name @property def scoped(self): """Return true if the auth token was scoped. Return true if scoped to a tenant(project) or domain, and contains a populated service catalog. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_scoped and may be removed in the 2.0.0 release. :returns: bool """ raise NotImplementedError() @property def project_scoped(self): """Return true if the auth token was scoped to a tenant(project). :returns: bool """ raise NotImplementedError() @property def domain_scoped(self): """Return true if the auth token was scoped to a domain. :returns: bool """ raise NotImplementedError() @property def trust_id(self): """Return the trust id associated with the auth request. :returns: str or None (if no trust associated with the token) """ raise NotImplementedError() @property def trust_scoped(self): """Return true if the auth token was scoped from a delegated trust. The trust delegation is via the OS-TRUST v3 extension. :returns: bool """ raise NotImplementedError() @property def trustee_user_id(self): """Return the trustee user id associated with a trust. :returns: str or None (if no trust associated with the token) """ raise NotImplementedError() @property def trustor_user_id(self): """Return the trustor user id associated with a trust. :returns: str or None (if no trust associated with the token) """ raise NotImplementedError() @property def project_id(self): """Return the project ID associated with the auth request. This returns None if the auth token wasn't scoped to a project. :returns: str or None (if no project associated with the token) """ raise NotImplementedError() @property def tenant_id(self): """Synonym for project_id.""" return self.project_id @property def project_domain_id(self): """Return the project's domain id associated with the auth request. For v2, it returns 'default' if a project is scoped or None which may be different from the keystone configuration. :returns: str """ raise NotImplementedError() @property def project_domain_name(self): """Return the project's domain name associated with the auth request. For v2, it returns 'Default' if a project is scoped or None which may be different from the keystone configuration. :returns: str """ raise NotImplementedError() @property def auth_url(self): """Return a tuple of identity URLs. The identity URLs are from publicURL and adminURL for the service 'identity' from the service catalog associated with the authorization request. If the authentication request wasn't scoped to a tenant (project), this property will return None. DEPRECATED: this doesn't correctly handle region name. You should fetch it from the service catalog yourself. This may be removed in the 2.0.0 release. :returns: tuple of urls """ raise NotImplementedError() @property def management_url(self): """Return the first adminURL of the identity endpoint. The identity endpoint is from the service catalog associated with the authorization request, or None if the authentication request wasn't scoped to a tenant (project). DEPRECATED: this doesn't correctly handle region name. You should fetch it from the service catalog yourself. This may be removed in the 2.0.0 release. :returns: tuple of urls """ raise NotImplementedError() @property def version(self): """Return the version of the auth token from identity service. :returns: str """ return self.get('version') @property def oauth_access_token_id(self): """Return the access token ID if OAuth authentication used. :returns: str or None. """ raise NotImplementedError() @property def oauth_consumer_id(self): """Return the consumer ID if OAuth authentication used. :returns: str or None. """ raise NotImplementedError() @property def is_federated(self): """Return true if federation was used to get the token. :returns: boolean """ raise NotImplementedError() @property def audit_id(self): """Return the audit ID if present. :returns: str or None. """ raise NotImplementedError() @property def audit_chain_id(self): """Return the audit chain ID if present. In the event that a token was rescoped then this ID will be the :py:attr:`audit_id` of the initial token. Returns None if no value present. :returns: str or None. """ raise NotImplementedError() @property def initial_audit_id(self): """The audit ID of the initially requested token. This is the :py:attr:`audit_chain_id` if present or the :py:attr:`audit_id`. """ return self.audit_chain_id or self.audit_id class AccessInfoV2(AccessInfo): """An object for encapsulating raw v2 auth token from identity service.""" def __init__(self, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.update(version='v2.0') self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, token=self['token']['id'], region_name=self._region_name) @classmethod def is_valid(cls, body, **kwargs): if body: return 'access' in body elif kwargs: return kwargs.get('version') == 'v2.0' else: return False def has_service_catalog(self): return 'serviceCatalog' in self @AccessInfo.auth_token.getter def auth_token(self): try: return super(AccessInfoV2, self).auth_token except KeyError: return self['token']['id'] @property def expires(self): return timeutils.parse_isotime(self['token']['expires']) @property def issued(self): return timeutils.parse_isotime(self['token']['issued_at']) @property def username(self): return self['user'].get('name', self['user'].get('username')) @property def user_id(self): return self['user']['id'] @property def user_domain_id(self): return 'default' @property def user_domain_name(self): return 'Default' @property def role_ids(self): return self.get('metadata', {}).get('roles', []) @property def role_names(self): return [r['name'] for r in self['user'].get('roles', [])] @property def domain_name(self): return None @property def domain_id(self): return None @property def project_name(self): try: tenant_dict = self['token']['tenant'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenant' key in # token, return the name of the tenant or None pass else: return tenant_dict.get('name') # pre grizzly try: return self['user']['tenantName'] except KeyError: # nosec(cjschaef): no 'user' key or 'tenantName' in # 'user', attempt 'tenantId' or return None pass # pre diablo, keystone only provided a tenantId try: return self['token']['tenantId'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenantName' or # 'tenantId' could be found, return None pass @property def scoped(self): """Deprecated as of the 1.7.0 release. Use project_scoped instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'scoped is deprecated as of the 1.7.0 release in favor of ' 'project_scoped and may be removed in the 2.0.0 release.', DeprecationWarning) if ('serviceCatalog' in self and self['serviceCatalog'] and 'tenant' in self['token']): return True return False @property def project_scoped(self): return 'tenant' in self['token'] @property def domain_scoped(self): return False @property def trust_id(self): return self.get('trust', {}).get('id') @property def trust_scoped(self): return 'trust' in self @property def trustee_user_id(self): return self.get('trust', {}).get('trustee_user_id') @property def trustor_user_id(self): # this information is not available in the v2 token bug: #1331882 return None @property def project_id(self): try: tenant_dict = self['token']['tenant'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenant' dict, # attempt to return 'tenantId' or return None pass else: return tenant_dict.get('id') # pre grizzly try: return self['user']['tenantId'] except KeyError: # nosec(cjschaef): no 'user' key or 'tenantId' in # 'user', attempt to retrieve from 'token' or return None pass # pre diablo try: return self['token']['tenantId'] except KeyError: # nosec(cjschaef): no 'token' key or 'tenantId' # could be found, return None pass @property def project_domain_id(self): if self.project_id: return 'default' @property def project_domain_name(self): if self.project_id: return 'Default' @property def auth_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'auth_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='publicURL', region_name=self._region_name) else: return None @property def management_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'management_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='adminURL', region_name=self._region_name) else: return None @property def oauth_access_token_id(self): return None @property def oauth_consumer_id(self): return None @property def is_federated(self): return False @property def audit_id(self): try: return self['token'].get('audit_ids', [])[0] except IndexError: return None @property def audit_chain_id(self): try: return self['token'].get('audit_ids', [])[1] except IndexError: return None class AccessInfoV3(AccessInfo): """An object encapsulating raw v3 auth token from identity service.""" def __init__(self, token, *args, **kwargs): super(AccessInfo, self).__init__(*args, **kwargs) self.update(version='v3') self.service_catalog = service_catalog.ServiceCatalog.factory( resource_dict=self, token=token, region_name=self._region_name) if token: self.auth_token = token @classmethod def is_valid(cls, body, **kwargs): if body: return 'token' in body elif kwargs: return kwargs.get('version') == 'v3' else: return False def has_service_catalog(self): return 'catalog' in self @property def is_federated(self): return 'OS-FEDERATION' in self['user'] @property def expires(self): return timeutils.parse_isotime(self['expires_at']) @property def issued(self): return timeutils.parse_isotime(self['issued_at']) @property def user_id(self): return self['user']['id'] @property def user_domain_id(self): try: return self['user']['domain']['id'] except KeyError: if self.is_federated: return None raise @property def user_domain_name(self): try: return self['user']['domain']['name'] except KeyError: if self.is_federated: return None raise @property def role_ids(self): return [r['id'] for r in self.get('roles', [])] @property def role_names(self): return [r['name'] for r in self.get('roles', [])] @property def username(self): return self['user']['name'] @property def domain_name(self): domain = self.get('domain') if domain: return domain['name'] @property def domain_id(self): domain = self.get('domain') if domain: return domain['id'] @property def project_id(self): project = self.get('project') if project: return project['id'] @property def project_domain_id(self): project = self.get('project') if project: return project['domain']['id'] @property def project_domain_name(self): project = self.get('project') if project: return project['domain']['name'] @property def project_name(self): project = self.get('project') if project: return project['name'] @property def scoped(self): """Deprecated as of the 1.7.0 release. Use project_scoped instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'scoped is deprecated as of the 1.7.0 release in favor of ' 'project_scoped and may be removed in the 2.0.0 release.', DeprecationWarning) return ('catalog' in self and self['catalog'] and 'project' in self) @property def project_scoped(self): return 'project' in self @property def domain_scoped(self): return 'domain' in self @property def trust_id(self): return self.get('OS-TRUST:trust', {}).get('id') @property def trust_scoped(self): return 'OS-TRUST:trust' in self @property def trustee_user_id(self): return self.get('OS-TRUST:trust', {}).get('trustee_user', {}).get('id') @property def trustor_user_id(self): return self.get('OS-TRUST:trust', {}).get('trustor_user', {}).get('id') @property def auth_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'auth_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='public', region_name=self._region_name) else: return None @property def management_url(self): """Deprecated as of the 1.7.0 release. Use service_catalog.get_urls() instead. It may be removed in the 2.0.0 release. """ warnings.warn( 'management_url is deprecated as of the 1.7.0 release in favor of ' 'service_catalog.get_urls() and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) if self.service_catalog: return self.service_catalog.get_urls(service_type='identity', endpoint_type='admin', region_name=self._region_name) else: return None @property def oauth_access_token_id(self): return self.get('OS-OAUTH1', {}).get('access_token_id') @property def oauth_consumer_id(self): return self.get('OS-OAUTH1', {}).get('consumer_id') @property def audit_id(self): try: return self.get('audit_ids', [])[0] except IndexError: return None @property def audit_chain_id(self): try: return self.get('audit_ids', [])[1] except IndexError: return None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/adapter.py0000664000175000017500000002113700000000000023244 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 warnings from oslo_serialization import jsonutils class Adapter(object): """An instance of a session with local variables. A session is a global object that is shared around amongst many clients. It therefore contains state that is relevant to everyone. There is a lot of state such as the service type and region_name that are only relevant to a particular client that is using the session. An adapter provides a wrapper of client local data around the global session object. :param session: The session object to wrap. :type session: keystoneclient.session.Session :param str service_type: The default service_type for URL discovery. :param str service_name: The default service_name for URL discovery. :param str interface: The default interface for URL discovery. :param str region_name: The default region_name for URL discovery. :param str endpoint_override: Always use this endpoint URL for requests for this client. :param tuple version: The version that this API targets. :param auth: An auth plugin to use instead of the session one. :type auth: keystoneclient.auth.base.BaseAuthPlugin :param str user_agent: The User-Agent string to set. :param int connect_retries: the maximum number of retries that should be attempted for connection errors. Default None - use session default which is don't retry. :param logger: A logging object to use for requests that pass through this adapter. :type logger: logging.Logger """ def __init__(self, session, service_type=None, service_name=None, interface=None, region_name=None, endpoint_override=None, version=None, auth=None, user_agent=None, connect_retries=None, logger=None): warnings.warn( 'keystoneclient.adapter.Adapter is deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1.adapter.Adapter. It will be ' 'removed in future releases.', DeprecationWarning) # NOTE(jamielennox): when adding new parameters to adapter please also # add them to the adapter call in httpclient.HTTPClient.__init__ self.session = session self.service_type = service_type self.service_name = service_name self.interface = interface self.region_name = region_name self.endpoint_override = endpoint_override self.version = version self.user_agent = user_agent self.auth = auth self.connect_retries = connect_retries self.logger = logger def _set_endpoint_filter_kwargs(self, kwargs): if self.service_type: kwargs.setdefault('service_type', self.service_type) if self.service_name: kwargs.setdefault('service_name', self.service_name) if self.interface: kwargs.setdefault('interface', self.interface) if self.region_name: kwargs.setdefault('region_name', self.region_name) if self.version: kwargs.setdefault('version', self.version) def request(self, url, method, **kwargs): endpoint_filter = kwargs.setdefault('endpoint_filter', {}) self._set_endpoint_filter_kwargs(endpoint_filter) if self.endpoint_override: kwargs.setdefault('endpoint_override', self.endpoint_override) if self.auth: kwargs.setdefault('auth', self.auth) if self.user_agent: kwargs.setdefault('user_agent', self.user_agent) if self.connect_retries is not None: kwargs.setdefault('connect_retries', self.connect_retries) if self.logger: kwargs.setdefault('logger', self.logger) return self.session.request(url, method, **kwargs) def get_token(self, auth=None): """Return a token as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :returns: A valid token. :rtype: string """ return self.session.get_token(auth or self.auth) def get_endpoint(self, auth=None, **kwargs): """Get an endpoint as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: An endpoint if available or None. :rtype: string """ if self.endpoint_override: return self.endpoint_override self._set_endpoint_filter_kwargs(kwargs) return self.session.get_endpoint(auth or self.auth, **kwargs) def invalidate(self, auth=None): """Invalidate an authentication plugin.""" return self.session.invalidate(auth or self.auth) def get_user_id(self, auth=None): """Return the authenticated user_id as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Current `user_id` or None if not supported by plugin. :rtype: string """ return self.session.get_user_id(auth or self.auth) def get_project_id(self, auth=None): """Return the authenticated project_id as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Current `project_id` or None if not supported by plugin. :rtype: string """ return self.session.get_project_id(auth or self.auth) def get(self, url, **kwargs): return self.request(url, 'GET', **kwargs) def head(self, url, **kwargs): return self.request(url, 'HEAD', **kwargs) def post(self, url, **kwargs): return self.request(url, 'POST', **kwargs) def put(self, url, **kwargs): return self.request(url, 'PUT', **kwargs) def patch(self, url, **kwargs): return self.request(url, 'PATCH', **kwargs) def delete(self, url, **kwargs): return self.request(url, 'DELETE', **kwargs) class LegacyJsonAdapter(Adapter): """Make something that looks like an old HTTPClient. A common case when using an adapter is that we want an interface similar to the HTTPClients of old which returned the body as JSON as well. You probably don't want this if you are starting from scratch. """ def request(self, *args, **kwargs): headers = kwargs.setdefault('headers', {}) headers.setdefault('Accept', 'application/json') try: kwargs['json'] = kwargs.pop('body') except KeyError: # nosec(cjschaef): kwargs doesn't contain a 'body' # key, while 'json' is an optional argument for Session.request pass resp = super(LegacyJsonAdapter, self).request(*args, **kwargs) body = None if resp.text: try: body = jsonutils.loads(resp.text) except ValueError: # nosec(cjschaef): return None for body as # expected pass return resp, body ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2110035 python_keystoneclient-5.6.0/keystoneclient/auth/0000775000175000017500000000000000000000000022207 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/__init__.py0000664000175000017500000000220300000000000024315 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. # flake8: noqa: F405 from keystoneclient.auth.base import * # noqa from keystoneclient.auth.cli import * # noqa from keystoneclient.auth.conf import * # noqa __all__ = ( # auth.base 'AUTH_INTERFACE', 'BaseAuthPlugin', 'get_available_plugin_names', 'get_available_plugin_classes', 'get_plugin_class', 'IDENTITY_AUTH_HEADER_NAME', 'PLUGIN_NAMESPACE', # auth.cli 'load_from_argparse_arguments', 'register_argparse_arguments', # auth.conf 'get_common_conf_options', 'get_plugin_options', 'load_from_conf_options', 'register_conf_options', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/base.py0000664000175000017500000003313200000000000023475 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 from debtcollector import removals from keystoneauth1 import plugin import stevedore from keystoneclient import exceptions # NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be # requested from get_endpoint. If a plugin receives this as the value of # 'interface' it should return the initial URL that was passed to the plugin. AUTH_INTERFACE = plugin.AUTH_INTERFACE PLUGIN_NAMESPACE = 'keystoneclient.auth.plugin' IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token' @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_available_plugin_names(): """Get the names of all the plugins that are available on the system. This is particularly useful for help and error text to prompt a user for example what plugins they may specify. :returns: A list of names. :rtype: frozenset """ mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE, invoke_on_load=False) return frozenset(mgr.names()) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_available_plugin_classes(): """Retrieve all the plugin classes available on the system. :returns: A dict with plugin entrypoint name as the key and the plugin class as the value. :rtype: dict """ mgr = stevedore.ExtensionManager(namespace=PLUGIN_NAMESPACE, propagate_map_exceptions=True, invoke_on_load=False) return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.plugin))) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_plugin_class(name): """Retrieve a plugin class by its entrypoint name. :param str name: The name of the object to get. :returns: An auth plugin class. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ try: mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE, name=name, invoke_on_load=False) except RuntimeError: raise exceptions.NoMatchingPlugin(name) return mgr.driver class BaseAuthPlugin(object): """The basic structure of an authentication plugin.""" def get_token(self, session, **kwargs): """Obtain a token. How the token is obtained is up to the plugin. If it is still valid it may be re-used, retrieved from cache or invoke an authentication request against a server. There are no required kwargs. They are passed directly to the auth plugin and they are implementation specific. Returning None will indicate that no token was able to be retrieved. This function is misplaced as it should only be required for auth plugins that use the 'X-Auth-Token' header. However due to the way plugins evolved this method is required and often called to trigger an authentication request on a new plugin. When implementing a new plugin it is advised that you implement this method, however if you don't require the 'X-Auth-Token' header override the `get_headers` method instead. :param session: A session object so the plugin can make HTTP calls. :type session: keystoneclient.session.Session :return: A token to use. :rtype: string """ return None def get_headers(self, session, **kwargs): """Fetch authentication headers for message. This is a more generalized replacement of the older get_token to allow plugins to specify different or additional authentication headers to the OpenStack standard 'X-Auth-Token' header. How the authentication headers are obtained is up to the plugin. If the headers are still valid they may be re-used, retrieved from cache or the plugin may invoke an authentication request against a server. The default implementation of get_headers calls the `get_token` method to enable older style plugins to continue functioning unchanged. Subclasses should feel free to completely override this function to provide the headers that they want. There are no required kwargs. They are passed directly to the auth plugin and they are implementation specific. Returning None will indicate that no token was able to be retrieved and that authorization was a failure. Adding no authentication data can be achieved by returning an empty dictionary. :param session: The session object that the auth_plugin belongs to. :type session: keystoneclient.session.Session :returns: Headers that are set to authenticate a message or None for failure. Note that when checking this value that the empty dict is a valid, non-failure response. :rtype: dict """ token = self.get_token(session) if not token: return None return {IDENTITY_AUTH_HEADER_NAME: token} def get_endpoint(self, session, **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. However there are certain standard options that will be generated by the clients and should be used by plugins: - ``service_type``: what sort of service is required. - ``service_name``: the name of the service in the catalog. - ``interface``: what visibility the endpoint should have. - ``region_name``: the region the endpoint exists in. :param session: The session object that the auth_plugin belongs to. :type session: keystoneclient.session.Session :returns: The base URL that will be used to talk to the required service or None if not available. :rtype: string """ return None def get_connection_params(self, session, **kwargs): """Return any additional connection parameters required for the plugin. :param session: The session object that the auth_plugin belongs to. :type session: keystoneclient.session.Session :returns: Headers that are set to authenticate a message or None for failure. Note that when checking this value that the empty dict is a valid, non-failure response. :rtype: dict """ return {} def invalidate(self): """Invalidate the current authentication data. This should result in fetching a new token on next call. A plugin may be invalidated if an Unauthorized HTTP response is returned to indicate that the token may have been revoked or is otherwise now invalid. :returns: True if there was something that the plugin did to invalidate. This means that it makes sense to try again. If nothing happens returns False to indicate give up. :rtype: bool """ return False def get_user_id(self, session, **kwargs): """Return a unique user identifier of the plugin. Wherever possible the user id should be inferred from the token however there are certain URLs and other places that require access to the currently authenticated user id. :param session: A session object so the plugin can make HTTP calls. :type session: keystoneclient.session.Session :returns: A user identifier or None if one is not available. :rtype: str """ return None def get_project_id(self, session, **kwargs): """Return the project id that we are authenticated to. Wherever possible the project id should be inferred from the token however there are certain URLs and other places that require access to the currently authenticated project id. :param session: A session object so the plugin can make HTTP calls. :type session: keystoneclient.session.Session :returns: A project identifier or None if one is not available. :rtype: str """ return None @classmethod def get_options(cls): """Return the list of parameters associated with the auth plugin. This list may be used to generate CLI or config arguments. :returns: A list of Param objects describing available plugin parameters. :rtype: List """ return [] @classmethod def load_from_options(cls, **kwargs): """Create a plugin from the arguments retrieved from get_options. A client can override this function to do argument validation or to handle differences between the registered options and what is required to create the plugin. """ return cls(**kwargs) @classmethod def register_argparse_arguments(cls, parser): """Register the CLI options provided by a specific plugin. Given a plugin class convert it's options into argparse arguments and add them to a parser. :param parser: the parser to attach argparse options. :type parser: argparse.ArgumentParser """ # NOTE(jamielennox): ideally oslo_config would be smart enough to # handle all the Opt manipulation that goes on in this file. However it # is currently not. Options are handled in as similar a way as # possible to oslo_config such that when available we should be able to # transition. for opt in cls.get_options(): args = [] envs = [] for o in [opt] + opt.deprecated_opts: args.append('--os-%s' % o.name) envs.append('OS_%s' % o.name.replace('-', '_').upper()) # select the first ENV that is not false-y or return None env_vars = (os.environ.get(e) for e in envs) default = next(filter(None, env_vars), None) parser.add_argument(*args, default=default or opt.default, metavar=opt.metavar, help=opt.help, dest='os_%s' % opt.dest) @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): """Load a specific plugin object from an argparse result. Convert the results of a parse into the specified plugin. :param namespace: The result from CLI parsing. :type namespace: argparse.Namespace :returns: An auth plugin, or None if a name is not provided. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` """ def _getter(opt): return getattr(namespace, 'os_%s' % opt.dest) return cls.load_from_options_getter(_getter, **kwargs) @classmethod def register_conf_options(cls, conf, group): """Register the oslo_config options that are needed for a plugin. :param conf: A config object. :type conf: oslo_config.cfg.ConfigOpts :param string group: The group name that options should be read from. """ plugin_opts = cls.get_options() conf.register_opts(plugin_opts, group=group) @classmethod def load_from_conf_options(cls, conf, group, **kwargs): """Load the plugin from a CONF object. Convert the options already registered into a real plugin. :param conf: A config object. :type conf: oslo_config.cfg.ConfigOpts :param string group: The group name that options should be read from. :returns: An authentication Plugin. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` """ def _getter(opt): return conf[group][opt.dest] return cls.load_from_options_getter(_getter, **kwargs) @classmethod def load_from_options_getter(cls, getter, **kwargs): """Load a plugin from a getter function returning appropriate values. To handle cases other than the provided CONF and CLI loading you can specify a custom loader function that will be queried for the option value. The getter is a function that takes one value, an :py:class:`oslo_config.cfg.Opt` and returns a value to load with. :param getter: A function that returns a value for the given opt. :type getter: callable :returns: An authentication Plugin. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` """ plugin_opts = cls.get_options() for opt in plugin_opts: val = getter(opt) if val is not None: val = opt.type(val) kwargs.setdefault(opt.dest, val) return cls.load_from_options(**kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/cli.py0000664000175000017500000000670000000000000023333 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 argparse import os from debtcollector import removals from keystoneclient.auth import base @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def register_argparse_arguments(parser, argv, default=None): """Register CLI options needed to create a plugin. The function inspects the provided arguments so that it can also register the options required for that specific plugin if available. :param argparse.ArgumentParser: the parser to attach argparse options to. :param List argv: the arguments provided to the application. :param str/class default: a default plugin name or a plugin object to use if one isn't specified by the CLI. default: None. :returns: The plugin class that will be loaded or None if not provided. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ in_parser = argparse.ArgumentParser(add_help=False) env_plugin = os.environ.get('OS_AUTH_PLUGIN', default) for p in (in_parser, parser): p.add_argument('--os-auth-plugin', metavar='', default=env_plugin, help='The auth plugin to load') options, _args = in_parser.parse_known_args(argv) if not options.os_auth_plugin: return None if isinstance(options.os_auth_plugin, type): msg = 'Default Authentication options' plugin = options.os_auth_plugin else: msg = 'Options specific to the %s plugin.' % options.os_auth_plugin plugin = base.get_plugin_class(options.os_auth_plugin) group = parser.add_argument_group('Authentication Options', msg) plugin.register_argparse_arguments(group) return plugin @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def load_from_argparse_arguments(namespace, **kwargs): """Retrieve the created plugin from the completed argparse results. Loads and creates the auth plugin from the information parsed from the command line by argparse. :param Namespace namespace: The result from CLI parsing. :returns: An auth plugin, or None if a name is not provided. :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ if not namespace.os_auth_plugin: return None if isinstance(namespace.os_auth_plugin, type): plugin = namespace.os_auth_plugin else: plugin = base.get_plugin_class(namespace.os_auth_plugin) return plugin.load_from_argparse_arguments(namespace, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/conf.py0000664000175000017500000001137000000000000023510 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 debtcollector import removals from oslo_config import cfg from keystoneclient.auth import base _AUTH_PLUGIN_OPT = cfg.StrOpt('auth_plugin', help='Name of the plugin to load') _section_help = 'Config Section from which to load plugin specific options' _AUTH_SECTION_OPT = cfg.StrOpt('auth_section', help=_section_help) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_common_conf_options(): """Get the oslo_config options common for all auth plugins. These may be useful without being registered for config file generation or to manipulate the options before registering them yourself. The options that are set are: :auth_plugin: The name of the plugin to load. :auth_section: The config file section to load options from. :returns: A list of oslo_config options. """ return [_AUTH_PLUGIN_OPT, _AUTH_SECTION_OPT] @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def get_plugin_options(name): """Get the oslo_config options for a specific plugin. This will be the list of config options that is registered and loaded by the specified plugin. :returns: A list of oslo_config options. """ return base.get_plugin_class(name).get_options() @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def register_conf_options(conf, group): """Register the oslo_config options that are needed for a plugin. This only registers the basic options shared by all plugins. Options that are specific to a plugin are loaded just before they are read. The defined options are: - auth_plugin: the name of the auth plugin that will be used for authentication. - auth_section: the group from which further auth plugin options should be taken. If section is not provided then the auth plugin options will be taken from the same group as provided in the parameters. :param conf: config object to register with. :type conf: oslo_config.cfg.ConfigOpts :param string group: The ini group to register options in. """ conf.register_opt(_AUTH_SECTION_OPT, group=group) # NOTE(jamielennox): plugins are allowed to specify a 'section' which is # the group that auth options should be taken from. If not present they # come from the same as the base options were registered in. If present # then the auth_plugin option may be read from that section so add that # option. if conf[group].auth_section: group = conf[group].auth_section conf.register_opt(_AUTH_PLUGIN_OPT, group=group) @removals.remove( message='keystoneclient auth plugins are deprecated. Use keystoneauth.', version='2.1.0', removal_version='3.0.0' ) def load_from_conf_options(conf, group, **kwargs): """Load a plugin from an oslo_config CONF object. Each plugin will register their own required options and so there is no standard list and the plugin should be consulted. The base options should have been registered with register_conf_options before this function is called. :param conf: A conf object. :type conf: oslo_config.cfg.ConfigOpts :param string group: The group name that options should be read from. :returns: An authentication Plugin or None if a name is not provided :rtype: :py:class:`keystoneclient.auth.BaseAuthPlugin` :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be created. """ # NOTE(jamielennox): plugins are allowed to specify a 'section' which is # the group that auth options should be taken from. If not present they # come from the same as the base options were registered in. if conf[group].auth_section: group = conf[group].auth_section name = conf[group].auth_plugin if not name: return None plugin_class = base.get_plugin_class(name) plugin_class.register_conf_options(conf, group) return plugin_class.load_from_conf_options(conf, group, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2110035 python_keystoneclient-5.6.0/keystoneclient/auth/identity/0000775000175000017500000000000000000000000024040 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/__init__.py0000664000175000017500000000210100000000000026143 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 keystoneclient.auth.identity import base from keystoneclient.auth.identity import generic from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 BaseIdentityPlugin = base.BaseIdentityPlugin V2Password = v2.Password V2Token = v2.Token V3Password = v3.Password V3Token = v3.Token Password = generic.Password Token = generic.Token __all__ = ('BaseIdentityPlugin', 'Password', 'Token', 'V2Password', 'V2Token', 'V3Password', 'V3Token') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/access.py0000664000175000017500000000350300000000000025654 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 keystoneclient.auth.identity import base class AccessInfoPlugin(base.BaseIdentityPlugin): """A plugin that turns an existing AccessInfo object into a usable plugin. There are cases where reuse of an auth_ref or AccessInfo object is warranted such as from a cache, from auth_token middleware, or another source. Turn the existing access info object into an identity plugin. This plugin cannot be refreshed as the AccessInfo object does not contain any authorizing information. :param auth_ref: the existing AccessInfo object. :type auth_ref: keystoneclient.access.AccessInfo :param auth_url: the url where this AccessInfo was retrieved from. Required if using the AUTH_INTERFACE with get_endpoint. (optional) """ def __init__(self, auth_ref, auth_url=None): super(AccessInfoPlugin, self).__init__(auth_url=auth_url, reauthenticate=False) self.auth_ref = auth_ref def get_auth_ref(self, session, **kwargs): return self.auth_ref def invalidate(self): # NOTE(jamielennox): Don't allow the default invalidation to occur # because on next authentication request we will only get the same # auth_ref object again. return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/base.py0000664000175000017500000003672400000000000025340 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 logging import threading import warnings from oslo_config import cfg from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions LOG = logging.getLogger(__name__) def get_options(): return [ cfg.StrOpt('auth-url', help='Authentication URL'), ] class BaseIdentityPlugin(base.BaseAuthPlugin, metaclass=abc.ABCMeta): # we count a token as valid (not needing refreshing) if it is valid for at # least this many seconds before the token expiry time MIN_TOKEN_LIFE_SECONDS = 120 def __init__(self, auth_url=None, username=None, password=None, token=None, trust_id=None, reauthenticate=True): super(BaseIdentityPlugin, self).__init__() warnings.warn( 'keystoneclient auth plugins are deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1 plugins. They will be removed ' 'in future releases.', DeprecationWarning) self.auth_url = auth_url self.auth_ref = None self.reauthenticate = reauthenticate self._endpoint_cache = {} self._lock = threading.Lock() self._username = username self._password = password self._token = token self._trust_id = trust_id @property def username(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'username is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._username @username.setter def username(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'username is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._username = value @property def password(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'password is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._password @password.setter def password(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'password is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._password = value @property def token(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'token is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._token @token.setter def token(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'token is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._token = value @property def trust_id(self): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'trust_id is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) return self._trust_id @trust_id.setter def trust_id(self, value): """Deprecated as of the 1.7.0 release. It may be removed in the 2.0.0 release. """ warnings.warn( 'trust_id is deprecated as of the 1.7.0 release and may be ' 'removed in the 2.0.0 release.', DeprecationWarning) self._trust_id = value @abc.abstractmethod def get_auth_ref(self, session, **kwargs): """Obtain a token from an OpenStack Identity Service. This method is overridden by the various token version plugins. This method should not be called independently and is expected to be invoked via the do_authenticate() method. This method will be invoked if the AccessInfo object cached by the plugin is not valid. Thus plugins should always fetch a new AccessInfo when invoked. If you are looking to just retrieve the current auth data then you should use get_access(). :param session: A session object that can be used for communication. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.InvalidResponse: The response returned wasn't appropriate. :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: Token access information. :rtype: :py:class:`keystoneclient.access.AccessInfo` """ pass # pragma: no cover def get_token(self, session, **kwargs): """Return a valid auth token. If a valid token is not present then a new one will be fetched. :param session: A session object that can be used for communication. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :return: A valid token. :rtype: string """ return self.get_access(session).auth_token def _needs_reauthenticate(self): """Return if the existing token needs to be re-authenticated. The token should be refreshed if it is about to expire. :returns: True if the plugin should fetch a new token. False otherwise. """ if not self.auth_ref: # authentication was never fetched. return True if not self.reauthenticate: # don't re-authenticate if it has been disallowed. return False if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS): # if it's about to expire we should re-authenticate now. return True # otherwise it's fine and use the existing one. return False def get_access(self, session, **kwargs): """Fetch or return a current AccessInfo object. If a valid AccessInfo is present then it is returned otherwise a new one will be fetched. :param session: A session object that can be used for communication. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: Valid AccessInfo :rtype: :py:class:`keystoneclient.access.AccessInfo` """ # Hey Kids! Thread safety is important particularly in the case where # a service is creating an admin style plugin that will then proceed # to make calls from many threads. As a token expires all the threads # will try and fetch a new token at once, so we want to ensure that # only one thread tries to actually fetch from keystone at once. with self._lock: if self._needs_reauthenticate(): self.auth_ref = self.get_auth_ref(session) return self.auth_ref def invalidate(self): """Invalidate the current authentication data. This should result in fetching a new token on next call. A plugin may be invalidated if an Unauthorized HTTP response is returned to indicate that the token may have been revoked or is otherwise now invalid. :returns: True if there was something that the plugin did to invalidate. This means that it makes sense to try again. If nothing happens returns False to indicate give up. :rtype: bool """ if self.auth_ref: self.auth_ref = None return True return False def get_endpoint(self, session, service_type=None, interface=None, region_name=None, service_name=None, version=None, **kwargs): """Return a valid endpoint for a service. If a valid token is not present then a new one will be fetched using the session and kwargs. :param session: A session object that can be used for communication. :type session: keystoneclient.session.Session :param string service_type: The type of service to lookup the endpoint for. This plugin will return None (failure) if service_type is not provided. :param string interface: The exposure of the endpoint. Should be `public`, `internal`, `admin`, or `auth`. `auth` is special here to use the `auth_url` rather than a URL extracted from the service catalog. Defaults to `public`. :param string region_name: The region the endpoint should exist in. (optional) :param string service_name: The name of the service in the catalog. (optional) :param tuple version: The minimum version number required for this endpoint. (optional) :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :return: A valid endpoint URL or None if not available. :rtype: string or None """ # NOTE(jamielennox): if you specifically ask for requests to be sent to # the auth url then we can ignore many of the checks. Typically if you # are asking for the auth endpoint it means that there is no catalog to # query however we still need to support asking for a specific version # of the auth_url for generic plugins. if interface is base.AUTH_INTERFACE: url = self.auth_url service_type = service_type or 'identity' else: if not service_type: LOG.warning( 'Plugin cannot return an endpoint without knowing the ' 'service type that is required. Add service_type to ' 'endpoint filtering data.') return None if not interface: interface = 'public' service_catalog = self.get_access(session).service_catalog url = service_catalog.url_for(service_type=service_type, endpoint_type=interface, region_name=region_name, service_name=service_name) if not version: # NOTE(jamielennox): This may not be the best thing to default to # but is here for backwards compatibility. It may be worth # defaulting to the most recent version. return url # NOTE(jamielennox): For backwards compatibility people might have a # versioned endpoint in their catalog even though they want to use # other endpoint versions. So we support a list of client defined # situations where we can strip the version component from a URL before # doing discovery. hacked_url = _discover.get_catalog_discover_hack(service_type, url) try: disc = self.get_discovery(session, hacked_url, authenticated=False) except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): # NOTE(jamielennox): Again if we can't contact the server we fall # back to just returning the URL from the catalog. This may not be # the best default but we need it for now. LOG.warning( 'Failed to contact the endpoint at %s for discovery. Fallback ' 'to using that endpoint as the base url.', url) else: url = disc.url_for(version) return url def get_user_id(self, session, **kwargs): return self.get_access(session).user_id def get_project_id(self, session, **kwargs): return self.get_access(session).project_id def get_discovery(self, session, url, authenticated=None): """Return the discovery object for a URL. Check the session and the plugin cache to see if we have already performed discovery on the URL and if so return it, otherwise create a new discovery object, cache it and return it. This function is expected to be used by subclasses and should not be needed by users. :param session: A session object to discover with. :type session: keystoneclient.session.Session :param str url: The url to lookup. :param bool authenticated: Include a token in the discovery call. (optional) Defaults to None (use a token if a plugin is installed). :raises keystoneclient.exceptions.DiscoveryFailure: if for some reason the lookup fails. :raises keystoneclient.exceptions.HttpError: An error from an invalid HTTP response. :returns: A discovery object with the results of looking up that URL. """ # NOTE(jamielennox): we want to cache endpoints on the session as well # so that they maintain sharing between auth plugins. Create a cache on # the session if it doesn't exist already. try: session_endpoint_cache = session._identity_endpoint_cache except AttributeError: session_endpoint_cache = session._identity_endpoint_cache = {} # NOTE(jamielennox): There is a cache located on both the session # object and the auth plugin object so that they can be shared and the # cache is still usable for cache in (self._endpoint_cache, session_endpoint_cache): disc = cache.get(url) if disc: break else: disc = _discover.Discover(session, url, authenticated=authenticated) self._endpoint_cache[url] = disc session_endpoint_cache[url] = disc return disc @classmethod def get_options(cls): options = super(BaseIdentityPlugin, cls).get_options() options.extend(get_options()) return options ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2110035 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/0000775000175000017500000000000000000000000025454 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/__init__.py0000664000175000017500000000153400000000000027570 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 keystoneclient.auth.identity.generic.base import BaseGenericPlugin # noqa from keystoneclient.auth.identity.generic.password import Password # noqa from keystoneclient.auth.identity.generic.token import Token # noqa __all__ = ('BaseGenericPlugin', 'Password', 'Token', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/base.py0000664000175000017500000001556200000000000026751 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 logging import urllib.parse as urlparse from oslo_config import cfg from keystoneclient import _discover from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient.i18n import _ LOG = logging.getLogger(__name__) def get_options(): return [ cfg.StrOpt('domain-id', help='Domain ID to scope to'), cfg.StrOpt('domain-name', help='Domain name to scope to'), cfg.StrOpt('tenant-id', help='Tenant ID to scope to'), cfg.StrOpt('tenant-name', help='Tenant name to scope to'), cfg.StrOpt('project-id', help='Project ID to scope to'), cfg.StrOpt('project-name', help='Project name to scope to'), cfg.StrOpt('project-domain-id', help='Domain ID containing project'), cfg.StrOpt('project-domain-name', help='Domain name containing project'), cfg.StrOpt('trust-id', help='Trust ID'), ] class BaseGenericPlugin(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): """An identity plugin that is not version dependent. Internally we will construct a version dependent plugin with the resolved URL and then proxy all calls from the base plugin to the versioned one. """ def __init__(self, auth_url, tenant_id=None, tenant_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, domain_id=None, domain_name=None, trust_id=None): super(BaseGenericPlugin, self).__init__(auth_url=auth_url) self._project_id = project_id or tenant_id self._project_name = project_name or tenant_name self._project_domain_id = project_domain_id self._project_domain_name = project_domain_name self._domain_id = domain_id self._domain_name = domain_name self._trust_id = trust_id self._plugin = None @property def trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value @abc.abstractmethod def create_plugin(self, session, version, url, raw_status=None): """Create a plugin from the given parameters. This function will be called multiple times with the version and url of a potential endpoint. If a plugin can be constructed that fits the params then it should return it. If not return None and then another call will be made with other available URLs. :param session: A session object. :type session: keystoneclient.session.Session :param tuple version: A tuple of the API version at the URL. :param string url: The base URL for this version. :param string raw_status: The status that was in the discovery field. :returns: A plugin that can match the parameters or None if nothing. """ return None # pragma: no cover @property def _has_domain_scope(self): """Are there domain parameters. Domain parameters are v3 only so returns if any are set. :returns: True if a domain parameter is set, false otherwise. """ return any([self._domain_id, self._domain_name, self._project_domain_id, self._project_domain_name]) @property def _v2_params(self): """Return parameters that are common to v2 plugins.""" return {'trust_id': self._trust_id, 'tenant_id': self._project_id, 'tenant_name': self._project_name} @property def _v3_params(self): """Return parameters that are common to v3 plugins.""" return {'trust_id': self._trust_id, 'project_id': self._project_id, 'project_name': self._project_name, 'project_domain_id': self._project_domain_id, 'project_domain_name': self._project_domain_name, 'domain_id': self._domain_id, 'domain_name': self._domain_name} def _do_create_plugin(self, session): plugin = None try: disc = self.get_discovery(session, self.auth_url, authenticated=False) except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): LOG.warning('Discovering versions from the identity service ' 'failed when creating the password plugin. ' 'Attempting to determine version from URL.') url_parts = urlparse.urlparse(self.auth_url) path = url_parts.path.lower() if path.startswith('/v2.0') and not self._has_domain_scope: plugin = self.create_plugin(session, (2, 0), self.auth_url) elif path.startswith('/v3'): plugin = self.create_plugin(session, (3, 0), self.auth_url) else: disc_data = disc.version_data() for data in disc_data: version = data['version'] if (_discover.version_match((2,), version) and self._has_domain_scope): # NOTE(jamielennox): if there are domain parameters there # is no point even trying against v2 APIs. continue plugin = self.create_plugin(session, version, data['url'], raw_status=data['raw_status']) if plugin: break if plugin: return plugin # so there were no URLs that i could use for auth of any version. msg = _('Could not determine a suitable URL for the plugin') raise exceptions.DiscoveryFailure(msg) def get_auth_ref(self, session, **kwargs): if not self._plugin: self._plugin = self._do_create_plugin(session) return self._plugin.get_auth_ref(session, **kwargs) @classmethod def get_options(cls): options = super(BaseGenericPlugin, cls).get_options() options.extend(get_options()) return options ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/cli.py0000664000175000017500000000650200000000000026600 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 cfg from keystoneclient.auth.identity.generic import password from keystoneclient import exceptions as exc from keystoneclient.i18n import _ class DefaultCLI(password.Password): """A Plugin that provides typical authentication options for CLIs. This plugin provides standard username and password authentication options as well as allowing users to override with a custom token and endpoint. """ def __init__(self, endpoint=None, token=None, **kwargs): super(DefaultCLI, self).__init__(**kwargs) self._token = token self._endpoint = endpoint @classmethod def get_options(cls): options = super(DefaultCLI, cls).get_options() options.extend([cfg.StrOpt('endpoint', help='A URL to use instead of a catalog'), cfg.StrOpt('token', secret=True, help='Always use the specified token')]) return options def get_token(self, *args, **kwargs): if self._token: return self._token return super(DefaultCLI, self).get_token(*args, **kwargs) def get_endpoint(self, *args, **kwargs): if self._endpoint: return self._endpoint return super(DefaultCLI, self).get_endpoint(*args, **kwargs) @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): token = kwargs.get('token') or namespace.os_token endpoint = kwargs.get('endpoint') or namespace.os_endpoint auth_url = kwargs.get('auth_url') or namespace.os_auth_url if token and not endpoint: # if a user provides a token then they must also provide an # endpoint because we aren't fetching a token to get a catalog from msg = _('A service URL must be provided with a token') raise exc.CommandError(msg) elif (not token) and (not auth_url): # if you don't provide a token you are going to provide at least an # auth_url with which to authenticate. raise exc.CommandError(_('Expecting an auth URL via either ' '--os-auth-url or env[OS_AUTH_URL]')) plugin = super(DefaultCLI, cls).load_from_argparse_arguments(namespace, **kwargs) if (not token) and (not plugin._password): # we do this after the load so that the base plugin has an # opportunity to prompt the user for a password raise exc.CommandError(_('Expecting a password provided via ' 'either --os-password, env[OS_PASSWORD], ' 'or prompted response')) return plugin ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/password.py0000664000175000017500000000673500000000000027703 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 cfg from keystoneclient import _discover from keystoneclient.auth.identity.generic import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient import utils def get_options(): return [ cfg.StrOpt('user-id', help='User id'), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('user-domain-id', help="User's domain id"), cfg.StrOpt('user-domain-name', help="User's domain name"), cfg.StrOpt('password', secret=True, help="User's password"), ] class Password(base.BaseGenericPlugin): """A common user/password authentication plugin. :param string username: Username for authentication. :param string user_id: User ID for authentication. :param string password: Password for authentication. :param string user_domain_id: User's domain ID for authentication. :param string user_domain_name: User's domain name for authentication. """ def __init__(self, auth_url, username=None, user_id=None, password=None, user_domain_id=None, user_domain_name=None, **kwargs): super(Password, self).__init__(auth_url=auth_url, **kwargs) self._username = username self._user_id = user_id self._password = password self._user_domain_id = user_domain_id self._user_domain_name = user_domain_name def create_plugin(self, session, version, url, raw_status=None): if _discover.version_match((2,), version): if self._user_domain_id or self._user_domain_name: # If you specify any domain parameters it won't work so quit. return None return v2.Password(auth_url=url, user_id=self._user_id, username=self._username, password=self._password, **self._v2_params) elif _discover.version_match((3,), version): return v3.Password(auth_url=url, user_id=self._user_id, username=self._username, user_domain_id=self._user_domain_id, user_domain_name=self._user_domain_name, password=self._password, **self._v3_params) @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend(get_options()) return options @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/generic/token.py0000664000175000017500000000310500000000000027145 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 cfg from keystoneclient import _discover from keystoneclient.auth.identity.generic import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 def get_options(): return [ cfg.StrOpt('token', secret=True, help='Token to authenticate with'), ] class Token(base.BaseGenericPlugin): """Generic token auth plugin. :param string token: Token for authentication. """ def __init__(self, auth_url, token=None, **kwargs): super(Token, self).__init__(auth_url, **kwargs) self._token = token def create_plugin(self, session, version, url, raw_status=None): if _discover.version_match((2,), version): return v2.Token(url, self._token, **self._v2_params) elif _discover.version_match((3,), version): return v3.Token(url, self._token, **self._v3_params) @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend(get_options()) return options ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v2.py0000664000175000017500000001717400000000000024753 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 logging from oslo_config import cfg from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient import utils _logger = logging.getLogger(__name__) class Auth(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): """Identity V2 Authentication Plugin. :param string auth_url: Identity service endpoint for authorization. :param string trust_id: Trust ID for trust scoping. :param string tenant_id: Tenant ID for project scoping. :param string tenant_name: Tenant name for project scoping. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ @classmethod def get_options(cls): options = super(Auth, cls).get_options() options.extend([ cfg.StrOpt('tenant-id', help='Tenant ID'), cfg.StrOpt('tenant-name', help='Tenant Name'), cfg.StrOpt('trust-id', help='Trust ID'), ]) return options def __init__(self, auth_url, trust_id=None, tenant_id=None, tenant_name=None, reauthenticate=True): super(Auth, self).__init__(auth_url=auth_url, reauthenticate=reauthenticate) self._trust_id = trust_id self.tenant_id = tenant_id self.tenant_name = tenant_name @property def trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value def get_auth_ref(self, session, **kwargs): headers = {'Accept': 'application/json'} url = self.auth_url.rstrip('/') + '/tokens' params = {'auth': self.get_auth_data(headers)} if self.tenant_id: params['auth']['tenantId'] = self.tenant_id elif self.tenant_name: params['auth']['tenantName'] = self.tenant_name if self.trust_id: params['auth']['trust_id'] = self.trust_id _logger.debug('Making authentication request to %s', url) resp = session.post(url, json=params, headers=headers, authenticated=False, log=False) try: resp_data = resp.json()['access'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV2(**resp_data) @abc.abstractmethod def get_auth_data(self, headers=None): """Return the authentication section of an auth plugin. :param dict headers: The headers that will be sent with the auth request if a plugin needs to add to them. :return: A dict of authentication data for the auth type. :rtype: dict """ pass # pragma: no cover _NOT_PASSED = object() class Password(Auth): """A plugin for authenticating with a username and password. A username or user_id must be provided. :param string auth_url: Identity service endpoint for authorization. :param string username: Username for authentication. :param string password: Password for authentication. :param string user_id: User ID for authentication. :param string trust_id: Trust ID for trust scoping. :param string tenant_id: Tenant ID for tenant scoping. :param string tenant_name: Tenant name for tenant scoping. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True :raises TypeError: if a user_id or username is not provided. """ def __init__(self, auth_url, username=_NOT_PASSED, password=None, user_id=_NOT_PASSED, **kwargs): super(Password, self).__init__(auth_url, **kwargs) if username is _NOT_PASSED and user_id is _NOT_PASSED: msg = 'You need to specify either a username or user_id' raise TypeError(msg) if username is _NOT_PASSED: username = None if user_id is _NOT_PASSED: user_id = None self.user_id = user_id self._username = username self._password = password @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value def get_auth_data(self, headers=None): auth = {'password': self.password} if self.username: auth['username'] = self.username elif self.user_id: auth['userId'] = self.user_id return {'passwordCredentials': auth} @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend([ cfg.StrOpt('username', dest='username', deprecated_name='user-name', help='Username to login with'), cfg.StrOpt('user-id', help='User ID to login with'), cfg.StrOpt('password', secret=True, help='Password to use'), ]) return options class Token(Auth): """A plugin for authenticating with an existing token. :param string auth_url: Identity service endpoint for authorization. :param string token: Existing token for authentication. :param string tenant_id: Tenant ID for tenant scoping. :param string tenant_name: Tenant name for tenant scoping. :param string trust_id: Trust ID for trust scoping. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ def __init__(self, auth_url, token, **kwargs): super(Token, self).__init__(auth_url, **kwargs) self._token = token @property def token(self): # Override to remove deprecation. return self._token @token.setter def token(self, value): # Override to remove deprecation. self._token = value def get_auth_data(self, headers=None): if headers is not None: headers['X-Auth-Token'] = self.token return {'token': {'id': self.token}} @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('token', secret=True, help='Token'), ]) return options ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2110035 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/0000775000175000017500000000000000000000000024370 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/__init__.py0000664000175000017500000000202600000000000026501 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. # flake8: noqa: F405 from keystoneclient.auth.identity.v3.base import * # noqa from keystoneclient.auth.identity.v3.federated import * # noqa from keystoneclient.auth.identity.v3.password import * # noqa from keystoneclient.auth.identity.v3.token import * # noqa __all__ = ('Auth', 'AuthConstructor', 'AuthMethod', 'BaseAuth', 'FederatedBaseAuth', 'Password', 'PasswordMethod', 'Token', 'TokenMethod') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/base.py0000664000175000017500000002467300000000000025670 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 logging from oslo_config import cfg from oslo_serialization import jsonutils from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions from keystoneclient.i18n import _ _logger = logging.getLogger(__name__) __all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth') class BaseAuth(base.BaseIdentityPlugin, metaclass=abc.ABCMeta): """Identity V3 Authentication Plugin. :param string auth_url: Identity service endpoint for authentication. :param List auth_methods: A collection of methods to authenticate with. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True :param bool include_catalog: Include the service catalog in the returned token. (optional) default True. """ def __init__(self, auth_url, trust_id=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, reauthenticate=True, include_catalog=True): super(BaseAuth, self).__init__(auth_url=auth_url, reauthenticate=reauthenticate) self._trust_id = trust_id self.domain_id = domain_id self.domain_name = domain_name self.project_id = project_id self.project_name = project_name self.project_domain_id = project_domain_id self.project_domain_name = project_domain_name self.include_catalog = include_catalog @property def trust_id(self): # Override to remove deprecation. return self._trust_id @trust_id.setter def trust_id(self, value): # Override to remove deprecation. self._trust_id = value @property def token_url(self): """The full URL where we will send authentication data.""" return '%s/auth/tokens' % self.auth_url.rstrip('/') @abc.abstractmethod def get_auth_ref(self, session, **kwargs): return None # pragma: no cover @classmethod def get_options(cls): options = super(BaseAuth, cls).get_options() options.extend([ cfg.StrOpt('domain-id', help='Domain ID to scope to'), cfg.StrOpt('domain-name', help='Domain name to scope to'), cfg.StrOpt('project-id', help='Project ID to scope to'), cfg.StrOpt('project-name', help='Project name to scope to'), cfg.StrOpt('project-domain-id', help='Domain ID containing project'), cfg.StrOpt('project-domain-name', help='Domain name containing project'), cfg.StrOpt('trust-id', help='Trust ID'), ]) return options class Auth(BaseAuth): """Identity V3 Authentication Plugin. :param string auth_url: Identity service endpoint for authentication. :param List auth_methods: A collection of methods to authenticate with. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True :param bool include_catalog: Include the service catalog in the returned token. (optional) default True. :param bool unscoped: Force the return of an unscoped token. This will make the keystone server return an unscoped token even if a default_project_id is set for this user. """ def __init__(self, auth_url, auth_methods, **kwargs): self.unscoped = kwargs.pop('unscoped', False) super(Auth, self).__init__(auth_url=auth_url, **kwargs) self.auth_methods = auth_methods def get_auth_ref(self, session, **kwargs): headers = {'Accept': 'application/json'} body = {'auth': {'identity': {}}} ident = body['auth']['identity'] rkwargs = {} for method in self.auth_methods: name, auth_data = method.get_auth_data(session, self, headers, request_kwargs=rkwargs) ident.setdefault('methods', []).append(name) ident[name] = auth_data if not ident: raise exceptions.AuthorizationFailure( _('Authentication method required (e.g. password)')) mutual_exclusion = [bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), bool(self.trust_id), bool(self.unscoped)] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure( _('Authentication cannot be scoped to multiple targets. Pick ' 'one of: project, domain, trust or unscoped')) if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} elif self.domain_name: body['auth']['scope'] = {'domain': {'name': self.domain_name}} elif self.project_id: body['auth']['scope'] = {'project': {'id': self.project_id}} elif self.project_name: scope = body['auth']['scope'] = {'project': {}} scope['project']['name'] = self.project_name if self.project_domain_id: scope['project']['domain'] = {'id': self.project_domain_id} elif self.project_domain_name: scope['project']['domain'] = {'name': self.project_domain_name} elif self.trust_id: body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}} elif self.unscoped: body['auth']['scope'] = {'unscoped': {}} # NOTE(jamielennox): we add nocatalog here rather than in token_url # directly as some federation plugins require the base token_url token_url = self.token_url if not self.include_catalog: token_url += '?nocatalog' _logger.debug('Making authentication request to %s', token_url) resp = session.post(token_url, json=body, headers=headers, authenticated=False, log=False, **rkwargs) try: _logger.debug(jsonutils.dumps(resp.json())) resp_data = resp.json()['token'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(resp.headers['X-Subject-Token'], **resp_data) class AuthMethod(object, metaclass=abc.ABCMeta): """One part of a V3 Authentication strategy. V3 Tokens allow multiple methods to be presented when authentication against the server. Each one of these methods is implemented by an AuthMethod. Note: When implementing an AuthMethod use the method_parameters and do not use positional arguments. Otherwise they can't be picked up by the factory method and don't work as well with AuthConstructors. """ _method_parameters = [] def __init__(self, **kwargs): for param in self._method_parameters: setattr(self, param, kwargs.pop(param, None)) if kwargs: msg = _("Unexpected Attributes: %s") % ", ".join(kwargs) raise AttributeError(msg) @classmethod def _extract_kwargs(cls, kwargs): """Remove parameters related to this method from other kwargs.""" return dict([(p, kwargs.pop(p, None)) for p in cls._method_parameters]) @abc.abstractmethod def get_auth_data(self, session, auth, headers, **kwargs): """Return the authentication section of an auth plugin. :param session: The communication session. :type session: keystoneclient.session.Session :param base.Auth auth: The auth plugin calling the method. :param dict headers: The headers that will be sent with the auth request if a plugin needs to add to them. :return: The identifier of this plugin and a dict of authentication data for the auth type. :rtype: tuple(string, dict) """ pass # pragma: no cover class AuthConstructor(Auth, metaclass=abc.ABCMeta): """Abstract base class for creating an Auth Plugin. The Auth Plugin created contains only one authentication method. This is generally the required usage. An AuthConstructor creates an AuthMethod based on the method's arguments and the auth_method_class defined by the plugin. It then creates the auth plugin with only that authentication method. """ _auth_method_class = None def __init__(self, auth_url, *args, **kwargs): method_kwargs = self._auth_method_class._extract_kwargs(kwargs) method = self._auth_method_class(*args, **method_kwargs) super(AuthConstructor, self).__init__(auth_url, [method], **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/federated.py0000664000175000017500000001057400000000000026674 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 from oslo_config import cfg from keystoneclient.auth.identity.v3 import base from keystoneclient.auth.identity.v3 import token __all__ = ('FederatedBaseAuth',) class FederatedBaseAuth(base.BaseAuth, metaclass=abc.ABCMeta): rescoping_plugin = token.Token def __init__(self, auth_url, identity_provider, protocol, **kwargs): """Class constructor for federated authentication plugins. Accepting following parameters: :param auth_url: URL of the Identity Service :type auth_url: string :param identity_provider: Name of the Identity Provider the client will authenticate against. This parameter will be used to build a dynamic URL used to obtain unscoped OpenStack token. :type identity_provider: string :param protocol: Protocol name configured on the keystone service provider side :type protocol: string """ super(FederatedBaseAuth, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.protocol = protocol @classmethod def get_options(cls): options = super(FederatedBaseAuth, cls).get_options() options.extend([ cfg.StrOpt('identity-provider', help="Identity Provider's name"), cfg.StrOpt('protocol', help="Name of the federated protocol used " "for federated authentication. Must " "match its counterpart name " "configured at the keystone service " "provider. Typically values would be " "'saml2' or 'oidc'.") ]) return options @property def federated_token_url(self): """Full URL where authorization data is sent.""" values = { 'host': self.auth_url.rstrip('/'), 'identity_provider': self.identity_provider, 'protocol': self.protocol } url = ("%(host)s/OS-FEDERATION/identity_providers/" "%(identity_provider)s/protocols/%(protocol)s/auth") url = url % values return url def _get_scoping_data(self): return {'trust_id': self.trust_id, 'domain_id': self.domain_id, 'domain_name': self.domain_name, 'project_id': self.project_id, 'project_name': self.project_name, 'project_domain_id': self.project_domain_id, 'project_domain_name': self.project_domain_name} def get_auth_ref(self, session, **kwargs): """Authenticate retrieve token information. This is a multi-step process where a client does federated authn receives an unscoped token. If an unscoped token is successfully received and scoping information is present then the token is rescoped to that target. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: a token data representation :rtype: :py:class:`keystoneclient.access.AccessInfo` """ auth_ref = self.get_unscoped_auth_ref(session) scoping = self._get_scoping_data() if any(scoping.values()): token_plugin = self.rescoping_plugin(self.auth_url, token=auth_ref.auth_token, **scoping) auth_ref = token_plugin.get_auth_ref(session) return auth_ref @abc.abstractmethod def get_unscoped_auth_ref(self, session, **kwargs): """Fetch unscoped federated token.""" pass # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/password.py0000664000175000017500000000743500000000000026615 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 cfg from keystoneclient.auth.identity.v3 import base from keystoneclient import utils __all__ = ('PasswordMethod', 'Password') class PasswordMethod(base.AuthMethod): """Construct a User/Password based authentication method. :param string password: Password for authentication. :param string username: Username for authentication. :param string user_id: User ID for authentication. :param string user_domain_id: User's domain ID for authentication. :param string user_domain_name: User's domain name for authentication. """ _method_parameters = ['user_id', 'username', 'user_domain_id', 'user_domain_name', 'password'] def get_auth_data(self, session, auth, headers, **kwargs): user = {'password': self.password} if self.user_id: user['id'] = self.user_id elif self.username: user['name'] = self.username if self.user_domain_id: user['domain'] = {'id': self.user_domain_id} elif self.user_domain_name: user['domain'] = {'name': self.user_domain_name} return 'password', {'user': user} class Password(base.AuthConstructor): """A plugin for authenticating with a username and password. :param string auth_url: Identity service endpoint for authentication. :param string password: Password for authentication. :param string username: Username for authentication. :param string user_id: User ID for authentication. :param string user_domain_id: User's domain ID for authentication. :param string user_domain_name: User's domain name for authentication. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ _auth_method_class = PasswordMethod @classmethod def get_options(cls): options = super(Password, cls).get_options() options.extend([ cfg.StrOpt('user-id', help='User ID'), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('user-domain-id', help="User's domain id"), cfg.StrOpt('user-domain-name', help="User's domain name"), cfg.StrOpt('password', secret=True, help="User's password"), ]) return options @classmethod def load_from_argparse_arguments(cls, namespace, **kwargs): if not (kwargs.get('password') or namespace.os_password): kwargs['password'] = utils.prompt_user_password() return super(Password, cls).load_from_argparse_arguments(namespace, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/identity/v3/token.py0000664000175000017500000000435500000000000026071 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 cfg from keystoneclient.auth.identity.v3 import base __all__ = ('TokenMethod', 'Token') class TokenMethod(base.AuthMethod): """Construct an Auth plugin to fetch a token from a token. :param string token: Token for authentication. """ _method_parameters = ['token'] def get_auth_data(self, session, auth, headers, **kwargs): headers['X-Auth-Token'] = self.token return 'token', {'id': self.token} class Token(base.AuthConstructor): """A plugin for authenticating with an existing Token. :param string auth_url: Identity service endpoint for authentication. :param string token: Token for authentication. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. :param string project_id: Project ID for project scoping. :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. :param bool reauthenticate: Allow fetching a new token if the current one is going to expire. (optional) default True """ _auth_method_class = TokenMethod def __init__(self, auth_url, token, **kwargs): super(Token, self).__init__(auth_url, token=token, **kwargs) @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('token', secret=True, help='Token to authenticate with'), ]) return options ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/auth/token_endpoint.py0000664000175000017500000000400500000000000025600 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 warnings from oslo_config import cfg from keystoneclient.auth import base class Token(base.BaseAuthPlugin): """A provider that will always use the given token and endpoint. This is really only useful for testing and in certain CLI cases where you have a known endpoint and admin token that you want to use. """ def __init__(self, endpoint, token): # NOTE(jamielennox): endpoint is reserved for when plugins # can be used to provide that information warnings.warn( 'TokenEndpoint plugin is deprecated as of the 2.1.0 release in ' 'favor of keystoneauth1.token_endpoint.Token. It will be removed ' 'in future releases.', DeprecationWarning) self.endpoint = endpoint self.token = token def get_token(self, session): return self.token def get_endpoint(self, session, **kwargs): """Return the supplied endpoint. Using this plugin the same endpoint is returned regardless of the parameters passed to the plugin. """ return self.endpoint @classmethod def get_options(cls): options = super(Token, cls).get_options() options.extend([ cfg.StrOpt('endpoint', help='The endpoint that will always be used'), cfg.StrOpt('token', secret=True, help='The token that will always be used'), ]) return options ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/base.py0000664000175000017500000005256700000000000022551 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 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. """Base utilities to build API operation managers and objects on top of.""" import abc import copy import functools import urllib import warnings from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import plugin from oslo_utils import strutils from keystoneclient import exceptions as ksc_exceptions from keystoneclient.i18n import _ class Response(object): def __init__(self, http_response, data): self.request_ids = [] if isinstance(http_response, list): # http_response is a list of in case # of pagination for resp_obj in http_response: # Extract 'x-openstack-request-id' from headers self.request_ids.append(resp_obj.headers.get( 'x-openstack-request-id')) else: self.request_ids.append(http_response.headers.get( 'x-openstack-request-id')) self.data = data def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ if getattr(obj, 'uuid', None): return obj.uuid else: return getattr(obj, 'id', obj) def filter_none(**kwargs): """Remove any entries from a dictionary where the value is None.""" return dict((k, v) for k, v in kwargs.items() if v is not None) def filter_kwargs(f): @functools.wraps(f) def func(*args, **kwargs): new_kwargs = {} for key, ref in kwargs.items(): if ref is None: # drop null values continue id_value = getid(ref) if id_value != ref: # If an object with an id was passed, then use the id, e.g.: # user: user(id=1) becomes user_id: 1 key = '%s_id' % key new_kwargs[key] = id_value return f(*args, **new_kwargs) return func class Manager(object): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. :param client: instance of BaseClient descendant for HTTP requests """ resource_class = None def __init__(self, client): super(Manager, self).__init__() self.client = client @property def api(self): """The client. .. warning:: This property is deprecated as of the 1.7.0 release in favor of :meth:`client` and may be removed in the 2.0.0 release. """ warnings.warn( 'api is deprecated as of the 1.7.0 release in favor of client and ' 'may be removed in the 2.0.0 release', DeprecationWarning) return self.client def _prepare_return_value(self, http_response, data): if self.client.include_metadata: return Response(http_response, data) return data def _list(self, url, response_key, obj_class=None, body=None, **kwargs): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param kwargs: Additional arguments will be passed to the request. """ if body: resp, body = self.client.post(url, body=body, **kwargs) else: resp, body = self.client.get(url, **kwargs) if obj_class is None: obj_class = self.resource_class data = body[response_key] # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): # nosec(cjschaef): keystone data values # not as expected (see comment above), assumption is that values # are already returned in a list (so simply utilize that list) pass return self._prepare_return_value( resp, [obj_class(self, res, loaded=True) for res in data if res]) def _get(self, url, response_key, **kwargs): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.get(url, **kwargs) return self._prepare_return_value( resp, self.resource_class(self, body[response_key], loaded=True)) def _head(self, url, **kwargs): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.head(url, **kwargs) return self._prepare_return_value(resp, resp.status_code == 204) def _post(self, url, body, response_key, return_raw=False, **kwargs): """Create an object. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.post(url, body=body, **kwargs) if return_raw: return body[response_key] return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) def _put(self, url, body=None, response_key=None, **kwargs): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.put(url, body=body, **kwargs) # PUT requests may not return a body if body is not None: if response_key is not None: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value( resp, self.resource_class(self, body)) # In some cases (e.g. 'add_endpoint_to_project' from endpoint_filters # resource), PUT request may not return a body so return None as # response along with request_id if include_metadata is True. return self._prepare_return_value(resp, body) def _patch(self, url, body=None, response_key=None, **kwargs): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.patch(url, body=body, **kwargs) if response_key is not None: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value( resp, self.resource_class(self, body)) def _delete(self, url, **kwargs): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' :param kwargs: Additional arguments will be passed to the request. """ resp, body = self.client.delete(url, **kwargs) return resp, self._prepare_return_value(resp, body) def _update(self, url, body=None, response_key=None, method="PUT", **kwargs): methods = {"PUT": self.client.put, "POST": self.client.post, "PATCH": self.client.patch} try: resp, body = methods[method](url, body=body, **kwargs) except KeyError: raise ksc_exceptions.ClientException(_("Invalid update method: %s") % method) # PUT requests may not return a body if body: return self._prepare_return_value( resp, self.resource_class(self, body[response_key])) else: return self._prepare_return_value(resp, body) class ManagerWithFind(Manager, metaclass=abc.ABCMeta): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass # pragma: no cover def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ rl = self.findall(**kwargs) if self.client.include_metadata: base_response = rl rl = rl.data base_response.data = rl[0] if len(rl) == 0: msg = _("No %(name)s matching %(kwargs)s.") % { 'name': self.resource_class.__name__, 'kwargs': kwargs} raise ksa_exceptions.NotFound(404, msg) elif len(rl) > 1: raise ksc_exceptions.NoUniqueMatch else: return base_response if self.client.include_metadata else rl[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() def _extract_data(objs, response_data): for obj in objs: try: if all(getattr(obj, attr) == value for (attr, value) in searches): response_data.append(obj) except AttributeError: continue return response_data objs = self.list() if self.client.include_metadata: # 'objs' is the object of 'Response' class. objs.data = _extract_data(objs.data, found) return objs return _extract_data(objs, found) class CrudManager(Manager): """Base manager class for manipulating Keystone entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None base_url = None def build_url(self, dict_args_in_out=None): """Build a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} If a `base_url` is provided, the generated URL will be appended to it. If a 'tail' is provided, it will be appended to the end of the URL. """ if dict_args_in_out is None: dict_args_in_out = {} url = dict_args_in_out.pop('base_url', None) or self.base_url or '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = dict_args_in_out.pop('%s_id' % self.key, None) if entity_id is not None: url += '/%s' % entity_id if dict_args_in_out.get('tail'): url += dict_args_in_out['tail'] return url @filter_kwargs def create(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) return self._post( url, {self.key: kwargs}, self.key) @filter_kwargs def get(self, **kwargs): return self._get( self.build_url(dict_args_in_out=kwargs), self.key) @filter_kwargs def head(self, **kwargs): return self._head(self.build_url(dict_args_in_out=kwargs)) def _build_query(self, params): if params is None: return '' else: # NOTE(spilla) Since the manager cannot take in a hyphen as a # key in the kwarg, it is passed in with a _. This needs to be # replaced with a proper hyphen for the URL to work properly. tags_params = ('tags_any', 'not_tags', 'not_tags_any') for tag_param in tags_params: if tag_param in params: params[tag_param.replace('_', '-')] = params.pop(tag_param) return '?%s' % urllib.parse.urlencode(params, doseq=True) def build_key_only_query(self, params_list): """Build a query that does not include values, just keys. The Identity API has some calls that define queries without values, this can not be accomplished by using urllib.parse.urlencode(). This method builds a query using only the keys. """ return '?%s' % '&'.join(params_list) if params_list else '' @filter_kwargs def list(self, fallback_to_auth=False, **kwargs): def return_resp(resp, include_metadata=False): base_response = None list_data = resp if include_metadata: base_response = resp list_data = resp.data base_response.data = list_data return base_response if include_metadata else list_data if 'id' in kwargs.keys(): # Ensure that users are not trying to call things like # ``domains.list(id='default')`` when they should have used # ``[domains.get(domain_id='default')]`` instead. Keystone supports # ``GET /v3/domains/{domain_id}``, not ``GET # /v3/domains?id={domain_id}``. raise TypeError( _("list() got an unexpected keyword argument 'id'. To " "retrieve a single object using a globally unique " "identifier, try using get() instead.")) url = self.build_url(dict_args_in_out=kwargs) try: query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % {'url': url, 'query': query} list_resp = self._list(url_query, self.collection_key) return return_resp(list_resp, include_metadata=self.client.include_metadata) except ksa_exceptions.EmptyCatalog: if fallback_to_auth: list_resp = self._list(url_query, self.collection_key, endpoint_filter={ 'interface': plugin.AUTH_INTERFACE}) return return_resp( list_resp, include_metadata=self.client.include_metadata) else: raise @filter_kwargs def put(self, **kwargs): return self._update( self.build_url(dict_args_in_out=kwargs), method='PUT') @filter_kwargs def update(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) return self._update( url, {self.key: kwargs}, self.key, method='PATCH') @filter_kwargs def delete(self, **kwargs): return self._delete( self.build_url(dict_args_in_out=kwargs)) @filter_kwargs def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``.""" url = self.build_url(dict_args_in_out=kwargs) query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % { 'url': url, 'query': query } elements = self._list( url_query, self.collection_key) if self.client.include_metadata: base_response = elements elements = elements.data base_response.data = elements[0] if not elements: msg = _("No %(name)s matching %(kwargs)s.") % { 'name': self.resource_class.__name__, 'kwargs': kwargs} raise ksa_exceptions.NotFound(404, msg) elif len(elements) > 1: raise ksc_exceptions.NoUniqueMatch else: return (base_response if self.client.include_metadata else elements[0]) class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): """Return string representation of resource attributes.""" reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def human_id(self): """Human-readable ID which can be used for bash completion.""" if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): try: try: setattr(self, k, v) except UnicodeEncodeError: # This happens when we're running with Python version that # does not support Unicode identifiers (e.g. Python 2.7). # In that case we can't help but not set this attribute; # it'll be available in a dict representation though pass self._info[k] = v except AttributeError: # nosec(cjschaef): we already defined the # attribute on the class pass def __getattr__(self, k): """Checking attribute existence.""" if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) def __eq__(self, other): """Define equality for resources.""" if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False return self._info == other._info def __ne__(self, other): """Define inequality for resources.""" return not self == other def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) def delete(self): return self.manager.delete(self) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/baseclient.py0000664000175000017500000000276700000000000023745 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 warnings class Client(object): def __init__(self, session): warnings.warn( 'keystoneclient.baseclient.Client is deprecated as of the 2.1.0 ' 'release. It will be removed in future releases.', DeprecationWarning) self.session = session def request(self, url, method, **kwargs): kwargs.setdefault('authenticated', True) return self.session.request(url, method, **kwargs) def get(self, url, **kwargs): return self.request(url, 'GET', **kwargs) def head(self, url, **kwargs): return self.request(url, 'HEAD', **kwargs) def post(self, url, **kwargs): return self.request(url, 'POST', **kwargs) def put(self, url, **kwargs): return self.request(url, 'PUT', **kwargs) def patch(self, url, **kwargs): return self.request(url, 'PATCH', **kwargs) def delete(self, url, **kwargs): return self.request(url, 'DELETE', **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/client.py0000664000175000017500000000555400000000000023107 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 debtcollector import removals from keystoneclient import discover from keystoneclient import httpclient from keystoneclient import session as client_session @removals.remove(message='Use keystoneclient.httpclient.HTTPClient instead', version='1.7.0', removal_version='2.0.0') class HTTPClient(httpclient.HTTPClient): """Deprecated alias for httpclient.HTTPClient. This class is deprecated as of the 1.7.0 release in favor of :class:`keystoneclient.httpclient.HTTPClient` and may be removed in the 2.0.0 release. """ def Client(version=None, unstable=False, session=None, **kwargs): """Factory function to create a new identity service client. The returned client will be either a V3 or V2 client. Check the version using the :py:attr:`~keystoneclient.v3.client.Client.version` property or the instance's class (with instanceof). :param tuple version: The required version of the identity API. If specified the client will be selected such that the major version is equivalent and an endpoint provides at least the specified minor version. For example to specify the 3.1 API use ``(3, 1)``. (optional) :param bool unstable: Accept endpoints not marked as 'stable'. (optional) :param session: A session object to be used for communication. If one is not provided it will be constructed from the provided kwargs. (optional) :type session: keystoneclient.session.Session :param kwargs: Additional arguments are passed through to the client that is being created. :returns: New keystone client object. :rtype: :py:class:`keystoneclient.v3.client.Client` or :py:class:`keystoneclient.v2_0.client.Client` :raises keystoneclient.exceptions.DiscoveryFailure: if the server's response is invalid. :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client cannot be found. """ if not session: session = client_session.Session._construct(kwargs) d = discover.Discover(session=session, **kwargs) return d.create_client(version=version, unstable=unstable) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/common/0000775000175000017500000000000000000000000022536 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/common/__init__.py0000664000175000017500000000000000000000000024635 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/common/cms.py0000664000175000017500000004045400000000000023701 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. """Certificate signing functions. Call set_subprocess() with the subprocess module. Either Python's subprocess or eventlet.green.subprocess can be used. If set_subprocess() is not called, this module will pick Python's subprocess or eventlet.green.subprocess based on if os module is patched by eventlet. """ import base64 import errno import hashlib import logging import zlib from debtcollector import removals from keystoneclient import exceptions from keystoneclient.i18n import _ subprocess = None LOG = logging.getLogger(__name__) PKI_ASN1_PREFIX = 'MII' PKIZ_PREFIX = 'PKIZ_' PKIZ_CMS_FORM = 'DER' PKI_ASN1_FORM = 'PEM' # Adding nosec since this fails bandit B105, 'Possible hardcoded password'. DEFAULT_TOKEN_DIGEST_ALGORITHM = 'sha256' # nosec # The openssl cms command exits with these status codes. # See https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES class OpensslCmsExitStatus(object): SUCCESS = 0 COMMAND_OPTIONS_PARSING_ERROR = 1 INPUT_FILE_READ_ERROR = 2 CREATE_CMS_READ_MIME_ERROR = 3 def _ensure_subprocess(): # NOTE(vish): late loading subprocess so we can # use the green version if we are in # eventlet. global subprocess if not subprocess: try: from eventlet import patcher if patcher.already_patched: from eventlet.green import subprocess else: import subprocess # nosec(cjschaef): we must be careful when # using subprocess.Popen with possibly untrusted data, # assumption is that the certificate/key files provided are # trustworthy except ImportError: import subprocess # noqa # nosec(cjschaef): we must be careful # when using subprocess.Popen with possibly untrusted data, # assumption is that the certificate/key files provided are # trustworthy def set_subprocess(_subprocess=None): """Set subprocess module to use. The subprocess could be eventlet.green.subprocess if using eventlet, or Python's subprocess otherwise. """ global subprocess subprocess = _subprocess def _check_files_accessible(files): err = None retcode = -1 try: for try_file in files: with open(try_file, 'r'): pass except IOError as e: # Catching IOError means there is an issue with # the given file. err = try_file, e.strerror # Emulate openssl behavior, which returns with code 2 when # access to a file failed. retcode = OpensslCmsExitStatus.INPUT_FILE_READ_ERROR return retcode, err def _process_communicate_handle_oserror(process, data, files): """Wrapper around process.communicate that checks for OSError.""" try: output, err = process.communicate(data) except OSError as e: if e.errno != errno.EPIPE: raise # OSError with EPIPE only occurs with old Python 2.7.x versions # http://bugs.python.org/issue10963 # The quick exit is typically caused by the openssl command not being # able to read an input file, so check ourselves if can't read a file. retcode, err = _check_files_accessible(files) if process.stderr: msg = process.stderr.read() if isinstance(msg, bytes): msg = msg.decode('utf-8') if err: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): ' '%(stderr)s\nLikely due to %(file)s: %(error)s') % {'stderr': msg, 'file': err[0], 'error': err[1]}) else: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): %s') % msg) output = '' else: retcode = process.poll() if err is not None: if isinstance(err, bytes): err = err.decode('utf-8') return output, err, retcode def _encoding_for_form(inform): if inform == PKI_ASN1_FORM: encoding = 'UTF-8' elif inform == PKIZ_CMS_FORM: encoding = 'hex' else: raise ValueError( _('"inform" must be one of: %s') % ','.join((PKI_ASN1_FORM, PKIZ_CMS_FORM))) return encoding def cms_verify(formatted, signing_cert_file_name, ca_file_name, inform=PKI_ASN1_FORM): """Verify the signature of the contents IAW CMS syntax. :raises subprocess.CalledProcessError: :raises keystoneclient.exceptions.CertificateConfigError: if certificate is not configured properly. """ _ensure_subprocess() if isinstance(formatted, str): data = bytes(formatted, _encoding_for_form(inform)) else: data = formatted process = subprocess.Popen(['openssl', 'cms', '-verify', '-certfile', signing_cert_file_name, '-CAfile', ca_file_name, '-inform', 'PEM', '-nosmimecap', '-nodetach', '-nocerts', '-noattr'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, ca_file_name)) # Do not log errors, as some happen in the positive thread # instead, catch them in the calling code and log them there. # When invoke the openssl >= 1.1.0 with not exist file, return code should # be 2 instead of 1 and error msg will be returned. # You can get more from # https://www.openssl.org/docs/man1.1.0/apps/cms.html#EXIT-CODES # # $ openssl cms -verify -certfile not_exist_file -CAfile # not_exist_file -inform PEM -nosmimecap -nodetach # -nocerts -noattr # openssl < 1.1.0 returns # Error opening certificate file not_exist_file # openssl >= 1.1.0 returns # cms: Cannot open input file not_exist_file, No such file or directory # if retcode == OpensslCmsExitStatus.INPUT_FILE_READ_ERROR: if err.startswith('Error reading S/MIME message'): raise exceptions.CMSError(err) else: raise exceptions.CertificateConfigError(err) # workaround for OpenSSL >= 1.1.0, # should return OpensslCmsExitStatus.INPUT_FILE_READ_ERROR elif retcode == OpensslCmsExitStatus.COMMAND_OPTIONS_PARSING_ERROR: if err.startswith('cms: Cannot open input file'): raise exceptions.CertificateConfigError(err) else: raise subprocess.CalledProcessError(retcode, 'openssl', output=err) elif retcode != OpensslCmsExitStatus.SUCCESS: raise subprocess.CalledProcessError(retcode, 'openssl', output=err) return output def is_pkiz(token_text): """Determine if a token is PKIZ. Checks if the string has the prefix that indicates it is a Crypto Message Syntax, Z compressed token. """ return token_text.startswith(PKIZ_PREFIX) def pkiz_sign(text, signing_cert_file_name, signing_key_file_name, compression_level=6, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): signed = cms_sign_data(text, signing_cert_file_name, signing_key_file_name, PKIZ_CMS_FORM, message_digest=message_digest) compressed = zlib.compress(signed, compression_level) encoded = PKIZ_PREFIX + base64.urlsafe_b64encode( compressed).decode('utf-8') return encoded def pkiz_uncompress(signed_text): text = signed_text[len(PKIZ_PREFIX):].encode('utf-8') unencoded = base64.urlsafe_b64decode(text) uncompressed = zlib.decompress(unencoded) return uncompressed def pkiz_verify(signed_text, signing_cert_file_name, ca_file_name): uncompressed = pkiz_uncompress(signed_text) return cms_verify(uncompressed, signing_cert_file_name, ca_file_name, inform=PKIZ_CMS_FORM) def token_to_cms(signed_text): """Convert a custom formatted token to a PEM-formatted token. See documentation for cms_to_token() for details on the custom formatting. """ copy_of_text = signed_text.replace('-', '/') lines = ['-----BEGIN CMS-----'] lines += [copy_of_text[n:n + 64] for n in range(0, len(copy_of_text), 64)] lines.append('-----END CMS-----\n') return '\n'.join(lines) def verify_token(token, signing_cert_file_name, ca_file_name): return cms_verify(token_to_cms(token), signing_cert_file_name, ca_file_name) def is_asn1_token(token): """Determine if a token appears to be PKI-based. thx to ayoung for sorting this out. base64 decoded hex representation of MII is 3082:: In [3]: binascii.hexlify(base64.b64decode('MII=')) Out[3]: '3082' re: http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf :: pg4: For tags from 0 to 30 the first octet is the identfier pg10: Hex 30 means sequence, followed by the length of that sequence. pg5: Second octet is the length octet first bit indicates short or long form, next 7 bits encode the number of subsequent octets that make up the content length octets as an unsigned binary int 82 = 10000010 (first bit indicates long form) 0000010 = 2 octets of content length so read the next 2 octets to get the length of the content. In the case of a very large content length there could be a requirement to have more than 2 octets to designate the content length, therefore requiring us to check for MIM, MIQ, etc. :: In [4]: base64.b64encode(binascii.a2b_hex('3083')) Out[4]: 'MIM=' In [5]: base64.b64encode(binascii.a2b_hex('3084')) Out[5]: 'MIQ=' Checking for MI would become invalid at 16 octets of content length 10010000 = 90 In [6]: base64.b64encode(binascii.a2b_hex('3090')) Out[6]: 'MJA=' Checking for just M is insufficient But we will only check for MII: Max length of the content using 2 octets is 3FFF or 16383. It's not practical to support a token of this length or greater in http therefore, we will check for MII only and ignore the case of larger tokens """ return token[:3] == PKI_ASN1_PREFIX @removals.remove(message='Use is_asn1_token() instead.', version='1.7.0', removal_version='2.0.0') def is_ans1_token(token): """Deprecated. This function is deprecated as of the 1.7.0 release in favor of :func:`is_asn1_token` and may be removed in the 2.0.0 release. """ return is_asn1_token(token) def cms_sign_text(data_to_sign, signing_cert_file_name, signing_key_file_name, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): return cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, message_digest=message_digest) def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, outform=PKI_ASN1_FORM, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): """Use OpenSSL to sign a document. Produces a Base64 encoding of a DER formatted CMS Document http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax :param data_to_sign: data to sign :param signing_cert_file_name: path to the X509 certificate containing the public key associated with the private key used to sign the data :param signing_key_file_name: path to the private key used to sign the data :param outform: Format for the signed document PKIZ_CMS_FORM or PKI_ASN1_FORM :param message_digest: Digest algorithm to use when signing or resigning """ _ensure_subprocess() if isinstance(data_to_sign, str): data = bytes(data_to_sign, encoding='utf-8') else: data = data_to_sign process = subprocess.Popen(['openssl', 'cms', '-sign', '-signer', signing_cert_file_name, '-inkey', signing_key_file_name, '-outform', 'PEM', '-nosmimecap', '-nodetach', '-nocerts', '-noattr', '-md', message_digest, ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, signing_key_file_name)) if retcode != OpensslCmsExitStatus.SUCCESS or ('Error' in err): if retcode == OpensslCmsExitStatus.CREATE_CMS_READ_MIME_ERROR: LOG.error('Signing error: Unable to load certificate - ' 'ensure you have configured PKI with ' '"keystone-manage pki_setup"') else: LOG.error('Signing error: %s', err) raise subprocess.CalledProcessError(retcode, 'openssl') if outform == PKI_ASN1_FORM: return output.decode('utf-8') else: return output def cms_sign_token(text, signing_cert_file_name, signing_key_file_name, message_digest=DEFAULT_TOKEN_DIGEST_ALGORITHM): output = cms_sign_data(text, signing_cert_file_name, signing_key_file_name, message_digest=message_digest) return cms_to_token(output) def cms_to_token(cms_text): """Convert a CMS-signed token in PEM format to a custom URL-safe format. The conversion consists of replacing '/' char in the PEM-formatted token with the '-' char and doing other such textual replacements to make the result marshallable via HTTP. The return value can thus be used as the value of a HTTP header such as "X-Auth-Token". This ad-hoc conversion is an unfortunate oversight since the returned value now does not conform to any of the standard variants of base64 encoding. It would have been better to use base64url encoding (either on the PEM formatted text or, perhaps even better, on the inner CMS-signed binary value without any PEM formatting). In any case, the same conversion is done in reverse in the other direction (for token verification), so there are no correctness issues here. Note that the non-standard encoding of the token will be preserved so as to not break backward compatibility. The conversion issue is detailed by the code author in a blog post at http://adam.younglogic.com/2014/02/compressed-tokens/. """ start_delim = '-----BEGIN CMS-----' end_delim = '-----END CMS-----' signed_text = cms_text signed_text = signed_text.replace('/', '-') signed_text = signed_text.replace(start_delim, '') signed_text = signed_text.replace(end_delim, '') signed_text = signed_text.replace('\n', '') return signed_text def cms_hash_token(token_id, mode='md5'): """Hash PKI tokens. return: for asn1 or pkiz tokens, returns the hash of the passed in token otherwise, returns what it was passed in. """ if token_id is None: return None if is_asn1_token(token_id) or is_pkiz(token_id): hasher = hashlib.new(mode) if isinstance(token_id, str): token_id = token_id.encode('utf-8') hasher.update(token_id) return hasher.hexdigest() else: return token_id ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/contrib/0000775000175000017500000000000000000000000022706 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/__init__.py0000664000175000017500000000000000000000000025005 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/0000775000175000017500000000000000000000000023647 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/__init__.py0000664000175000017500000000000000000000000025746 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/v3/0000775000175000017500000000000000000000000024177 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/v3/__init__.py0000664000175000017500000000000000000000000026276 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/v3/oidc.py0000664000175000017500000002106300000000000025471 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 cfg from keystoneclient import access from keystoneclient.auth.identity.v3 import federated class OidcPassword(federated.FederatedBaseAuth): """Implement authentication plugin for OpenID Connect protocol. OIDC or OpenID Connect is a protocol for federated authentication. The OpenID Connect specification can be found at:: ``http://openid.net/specs/openid-connect-core-1_0.html`` """ @classmethod def get_options(cls): options = super(OidcPassword, cls).get_options() options.extend([ cfg.StrOpt('username', help='Username'), cfg.StrOpt('password', secret=True, help='Password'), cfg.StrOpt('client-id', help='OAuth 2.0 Client ID'), cfg.StrOpt('client-secret', secret=True, help='OAuth 2.0 Client Secret'), cfg.StrOpt('access-token-endpoint', help='OpenID Connect Provider Token Endpoint'), cfg.StrOpt('scope', default="profile", help='OpenID Connect scope that is requested from OP') ]) return options def __init__(self, auth_url, identity_provider, protocol, username, password, client_id, client_secret, access_token_endpoint, scope='profile', grant_type='password'): """The OpenID Connect plugin. It expects the following: :param auth_url: URL of the Identity Service :type auth_url: string :param identity_provider: Name of the Identity Provider the client will authenticate against :type identity_provider: string :param protocol: Protocol name as configured in keystone :type protocol: string :param username: Username used to authenticate :type username: string :param password: Password used to authenticate :type password: string :param client_id: OAuth 2.0 Client ID :type client_id: string :param client_secret: OAuth 2.0 Client Secret :type client_secret: string :param access_token_endpoint: OpenID Connect Provider Token Endpoint, for example: https://localhost:8020/oidc/OP/token :type access_token_endpoint: string :param scope: OpenID Connect scope that is requested from OP, defaults to "profile", for example: "profile email" :type scope: string :param grant_type: OpenID Connect grant type, it represents the flow that is used to talk to the OP. Valid values are: "authorization_code", "refresh_token", or "password". :type grant_type: string """ super(OidcPassword, self).__init__(auth_url, identity_provider, protocol) self._username = username self._password = password self.client_id = client_id self.client_secret = client_secret self.access_token_endpoint = access_token_endpoint self.scope = scope self.grant_type = grant_type @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value def get_unscoped_auth_ref(self, session): """Authenticate with OpenID Connect and get back claims. This is a multi-step process. First an access token must be retrieved, to do this, the username and password, the OpenID Connect client ID and secret, and the access token endpoint must be known. Secondly, we then exchange the access token upon accessing the protected Keystone endpoint (federated auth URL). This will trigger the OpenID Connect Provider to perform a user introspection and retrieve information (specified in the scope) about the user in the form of an OpenID Connect Claim. These claims will be sent to Keystone in the form of environment variables. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: a token data representation :rtype: :py:class:`keystoneclient.access.AccessInfo` """ # get an access token client_auth = (self.client_id, self.client_secret) payload = {'grant_type': self.grant_type, 'username': self.username, 'password': self.password, 'scope': self.scope} response = self._get_access_token(session, client_auth, payload, self.access_token_endpoint) access_token = response.json()['access_token'] # use access token against protected URL headers = {'Authorization': 'Bearer ' + access_token} response = self._get_keystone_token(session, headers, self.federated_token_url) # grab the unscoped token token = response.headers['X-Subject-Token'] token_json = response.json()['token'] return access.AccessInfoV3(token, **token_json) def _get_access_token(self, session, client_auth, payload, access_token_endpoint): """Exchange a variety of user supplied values for an access token. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session :param client_auth: a tuple representing client id and secret :type client_auth: tuple :param payload: a dict containing various OpenID Connect values, for example:: {'grant_type': 'password', 'username': self.username, 'password': self.password, 'scope': self.scope} :type payload: dict :param access_token_endpoint: URL to use to get an access token, for example: https://localhost/oidc/token :type access_token_endpoint: string """ op_response = session.post(self.access_token_endpoint, requests_auth=client_auth, data=payload, authenticated=False) return op_response def _get_keystone_token(self, session, headers, federated_token_url): r"""Exchange an acess token for a keystone token. By Sending the access token in an `Authorization: Bearer` header, to an OpenID Connect protected endpoint (Federated Token URL). The OpenID Connect server will use the access token to look up information about the authenticated user (this technique is called instrospection). The output of the instrospection will be an OpenID Connect Claim, that will be used against the mapping engine. Should the mapping engine succeed, a Keystone token will be presented to the user. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session :param headers: an Authorization header containing the access token. :type headers_: dict :param federated_auth_url: Protected URL for federated authentication, for example: https://localhost:5000/v3/\ OS-FEDERATION/identity_providers/bluepages/\ protocols/oidc/auth :type federated_auth_url: string """ auth_response = session.post(self.federated_token_url, headers=headers, authenticated=False) return auth_response ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/auth/v3/saml2.py0000664000175000017500000011201200000000000025564 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 urllib.parse import uuid from lxml import etree # nosec(cjschaef): used to create xml, not parse it from oslo_config import cfg from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient import exceptions from keystoneclient.i18n import _ class _BaseSAMLPlugin(v3.AuthConstructor): HTTP_MOVED_TEMPORARILY = 302 HTTP_SEE_OTHER = 303 PROTOCOL = 'saml2' @staticmethod def _first(_list): if len(_list) != 1: raise IndexError(_("Only single element list is acceptable")) return _list[0] @staticmethod def str_to_xml(content, msg=None, include_exc=True): try: return etree.XML(content) except etree.XMLSyntaxError as e: if not msg: msg = str(e) else: msg = msg % e if include_exc else msg raise exceptions.AuthorizationFailure(msg) @staticmethod def xml_to_str(content, **kwargs): return etree.tostring(content, **kwargs) @property def token_url(self): """Return full URL where authorization data is sent.""" values = { 'host': self.auth_url.rstrip('/'), 'identity_provider': self.identity_provider, 'protocol': self.PROTOCOL } url = ("%(host)s/OS-FEDERATION/identity_providers/" "%(identity_provider)s/protocols/%(protocol)s/auth") url = url % values return url @classmethod def get_options(cls): options = super(_BaseSAMLPlugin, cls).get_options() options.extend([ cfg.StrOpt('identity-provider', help="Identity Provider's name"), cfg.StrOpt('identity-provider-url', help="Identity Provider's URL"), cfg.StrOpt('username', dest='username', help='Username', deprecated_name='user-name'), cfg.StrOpt('password', secret=True, help='Password') ]) return options class Saml2UnscopedTokenAuthMethod(v3.AuthMethod): _method_parameters = [] def get_auth_data(self, session, auth, headers, **kwargs): raise exceptions.MethodNotImplemented(_('This method should never ' 'be called')) class Saml2UnscopedToken(_BaseSAMLPlugin): r"""Implement authentication plugin for SAML2 protocol. ECP stands for `Enhanced Client or Proxy` and is a SAML2 extension for federated authentication where a transportation layer consists of HTTP protocol and XML SOAP messages. `Read for more information `_ on ECP. Reference the `SAML2 ECP specification `_. Currently only HTTPBasicAuth mechanism is available for the IdP authenication. :param auth_url: URL of the Identity Service :type auth_url: string :param identity_provider: name of the Identity Provider the client will authenticate against. This parameter will be used to build a dynamic URL used to obtain unscoped OpenStack token. :type identity_provider: string :param identity_provider_url: An Identity Provider URL, where the SAML2 authn request will be sent. :type identity_provider_url: string :param username: User's login :type username: string :param password: User's password :type password: string """ _auth_method_class = Saml2UnscopedTokenAuthMethod SAML2_HEADER_INDEX = 0 ECP_SP_EMPTY_REQUEST_HEADERS = { 'Accept': 'text/html, application/vnd.paos+xml', 'PAOS': ('ver="urn:liberty:paos:2003-08";"urn:oasis:names:tc:' 'SAML:2.0:profiles:SSO:ecp"') } ECP_SP_SAML2_REQUEST_HEADERS = { 'Content-Type': 'application/vnd.paos+xml' } ECP_SAML2_NAMESPACES = { 'ecp': 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp', 'S': 'http://schemas.xmlsoap.org/soap/envelope/', 'paos': 'urn:liberty:paos:2003-08' } ECP_RELAY_STATE = '//ecp:RelayState' ECP_SERVICE_PROVIDER_CONSUMER_URL = ('/S:Envelope/S:Header/paos:Request/' '@responseConsumerURL') ECP_IDP_CONSUMER_URL = ('/S:Envelope/S:Header/ecp:Response/' '@AssertionConsumerServiceURL') SOAP_FAULT = """ S:Server responseConsumerURL from SP and assertionConsumerServiceURL from IdP do not match """ def __init__(self, auth_url, identity_provider, identity_provider_url, username, password, **kwargs): super(Saml2UnscopedToken, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.identity_provider_url = identity_provider_url self._username, self._password = username, password @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value def _handle_http_ecp_redirect(self, session, response, method, **kwargs): if response.status_code not in (self.HTTP_MOVED_TEMPORARILY, self.HTTP_SEE_OTHER): return response location = response.headers['location'] return session.request(location, method, authenticated=False, **kwargs) def _prepare_idp_saml2_request(self, saml2_authn_request): header = saml2_authn_request[self.SAML2_HEADER_INDEX] saml2_authn_request.remove(header) def _check_consumer_urls(self, session, sp_response_consumer_url, idp_sp_response_consumer_url): """Check if consumer URLs issued by SP and IdP are equal. In the initial SAML2 authn Request issued by a Service Provider there is a url called ``consumer url``. A trusted Identity Provider should issue identical url. If the URLs are not equal the federated authn process should be interrupted and the user should be warned. :param session: session object to send out HTTP requests. :type session: keystoneclient.session.Session :param sp_response_consumer_url: consumer URL issued by a SP :type sp_response_consumer_url: string :param idp_sp_response_consumer_url: consumer URL issued by an IdP :type idp_sp_response_consumer_url: string """ if sp_response_consumer_url != idp_sp_response_consumer_url: # send fault message to the SP, discard the response session.post(sp_response_consumer_url, data=self.SOAP_FAULT, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, authenticated=False) # prepare error message and raise an exception. msg = _("Consumer URLs from Service Provider %(service_provider)s " "%(sp_consumer_url)s and Identity Provider " "%(identity_provider)s %(idp_consumer_url)s are not equal") msg = msg % { 'service_provider': self.token_url, 'sp_consumer_url': sp_response_consumer_url, 'identity_provider': self.identity_provider, 'idp_consumer_url': idp_sp_response_consumer_url } raise exceptions.ValidationError(msg) def _send_service_provider_request(self, session): """Initial HTTP GET request to the SAML2 protected endpoint. It's crucial to include HTTP headers indicating that the client is willing to take advantage of the ECP SAML2 extension and receive data as the SOAP. Unlike standard authentication methods in the OpenStack Identity, the client accesses:: ``/v3/OS-FEDERATION/identity_providers/{identity_providers}/ protocols/{protocol}/auth`` After a successful HTTP call the HTTP response should include SAML2 authn request in the XML format. If a HTTP response contains ``X-Subject-Token`` in the headers and the response body is a valid JSON assume the user was already authenticated and Keystone returned a valid unscoped token. Return True indicating the user was already authenticated. :param session: a session object to send out HTTP requests. :type session: keystoneclient.session.Session """ sp_response = session.get(self.token_url, headers=self.ECP_SP_EMPTY_REQUEST_HEADERS, authenticated=False) if 'X-Subject-Token' in sp_response.headers: self.authenticated_response = sp_response return True try: self.saml2_authn_request = etree.XML(sp_response.content) except etree.XMLSyntaxError as e: msg = _("SAML2: Error parsing XML returned " "from Service Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) relay_state = self.saml2_authn_request.xpath( self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES) self.relay_state = self._first(relay_state) sp_response_consumer_url = self.saml2_authn_request.xpath( self.ECP_SERVICE_PROVIDER_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.sp_response_consumer_url = self._first(sp_response_consumer_url) return False def _send_idp_saml2_authn_request(self, session): """Present modified SAML2 authn assertion from the Service Provider.""" self._prepare_idp_saml2_request(self.saml2_authn_request) idp_saml2_authn_request = self.saml2_authn_request # Currently HTTPBasicAuth method is hardcoded into the plugin idp_response = session.post( self.identity_provider_url, headers={'Content-type': 'text/xml'}, data=etree.tostring(idp_saml2_authn_request), requests_auth=(self.username, self.password), authenticated=False, log=False) try: self.saml2_idp_authn_response = etree.XML(idp_response.content) except etree.XMLSyntaxError as e: msg = _("SAML2: Error parsing XML returned " "from Identity Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) idp_response_consumer_url = self.saml2_idp_authn_response.xpath( self.ECP_IDP_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.idp_response_consumer_url = self._first(idp_response_consumer_url) self._check_consumer_urls(session, self.idp_response_consumer_url, self.sp_response_consumer_url) def _send_service_provider_saml2_authn_response(self, session): """Present SAML2 assertion to the Service Provider. The assertion is issued by a trusted Identity Provider for the authenticated user. This function directs the HTTP request to SP managed URL, for instance: ``https://:/Shibboleth.sso/ SAML2/ECP``. Upon success there's a session created and access to the protected resource is granted. Many implementations of the SP return HTTP 302/303 status code pointing to the protected URL (``https://:/v3/ OS-FEDERATION/identity_providers/{identity_provider}/protocols/ {protocol_id}/auth`` in this case). Saml2 plugin should point to that URL again, with HTTP GET method, expecting an unscoped token. :param session: a session object to send out HTTP requests. """ self.saml2_idp_authn_response[0][0] = self.relay_state response = session.post( self.idp_response_consumer_url, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, data=etree.tostring(self.saml2_idp_authn_response), authenticated=False, redirect=False) # Don't follow HTTP specs - after the HTTP 302/303 response don't # repeat the call directed to the Location URL. In this case, this is # an indication that saml2 session is now active and protected resource # can be accessed. response = self._handle_http_ecp_redirect( session, response, method='GET', headers=self.ECP_SP_SAML2_REQUEST_HEADERS) self.authenticated_response = response def _get_unscoped_token(self, session): """Get unscoped OpenStack token after federated authentication. This is a multi-step process including multiple HTTP requests. The federated authentication consists of:: * HTTP GET request to the Identity Service (acting as a Service Provider). Client utilizes URL:: ``/v3/OS-FEDERATION/identity_providers/{identity_provider}/ protocols/saml2/auth``. It's crucial to include HTTP headers indicating we are expecting SOAP message in return. Service Provider should respond with such SOAP message. This step is handed by a method ``Saml2UnscopedToken_send_service_provider_request()`` * HTTP POST request to the external Identity Provider service with ECP extension enabled. The content sent is a header removed SOAP message returned from the Service Provider. It's also worth noting that ECP extension to the SAML2 doesn't define authentication method. The most popular is HttpBasicAuth with just user and password. Other possibilities could be X509 certificates or Kerberos. Upon successful authentication the user should receive a SAML2 assertion. This step is handed by a method ``Saml2UnscopedToken_send_idp_saml2_authn_request(session)`` * HTTP POST request again to the Service Provider. The body of the request includes SAML2 assertion issued by a trusted Identity Provider. The request should be sent to the Service Provider consumer url specified in the SAML2 assertion. Providing the authentication was successful and both Service Provider and Identity Providers are trusted to each other, the Service Provider will issue an unscoped token with a list of groups the federated user is a member of. This step is handed by a method ``Saml2UnscopedToken_send_service_provider_saml2_authn_response()`` Unscoped token example:: { "token": { "methods": [ "saml2" ], "user": { "id": "username%40example.com", "name": "username@example.com", "OS-FEDERATION": { "identity_provider": "ACME", "protocol": "saml2", "groups": [ {"id": "abc123"}, {"id": "bcd234"} ] } } } } :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: (token, token_json) """ saml_authenticated = self._send_service_provider_request(session) if not saml_authenticated: self._send_idp_saml2_authn_request(session) self._send_service_provider_saml2_authn_response(session) return (self.authenticated_response.headers['X-Subject-Token'], self.authenticated_response.json()['token']) def get_auth_ref(self, session, **kwargs): """Authenticate via SAML2 protocol and retrieve unscoped token. This is a multi-step process where a client does federated authn receives an unscoped token. Federated authentication utilizing SAML2 Enhanced Client or Proxy extension. See ``Saml2UnscopedToken_get_unscoped_token()`` for more information on that step. Upon successful authentication and assertion mapping an unscoped token is returned and stored within the plugin object for further use. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :return: an object with scoped token's id and unscoped token json included. :rtype: :py:class:`keystoneclient.access.AccessInfoV3` """ token, token_json = self._get_unscoped_token(session) return access.AccessInfoV3(token, **token_json) class ADFSUnscopedToken(_BaseSAMLPlugin): """Authentication plugin for Microsoft ADFS2.0 IdPs. :param auth_url: URL of the Identity Service :type auth_url: string :param identity_provider: name of the Identity Provider the client will authenticate against. This parameter will be used to build a dynamic URL used to obtain unscoped OpenStack token. :type identity_provider: string :param identity_provider_url: An Identity Provider URL, where the SAML2 authentication request will be sent. :type identity_provider_url: string :param service_provider_endpoint: Endpoint where an assertion is being sent, for instance: ``https://host.domain/Shibboleth.sso/ADFS`` :type service_provider_endpoint: string :param username: User's login :type username: string :param password: User's password :type password: string """ _auth_method_class = Saml2UnscopedTokenAuthMethod DEFAULT_ADFS_TOKEN_EXPIRATION = 120 HEADER_SOAP = {"Content-Type": "application/soap+xml; charset=utf-8"} HEADER_X_FORM = {"Content-Type": "application/x-www-form-urlencoded"} NAMESPACES = { 's': 'http://www.w3.org/2003/05/soap-envelope', 'a': 'http://www.w3.org/2005/08/addressing', 'u': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-utility-1.0.xsd') } ADFS_TOKEN_NAMESPACES = { 's': 'http://www.w3.org/2003/05/soap-envelope', 't': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512' } ADFS_ASSERTION_XPATH = ('/s:Envelope/s:Body' '/t:RequestSecurityTokenResponseCollection' '/t:RequestSecurityTokenResponse') def __init__(self, auth_url, identity_provider, identity_provider_url, service_provider_endpoint, username, password, **kwargs): super(ADFSUnscopedToken, self).__init__(auth_url=auth_url, **kwargs) self.identity_provider = identity_provider self.identity_provider_url = identity_provider_url self.service_provider_endpoint = service_provider_endpoint self._username, self._password = username, password @property def username(self): # Override to remove deprecation. return self._username @username.setter def username(self, value): # Override to remove deprecation. self._username = value @property def password(self): # Override to remove deprecation. return self._password @password.setter def password(self, value): # Override to remove deprecation. self._password = value @classmethod def get_options(cls): options = super(ADFSUnscopedToken, cls).get_options() options.extend([ cfg.StrOpt('service-provider-endpoint', help="Service Provider's Endpoint") ]) return options def _cookies(self, session): """Check if cookie jar is not empty. keystoneclient.session.Session object doesn't have a cookies attribute. We should then try fetching cookies from the underlying requests.Session object. If that fails too, there is something wrong and let Python raise the AttributeError. :param session :returns: True if cookie jar is nonempty, False otherwise :raises AttributeError: in case cookies are not find anywhere """ try: return bool(session.cookies) except AttributeError: # nosec(cjschaef): fetch cookies from # underylying requests.Session object, or fail trying pass return bool(session.session.cookies) def _token_dates(self, fmt='%Y-%m-%dT%H:%M:%S.%fZ'): """Calculate created and expires datetime objects. The method is going to be used for building ADFS Request Security Token message. Time interval between ``created`` and ``expires`` dates is now static and equals to 120 seconds. ADFS security tokens should not be live too long, as currently ``keystoneclient`` doesn't have mechanisms for reusing such tokens (every time ADFS authn method is called, keystoneclient will login with the ADFS instance). :param fmt: Datetime format for specifying string format of a date. It should not be changed if the method is going to be used for building the ADFS security token request. :type fmt: string """ date_created = datetime.datetime.utcnow() date_expires = date_created + datetime.timedelta( seconds=self.DEFAULT_ADFS_TOKEN_EXPIRATION) return [_time.strftime(fmt) for _time in (date_created, date_expires)] def _prepare_adfs_request(self): """Build the ADFS Request Security Token SOAP message. Some values like username or password are inserted in the request. """ WSS_SECURITY_NAMESPACE = { 'o': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-secext-1.0.xsd') } TRUST_NAMESPACE = { 'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512' } WSP_NAMESPACE = { 'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy' } WSA_NAMESPACE = { 'wsa': 'http://www.w3.org/2005/08/addressing' } root = etree.Element( '{http://www.w3.org/2003/05/soap-envelope}Envelope', nsmap=self.NAMESPACES) header = etree.SubElement( root, '{http://www.w3.org/2003/05/soap-envelope}Header') action = etree.SubElement( header, "{http://www.w3.org/2005/08/addressing}Action") action.set( "{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") action.text = ('http://docs.oasis-open.org/ws-sx/ws-trust/200512' '/RST/Issue') messageID = etree.SubElement( header, '{http://www.w3.org/2005/08/addressing}MessageID') messageID.text = 'urn:uuid:' + uuid.uuid4().hex replyID = etree.SubElement( header, '{http://www.w3.org/2005/08/addressing}ReplyTo') address = etree.SubElement( replyID, '{http://www.w3.org/2005/08/addressing}Address') address.text = 'http://www.w3.org/2005/08/addressing/anonymous' to = etree.SubElement( header, '{http://www.w3.org/2005/08/addressing}To') to.set("{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") security = etree.SubElement( header, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-secext-1.0.xsd}Security', nsmap=WSS_SECURITY_NAMESPACE) security.set( "{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") timestamp = etree.SubElement( security, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-utility-1.0.xsd}Timestamp')) timestamp.set( ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-utility-1.0.xsd}Id'), '_0') created = etree.SubElement( timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-utility-1.0.xsd}Created')) expires = etree.SubElement( timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-utility-1.0.xsd}Expires')) created.text, expires.text = self._token_dates() usernametoken = etree.SubElement( security, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' 'wss-wssecurity-secext-1.0.xsd}UsernameToken') usernametoken.set( ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-' 'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % uuid.uuid4().hex) username = etree.SubElement( usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-' '200401-wss-wssecurity-secext-1.0.xsd}Username')) password = etree.SubElement( usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-' '200401-wss-wssecurity-secext-1.0.xsd}Password'), Type=('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-' 'username-token-profile-1.0#PasswordText')) body = etree.SubElement( root, "{http://www.w3.org/2003/05/soap-envelope}Body") request_security_token = etree.SubElement( body, ('{http://docs.oasis-open.org/ws-sx/ws-trust/200512}' 'RequestSecurityToken'), nsmap=TRUST_NAMESPACE) applies_to = etree.SubElement( request_security_token, '{http://schemas.xmlsoap.org/ws/2004/09/policy}AppliesTo', nsmap=WSP_NAMESPACE) endpoint_reference = etree.SubElement( applies_to, '{http://www.w3.org/2005/08/addressing}EndpointReference', nsmap=WSA_NAMESPACE) wsa_address = etree.SubElement( endpoint_reference, '{http://www.w3.org/2005/08/addressing}Address') keytype = etree.SubElement( request_security_token, '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}KeyType') keytype.text = ('http://docs.oasis-open.org/ws-sx/' 'ws-trust/200512/Bearer') request_type = etree.SubElement( request_security_token, '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}RequestType') request_type.text = ('http://docs.oasis-open.org/ws-sx/' 'ws-trust/200512/Issue') token_type = etree.SubElement( request_security_token, '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}TokenType') token_type.text = 'urn:oasis:names:tc:SAML:1.0:assertion' # After constructing the request, let's plug in some values username.text = self.username password.text = self.password to.text = self.identity_provider_url wsa_address.text = self.service_provider_endpoint self.prepared_request = root def _get_adfs_security_token(self, session): """Send ADFS Security token to the ADFS server. Store the result in the instance attribute and raise an exception in case the response is not valid XML data. If a user cannot authenticate due to providing bad credentials, the ADFS2.0 server will return a HTTP 500 response and a XML Fault message. If ``exceptions.InternalServerError`` is caught, the method tries to parse the XML response. If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is raised with a reason from the XML fault. Otherwise an original ``exceptions.InternalServerError`` is re-raised. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.AuthorizationFailure: when HTTP response from the ADFS server is not a valid XML ADFS security token. :raises keystoneclient.exceptions.InternalServerError: If response status code is HTTP 500 and the response XML cannot be recognized. """ def _get_failure(e): xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value' content = e.response.content try: obj = self.str_to_xml(content).xpath( xpath, namespaces=self.NAMESPACES) obj = self._first(obj) return obj.text # NOTE(marek-denis): etree.Element.xpath() doesn't raise an # exception, it just returns an empty list. In that case, _first() # will raise IndexError and we should treat it as an indication XML # is not valid. exceptions.AuthorizationFailure can be raised from # str_to_xml(), however since server returned HTTP 500 we should # re-raise exceptions.InternalServerError. except (IndexError, exceptions.AuthorizationFailure): raise e request_security_token = self.xml_to_str(self.prepared_request) try: response = session.post( url=self.identity_provider_url, headers=self.HEADER_SOAP, data=request_security_token, authenticated=False) except exceptions.InternalServerError as e: reason = _get_failure(e) raise exceptions.AuthorizationFailure(reason) msg = _("Error parsing XML returned from " "the ADFS Identity Provider, reason: %s") self.adfs_token = self.str_to_xml(response.content, msg) def _prepare_sp_request(self): """Prepare ADFS Security Token to be sent to the Service Provider. The method works as follows: * Extract SAML2 assertion from the ADFS Security Token. * Replace namespaces * urlencode assertion * concatenate static string with the encoded assertion """ assertion = self.adfs_token.xpath( self.ADFS_ASSERTION_XPATH, namespaces=self.ADFS_TOKEN_NAMESPACES) assertion = self._first(assertion) assertion = self.xml_to_str(assertion) # TODO(marek-denis): Ideally no string replacement should occur. # Unfortunately lxml doesn't allow for namespaces changing in-place and # probably the only solution good for now is to build the assertion # from scratch and reuse values from the adfs security token. assertion = assertion.replace( b'http://docs.oasis-open.org/ws-sx/ws-trust/200512', b'http://schemas.xmlsoap.org/ws/2005/02/trust') encoded_assertion = urllib.parse.quote(assertion) self.encoded_assertion = 'wa=wsignin1.0&wresult=' + encoded_assertion def _send_assertion_to_service_provider(self, session): """Send prepared assertion to a service provider. As the assertion doesn't contain a protected resource, the value from the ``location`` header is not valid and we should not let the Session object get redirected there. The aim of this call is to get a cookie in the response which is required for entering a protected endpoint. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :raises: Corresponding HTTP error exception """ session.post( url=self.service_provider_endpoint, data=self.encoded_assertion, headers=self.HEADER_X_FORM, redirect=False, authenticated=False) def _access_service_provider(self, session): """Access protected endpoint and fetch unscoped token. After federated authentication workflow a protected endpoint should be accessible with the session object. The access is granted basing on the cookies stored within the session object. If, for some reason no cookies are present (quantity test) it means something went wrong and user will not be able to fetch an unscoped token. In that case an ``exceptions.AuthorizationFailure` exception is raised and no HTTP call is even made. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.AuthorizationFailure: in case session object has empty cookie jar. """ if self._cookies(session) is False: raise exceptions.AuthorizationFailure( _("Session object doesn't contain a cookie, therefore you are " "not allowed to enter the Identity Provider's protected " "area.")) self.authenticated_response = session.get(self.token_url, authenticated=False) def _get_unscoped_token(self, session, *kwargs): """Retrieve unscoped token after authentcation with ADFS server. This is a multistep process:: * Prepare ADFS Request Securty Token - build an etree.XML object filling certain attributes with proper user credentials, created/expires dates (ticket is be valid for 120 seconds as currently we don't handle reusing ADFS issued security tokens) . Step handled by ``ADFSUnscopedToken._prepare_adfs_request()`` method. * Send ADFS Security token to the ADFS server. Step handled by ``ADFSUnscopedToken._get_adfs_security_token()`` method. * Receive and parse security token, extract actual SAML assertion and prepare a request addressed for the Service Provider endpoint. This also includes changing namespaces in the XML document. Step handled by ``ADFSUnscopedToken._prepare_sp_request()`` method. * Send prepared assertion to the Service Provider endpoint. Usually the server will respond with HTTP 301 code which should be ignored as the 'location' header doesn't contain protected area. The goal of this operation is fetching the session cookie which later allows for accessing protected URL endpoints. Step handed by ``ADFSUnscopedToken._send_assertion_to_service_provider()`` method. * Once the session cookie is issued, the protected endpoint can be accessed and an unscoped token can be retrieved. Step handled by ``ADFSUnscopedToken._access_service_provider()`` method. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :returns: (Unscoped federated token, token JSON body) """ self._prepare_adfs_request() self._get_adfs_security_token(session) self._prepare_sp_request() self._send_assertion_to_service_provider(session) self._access_service_provider(session) try: return (self.authenticated_response.headers['X-Subject-Token'], self.authenticated_response.json()['token']) except (KeyError, ValueError): raise exceptions.InvalidResponse( response=self.authenticated_response) def get_auth_ref(self, session, **kwargs): token, token_json = self._get_unscoped_token(session) return access.AccessInfoV3(token, **token_json) class Saml2ScopedTokenMethod(v3.TokenMethod): _method_name = 'saml2' def get_auth_data(self, session, auth, headers, **kwargs): """Build and return request body for token scoping step.""" t = super(Saml2ScopedTokenMethod, self).get_auth_data( session, auth, headers, **kwargs) _token_method, token = t return self._method_name, token class Saml2ScopedToken(v3.Token): """Class for scoping unscoped saml2 token.""" _auth_method_class = Saml2ScopedTokenMethod def __init__(self, auth_url, token, **kwargs): super(Saml2ScopedToken, self).__init__(auth_url, token, **kwargs) if not (self.project_id or self.domain_id): raise exceptions.ValidationError( _('Neither project nor domain specified')) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/contrib/ec2/0000775000175000017500000000000000000000000023357 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/ec2/__init__.py0000664000175000017500000000000000000000000025456 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/contrib/ec2/utils.py0000664000175000017500000002700700000000000025077 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 Justin Santa Barbara # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import hashlib import hmac import re import urllib.parse from keystoneclient.i18n import _ class Ec2Signer(object): """Utility class for EC2 signing of request. This allows a request to be signed with an AWS style signature, which can then be used for authentication via the keystone ec2 authentication extension. """ def __init__(self, secret_key): self.secret_key = secret_key.encode() self.hmac = hmac.new(self.secret_key, digestmod=hashlib.sha1) if hashlib.sha256: self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256) def _v4_creds(self, credentials): """Detect if the credentials are for a v4 signed request. Check is needed since AWS removed the SignatureVersion field from the v4 request spec... This expects a dict of the request headers to be passed in the credentials dict, since the recommended way to pass v4 creds is via the 'Authorization' header see http://docs.aws.amazon.com/general/latest/gr/ sigv4-signed-request-examples.html Alternatively X-Amz-Algorithm can be specified as a query parameter, and the authentication data can also passed as query parameters. Note a hash of the request body is also required in the credentials for v4 auth to work in the body_hash key, calculated via: hashlib.sha256(req.body).hexdigest() """ try: auth_str = credentials['headers']['Authorization'] if auth_str.startswith('AWS4-HMAC-SHA256'): return True except KeyError: # Alternatively the Authorization data can be passed via # the query params list, check X-Amz-Algorithm=AWS4-HMAC-SHA256 try: if (credentials['params']['X-Amz-Algorithm'] == 'AWS4-HMAC-SHA256'): return True except KeyError: # nosec(cjschaef): in cases of not finding # entries, simply return False pass return False def generate(self, credentials): """Generate auth string according to what SignatureVersion is given.""" signature_version = credentials['params'].get('SignatureVersion') if signature_version == '0': return self._calc_signature_0(credentials['params']) if signature_version == '1': return self._calc_signature_1(credentials['params']) if signature_version == '2': return self._calc_signature_2(credentials['params'], credentials['verb'], credentials['host'], credentials['path']) if self._v4_creds(credentials): return self._calc_signature_4(credentials['params'], credentials['verb'], credentials['host'], credentials['path'], credentials['headers'], credentials['body_hash']) if signature_version is not None: raise Exception(_('Unknown signature version: %s') % signature_version) else: raise Exception(_('Unexpected signature format')) @staticmethod def _get_utf8_value(value): """Get the UTF8-encoded version of a value.""" if not isinstance(value, (str, bytes)): value = str(value) if isinstance(value, str): return value.encode('utf-8') else: return value def _calc_signature_0(self, params): """Generate AWS signature version 0 string.""" s = (params['Action'] + params['Timestamp']).encode('utf-8') self.hmac.update(s) return base64.b64encode(self.hmac.digest()).decode('utf-8') def _calc_signature_1(self, params): """Generate AWS signature version 1 string.""" for key in sorted(params, key=str.lower): self.hmac.update(key.encode('utf-8')) val = self._get_utf8_value(params[key]) self.hmac.update(val) return base64.b64encode(self.hmac.digest()).decode('utf-8') @staticmethod def _canonical_qs(params): """Construct a sorted, correctly encoded query string. This is required for _calc_signature_2 and _calc_signature_4. """ keys = list(params) keys.sort() pairs = [] for key in keys: val = Ec2Signer._get_utf8_value(params[key]) val = urllib.parse.quote(val, safe='-_~') pairs.append(urllib.parse.quote(key, safe='') + '=' + val) qs = '&'.join(pairs) return qs def _calc_signature_2(self, params, verb, server_string, path): """Generate AWS signature version 2 string.""" string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path) if self.hmac_256: current_hmac = self.hmac_256 params['SignatureMethod'] = 'HmacSHA256' else: current_hmac = self.hmac params['SignatureMethod'] = 'HmacSHA1' string_to_sign += self._canonical_qs(params) current_hmac.update(string_to_sign.encode('utf-8')) b64 = base64.b64encode(current_hmac.digest()).decode('utf-8') return b64 def _calc_signature_4(self, params, verb, server_string, path, headers, body_hash): """Generate AWS signature version 4 string.""" def sign(key, msg): return hmac.new(key, self._get_utf8_value(msg), hashlib.sha256).digest() def signature_key(datestamp, region_name, service_name): """Signature key derivation. See http://docs.aws.amazon.com/general/latest/gr/ signature-v4-examples.html#signature-v4-examples-python """ k_date = sign(self._get_utf8_value(b"AWS4" + self.secret_key), datestamp) k_region = sign(k_date, region_name) k_service = sign(k_region, service_name) k_signing = sign(k_service, "aws4_request") return k_signing def auth_param(param_name): """Get specified auth parameter. Provided via one of: - the Authorization header - the X-Amz-* query parameters """ try: auth_str = headers['Authorization'] param_str = auth_str.partition( '%s=' % param_name)[2].split(',')[0] except KeyError: param_str = params.get('X-Amz-%s' % param_name) return param_str def date_param(): """Get the X-Amz-Date' value. The value can be either a header or parameter. Note AWS supports parsing the Date header also, but this is not currently supported here as it will require some format mangling So the X-Amz-Date value must be YYYYMMDDTHHMMSSZ format, then it can be used to match against the YYYYMMDD format provided in the credential scope. see: http://docs.aws.amazon.com/general/latest/gr/ sigv4-date-handling.html """ try: return headers['X-Amz-Date'] except KeyError: return params.get('X-Amz-Date') def canonical_header_str(): # Get the list of headers to include, from either # - the Authorization header (SignedHeaders key) # - the X-Amz-SignedHeaders query parameter headers_lower = dict((k.lower().strip(), v.strip()) for (k, v) in headers.items()) # Boto versions < 2.9.3 strip the port component of the host:port # header, so detect the user-agent via the header and strip the # port if we detect an old boto version. FIXME: remove when all # distros package boto >= 2.9.3, this is a transitional workaround user_agent = headers_lower.get('user-agent', '') strip_port = re.match(r'Boto/2\.[0-9]\.[0-2]', user_agent) header_list = [] sh_str = auth_param('SignedHeaders') for h in sh_str.split(';'): if h not in headers_lower: continue if h == 'host' and strip_port: header_list.append('%s:%s' % (h, headers_lower[h].split(':')[0])) continue header_list.append('%s:%s' % (h, headers_lower[h])) return '\n'.join(header_list) + '\n' def canonical_query_str(verb, params): # POST requests pass parameters in through the request body canonical_qs = '' if verb.upper() != 'POST': canonical_qs = self._canonical_qs(params) return canonical_qs # Create canonical request: # http://docs.aws.amazon.com/general/latest/gr/ # sigv4-create-canonical-request.html # Get parameters and headers in expected string format cr = "\n".join((verb.upper(), path, canonical_query_str(verb, params), canonical_header_str(), auth_param('SignedHeaders'), body_hash)) # Check the date, reject any request where the X-Amz-Date doesn't # match the credential scope credential = auth_param('Credential') credential_split = credential.split('/') credential_scope = '/'.join(credential_split[1:]) credential_date = credential_split[1] param_date = date_param() if not param_date.startswith(credential_date): raise Exception(_('Request date mismatch error')) # Create the string to sign # http://docs.aws.amazon.com/general/latest/gr/ # sigv4-create-string-to-sign.html cr = cr.encode('utf-8') string_to_sign = '\n'.join(('AWS4-HMAC-SHA256', param_date, credential_scope, hashlib.sha256(cr).hexdigest())) # Calculate the derived key, this requires a datestamp, region # and service, which can be extracted from the credential scope (req_region, req_service) = credential_split[2:4] s_key = signature_key(credential_date, req_region, req_service) # Finally calculate the signature! signature = hmac.new(s_key, self._get_utf8_value(string_to_sign), hashlib.sha256).hexdigest() return signature ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/discover.py0000664000175000017500000003734400000000000023451 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 warnings from debtcollector import removals from keystoneauth1 import plugin from keystoneclient import _discover from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client _CLIENT_VERSIONS = {2: v2_client.Client, 3: v3_client.Client} # functions needed from the private file that can be made public def normalize_version_number(version): """Turn a version representation into a tuple. Takes a string, tuple or float which represent version formats we can handle and converts them into a (major, minor) version tuple that we can actually use for discovery. e.g. 'v3.3' gives (3, 3) 3.1 gives (3, 1) :param version: Inputted version number to try and convert. :returns: A usable version tuple :rtype: tuple :raises TypeError: if the inputted version cannot be converted to tuple. """ return _discover.normalize_version_number(version) def version_match(required, candidate): """Test that an available version satisfies the required version. To be suitable a version must be of the same major version as required and be at least a match in minor/patch level. eg. 3.3 is a match for a required 3.1 but 4.1 is not. :param tuple required: the version that must be met. :param tuple candidate: the version to test against required. :returns: True if candidate is suitable False otherwise. :rtype: bool """ return _discover.version_match(required, candidate) def available_versions(url, session=None, **kwargs): """Retrieve raw version data from a url.""" if not session: session = client_session.Session._construct(kwargs) return _discover.get_version_data(session, url) class Discover(_discover.Discover): """A means to discover and create clients. Clients are created depending on the supported API versions on the server. Querying the server is done on object creation and every subsequent method operates upon the data that was retrieved. The connection parameters associated with this method are the same format and name as those used by a client (see :py:class:`keystoneclient.v2_0.client.Client` and :py:class:`keystoneclient.v3.client.Client`). If not overridden in subsequent methods they will also be what is passed to the constructed client. In the event that auth_url and endpoint is provided then auth_url will be used in accordance with how the client operates. .. warning:: Creating an instance of this class without using the session argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param session: A session object that will be used for communication. Clients will also be constructed with this session. :type session: keystoneclient.session.Session :param string auth_url: Identity service endpoint for authorization. (optional) :param string endpoint: A user-supplied endpoint URL for the identity service. (optional) :param string original_ip: The original IP of the requesting user which will be sent to identity service in a 'Forwarded' header. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param boolean debug: Enables debug logging of all request and responses to the identity service. default False (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string cacert: Path to the Privacy Enhanced Mail (PEM) file which contains the trusted authority X.509 certificates needed to established SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string key: Path to the Privacy Enhanced Mail (PEM) file which contains the unencrypted client private key needed to established two-way SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string cert: Path to the Privacy Enhanced Mail (PEM) file which contains the corresponding X.509 client certificate needed to established two-way SSL connection with the identity service. (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param boolean insecure: Does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) This is ignored if a session is provided. Deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param bool authenticated: Should a token be used to perform the initial discovery operations. default: None (attach a token if an auth plugin is available). """ def __init__(self, session=None, authenticated=None, **kwargs): if not session: warnings.warn( 'Constructing a Discover instance without using a session is ' 'deprecated as of the 1.7.0 release and may be removed in the ' '2.0.0 release.', DeprecationWarning) session = client_session.Session._construct(kwargs) kwargs['session'] = session url = None endpoint = kwargs.pop('endpoint', None) auth_url = kwargs.pop('auth_url', None) if endpoint: self._use_endpoint = True url = endpoint elif auth_url: self._use_endpoint = False url = auth_url elif session.auth: self._use_endpoint = False url = session.get_endpoint(interface=plugin.AUTH_INTERFACE) if not url: raise exceptions.DiscoveryFailure( _('Not enough information to determine URL. Provide' ' either a Session, or auth_url or endpoint')) self._client_kwargs = kwargs super(Discover, self).__init__(session, url, authenticated=authenticated) @removals.remove(message='Use raw_version_data instead.', version='1.7.0', removal_version='2.0.0') def available_versions(self, **kwargs): """Return a list of identity APIs available on the server. The list returned includes the data associated with them. .. warning:: This method is deprecated as of the 1.7.0 release in favor of :meth:`raw_version_data` and may be removed in the 2.0.0 release. :param bool unstable: Accept endpoints not marked 'stable'. (optional) Equates to setting allow_experimental and allow_unknown to True. :param bool allow_experimental: Allow experimental version endpoints. :param bool allow_deprecated: Allow deprecated version endpoints. :param bool allow_unknown: Allow endpoints with an unrecognised status. :returns: A List of dictionaries as presented by the server. Each dict will contain the version and the URL to use for the version. It is a direct representation of the layout presented by the identity API. """ return self.raw_version_data(**kwargs) @removals.removed_kwarg( 'unstable', message='Use allow_experimental and allow_unknown instead.', version='1.7.0', removal_version='2.0.0') def raw_version_data(self, unstable=False, **kwargs): """Get raw version information from URL. Raw data indicates that only minimal validation processing is performed on the data, so what is returned here will be the data in the same format it was received from the endpoint. :param bool unstable: equates to setting allow_experimental and allow_unknown. This argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param bool allow_experimental: Allow experimental version endpoints. :param bool allow_deprecated: Allow deprecated version endpoints. :param bool allow_unknown: Allow endpoints with an unrecognised status. :returns: The endpoints returned from the server that match the criteria. :rtype: List Example:: >>> from keystoneclient import discover >>> disc = discover.Discovery(auth_url='http://localhost:5000') >>> disc.raw_version_data() [{'id': 'v3.0', 'links': [{'href': 'http://127.0.0.1:5000/v3/', 'rel': 'self'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v3+json'}, {'base': 'application/xml', 'type': 'application/vnd.openstack.identity-v3+xml'}], 'status': 'stable', 'updated': '2013-03-06T00:00:00Z'}, {'id': 'v2.0', 'links': [{'href': 'http://127.0.0.1:5000/v2.0/', 'rel': 'self'}, {'href': '...', 'rel': 'describedby', 'type': 'application/pdf'}], 'media-types': [ {'base': 'application/json', 'type': 'application/vnd.openstack.identity-v2.0+json'}, {'base': 'application/xml', 'type': 'application/vnd.openstack.identity-v2.0+xml'}], 'status': 'stable', 'updated': '2013-03-06T00:00:00Z'}] """ if unstable: kwargs.setdefault('allow_experimental', True) kwargs.setdefault('allow_unknown', True) return super(Discover, self).raw_version_data(**kwargs) def _calculate_version(self, version, unstable): version_data = None if version: version_data = self.data_for(version) else: # if no version specified pick the latest one all_versions = self.version_data(unstable=unstable) if all_versions: version_data = all_versions[-1] if not version_data: msg = _('Could not find a suitable endpoint') if version: msg = _('Could not find a suitable endpoint for client ' 'version: %s') % str(version) raise exceptions.VersionNotAvailable(msg) return version_data def _create_client(self, version_data, **kwargs): # Get the client for the version requested that was returned try: client_class = _CLIENT_VERSIONS[version_data['version'][0]] except KeyError: version = '.'.join(str(v) for v in version_data['version']) msg = _('No client available for version: %s') % version raise exceptions.DiscoveryFailure(msg) # kwargs should take priority over stored kwargs. for k, v in self._client_kwargs.items(): kwargs.setdefault(k, v) # restore the url to either auth_url or endpoint depending on what # was initially given if self._use_endpoint: kwargs['auth_url'] = None kwargs['endpoint'] = version_data['url'] else: kwargs['auth_url'] = version_data['url'] kwargs['endpoint'] = None return client_class(**kwargs) def create_client(self, version=None, unstable=False, **kwargs): """Factory function to create a new identity service client. :param tuple version: The required version of the identity API. If specified the client will be selected such that the major version is equivalent and an endpoint provides at least the specified minor version. For example to specify the 3.1 API use (3, 1). (optional) :param bool unstable: Accept endpoints not marked 'stable'. (optional) :param kwargs: Additional arguments will override those provided to this object's constructor. :returns: An instantiated identity client object. :raises keystoneclient.exceptions.DiscoveryFailure: if the server response is invalid :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client cannot be found. """ version_data = self._calculate_version(version, unstable) return self._create_client(version_data, **kwargs) def add_catalog_discover_hack(service_type, old, new): """Add a version removal rule for a particular service. Originally deployments of OpenStack would contain a versioned endpoint in the catalog for different services. E.g. an identity service might look like ``http://localhost:5000/v2.0``. This is a problem when we want to use a different version like v3.0 as there is no way to tell where it is located. We cannot simply change all service catalogs either so there must be a way to handle the older style of catalog. This function adds a rule for a given service type that if part of the URL matches a given regular expression in *old* then it will be replaced with the *new* value. This will replace all instances of old with new. It should therefore contain a regex anchor. For example the included rule states:: add_catalog_version_hack('identity', re.compile('/v2.0/?$'), '/') so if the catalog retrieves an *identity* URL that ends with /v2.0 or /v2.0/ then it should replace it simply with / to fix the user's catalog. :param str service_type: The service type as defined in the catalog that the rule will apply to. :param re.RegexObject old: The regular expression to search for and replace if found. :param str new: The new string to replace the pattern with. """ _discover._VERSION_HACKS.add_discover_hack(service_type, old, new) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/exceptions.py0000664000175000017500000003031300000000000024001 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Exception definitions.""" from keystoneauth1 import exceptions as _exc from keystoneclient.i18n import _ ClientException = _exc.ClientException """The base exception class for all exceptions this library raises. An alias of :py:exc:`keystoneauth1.exceptions.base.ClientException` """ ConnectionError = _exc.ConnectionError """Cannot connect to API service. An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectionError` """ ConnectionRefused = _exc.ConnectFailure """Connection refused while trying to connect to API service. An alias of :py:exc:`keystoneauth1.exceptions.connection.ConnectFailure` """ SSLError = _exc.SSLError """An SSL error occurred. An alias of :py:exc:`keystoneauth1.exceptions.connection.SSLError` """ AuthorizationFailure = _exc.AuthorizationFailure """Cannot authorize API client. An alias of :py:exc:`keystoneauth1.exceptions.auth.AuthorizationFailure` """ class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass EndpointException = _exc.CatalogException """Something is rotten in Service Catalog. An alias of :py:exc:`keystoneauth1.exceptions.catalog.CatalogException` """ EndpointNotFound = _exc.EndpointNotFound """Could not find requested endpoint in Service Catalog. An alias of :py:exc:`keystoneauth1.exceptions.catalog.EndpointNotFound` """ class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints HttpError = _exc.HttpError """The base exception class for all HTTP exceptions. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpError` """ HTTPClientError = _exc.HTTPClientError """Client-side HTTP error. Exception for cases in which the client seems to have erred. An alias of :py:exc:`keystoneauth1.exceptions.http.HTTPClientError` """ HttpServerError = _exc.HttpServerError """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpServerError` """ class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = 300 message = _("Multiple Choices") BadRequest = _exc.BadRequest """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. An alias of :py:exc:`keystoneauth1.exceptions.http.BadRequest` """ Unauthorized = _exc.Unauthorized """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. An alias of :py:exc:`keystoneauth1.exceptions.http.Unauthorized` """ PaymentRequired = _exc.PaymentRequired """HTTP 402 - Payment Required. Reserved for future use. An alias of :py:exc:`keystoneauth1.exceptions.http.PaymentRequired` """ Forbidden = _exc.Forbidden """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. An alias of :py:exc:`keystoneauth1.exceptions.http.Forbidden` """ NotFound = _exc.NotFound """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. An alias of :py:exc:`keystoneauth1.exceptions.http.NotFound` """ MethodNotAllowed = _exc.MethodNotAllowed """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. An alias of :py:exc:`keystoneauth1.exceptions.http.MethodNotAllowed` """ NotAcceptable = _exc.NotAcceptable """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. An alias of :py:exc:`keystoneauth1.exceptions.http.NotAcceptable` """ ProxyAuthenticationRequired = _exc.ProxyAuthenticationRequired """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. An alias of :py:exc:`keystoneauth1.exceptions.http.ProxyAuthenticationRequired` """ RequestTimeout = _exc.RequestTimeout """HTTP 408 - Request Timeout. The server timed out waiting for the request. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestTimeout` """ Conflict = _exc.Conflict """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. An alias of :py:exc:`keystoneauth1.exceptions.http.Conflict` """ Gone = _exc.Gone """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. An alias of :py:exc:`keystoneauth1.exceptions.http.Gone` """ LengthRequired = _exc.LengthRequired """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. An alias of :py:exc:`keystoneauth1.exceptions.http.LengthRequired` """ PreconditionFailed = _exc.PreconditionFailed """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. An alias of :py:exc:`keystoneauth1.exceptions.http.PreconditionFailed` """ RequestEntityTooLarge = _exc.RequestEntityTooLarge """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestEntityTooLarge` """ RequestUriTooLong = _exc.RequestUriTooLong """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestUriTooLong` """ UnsupportedMediaType = _exc.UnsupportedMediaType """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. An alias of :py:exc:`keystoneauth1.exceptions.http.UnsupportedMediaType` """ RequestedRangeNotSatisfiable = _exc.RequestedRangeNotSatisfiable """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. An alias of :py:exc:`keystoneauth1.exceptions.http.RequestedRangeNotSatisfiable` """ ExpectationFailed = _exc.ExpectationFailed """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. An alias of :py:exc:`keystoneauth1.exceptions.http.ExpectationFailed` """ UnprocessableEntity = _exc.UnprocessableEntity """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. An alias of :py:exc:`keystoneauth1.exceptions.http.UnprocessableEntity` """ InternalServerError = _exc.InternalServerError """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. An alias of :py:exc:`keystoneauth1.exceptions.http.InternalServerError` """ HttpNotImplemented = _exc.HttpNotImplemented """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpNotImplemented` """ BadGateway = _exc.BadGateway """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. An alias of :py:exc:`keystoneauth1.exceptions.http.BadGateway` """ ServiceUnavailable = _exc.ServiceUnavailable """HTTP 503 - Service Unavailable. The server is currently unavailable. An alias of :py:exc:`keystoneauth1.exceptions.http.ServiceUnavailable` """ GatewayTimeout = _exc.GatewayTimeout """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. An alias of :py:exc:`keystoneauth1.exceptions.http.GatewayTimeout` """ HttpVersionNotSupported = _exc.HttpVersionNotSupported """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. An alias of :py:exc:`keystoneauth1.exceptions.http.HttpVersionNotSupported` """ from_response = _exc.from_response """Return an instance of :class:`HttpError` or subclass based on response. An alias of :py:func:`keystoneauth1.exceptions.http.from_response` """ # NOTE(akurilin): This alias should be left here to support backwards # compatibility until we are sure that usage of these exceptions in # projects is correct. HTTPNotImplemented = HttpNotImplemented Timeout = RequestTimeout HTTPError = HttpError class CertificateConfigError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output msg = _('Unable to load certificate.') super(CertificateConfigError, self).__init__(msg) class CMSError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output msg = _('Unable to sign or verify data.') super(CMSError, self).__init__(msg) EmptyCatalog = _exc.EmptyCatalog """The service catalog is empty. An alias of :py:exc:`keystoneauth1.exceptions.catalog.EmptyCatalog` """ DiscoveryFailure = _exc.DiscoveryFailure """Discovery of client versions failed. An alias of :py:exc:`keystoneauth1.exceptions.discovery.DiscoveryFailure` """ VersionNotAvailable = _exc.VersionNotAvailable """Discovery failed as the version you requested is not available. An alias of :py:exc:`keystoneauth1.exceptions.discovery.VersionNotAvailable` """ class MethodNotImplemented(ClientException): """Method not implemented by the keystoneclient API.""" MissingAuthPlugin = _exc.MissingAuthPlugin """An authenticated request is required but no plugin available. An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin` """ NoMatchingPlugin = _exc.NoMatchingPlugin """There were no auth plugins that could be created from the parameters provided. An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin` """ class UnsupportedParameters(ClientException): """A parameter that was provided or returned is not supported. :param List(str) names: Names of the unsupported parameters. .. py:attribute:: names Names of the unsupported parameters. """ def __init__(self, names): self.names = names m = _('The following parameters were given that are unsupported: %s') super(UnsupportedParameters, self).__init__(m % ', '.join(self.names)) class InvalidResponse(ClientException): """The response from the server is not valid for this request.""" def __init__(self, response): super(InvalidResponse, self).__init__() self.response = response ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/fixture/0000775000175000017500000000000000000000000022734 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/fixture/__init__.py0000664000175000017500000000351600000000000025052 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. """ Produce keystone compliant structures for testing. The generators in this directory produce keystone compliant structures for use in testing. They should be considered part of the public API because they may be relied upon to generate test tokens for other clients. However they should never be imported into the main client (keystoneclient or other). Because of this there may be dependencies from this module on libraries that are only available in testing. .. warning:: The keystoneclient.fixture package is deprecated in favor of keystoneauth1.fixture and will not be supported. """ # flake8: noqa: F405 import warnings from keystoneclient.fixture.discovery import * # noqa from keystoneclient.fixture import exception from keystoneclient.fixture import v2 from keystoneclient.fixture import v3 warnings.warn( "The keystoneclient.fixture package is deprecated in favor of " "keystoneauth1.fixture and will not be supported.", DeprecationWarning) FixtureValidationError = exception.FixtureValidationError V2Token = v2.Token V3Token = v3.Token V3FederationToken = v3.V3FederationToken __all__ = ('DiscoveryList', 'FixtureValidationError', 'V2Discovery', 'V3Discovery', 'V2Token', 'V3Token', 'V3FederationToken', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/fixture/discovery.py0000664000175000017500000000221100000000000025311 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.fixture import discovery __all__ = ('DiscoveryList', 'V2Discovery', 'V3Discovery', ) V2Discovery = discovery.V2Discovery """A Version element for a V2 identity service endpoint. An alias of :py:exc:`keystoneauth1.fixture.discovery.V2Discovery` """ V3Discovery = discovery.V3Discovery """A Version element for a V3 identity service endpoint. An alias of :py:exc:`keystoneauth1.fixture.discovery.V3Discovery` """ DiscoveryList = discovery.DiscoveryList """A List of version elements. An alias of :py:exc:`keystoneauth1.fixture.discovery.DiscoveryList` """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/fixture/exception.py0000664000175000017500000000141100000000000025301 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.fixture import exception FixtureValidationError = exception.FixtureValidationError """The token you created is not legitimate. An alias of :py:exc:`keystoneauth1.fixture.exception.FixtureValidationError`` """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/fixture/v2.py0000664000175000017500000000131100000000000023631 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.fixture import v2 Token = v2.Token """A V2 Keystone token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v2.Token` """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/fixture/v3.py0000664000175000017500000000157100000000000023642 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.fixture import v3 Token = v3.Token """A V3 Keystone token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v3.Token` """ V3FederationToken = v3.V3FederationToken """A V3 Keystone Federation token that can be used for testing. An alias of :py:exc:`keystoneauth1.fixture.v3.V3FederationToken` """ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2150037 python_keystoneclient-5.6.0/keystoneclient/generic/0000775000175000017500000000000000000000000022662 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/generic/__init__.py0000664000175000017500000000003500000000000024771 0ustar00zuulzuul00000000000000 __all__ = ( 'client', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/generic/client.py0000664000175000017500000001775300000000000024527 0ustar00zuulzuul00000000000000# Copyright 2010 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import urllib.parse as urlparse from debtcollector import removals from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ _logger = logging.getLogger(__name__) # NOTE(jamielennox): To be removed after Pike. @removals.removed_class('keystoneclient.generic.client.Client', message='Use keystoneauth discovery', version='3.9.0', removal_version='4.0.0') class Client(httpclient.HTTPClient): """Client for the OpenStack Keystone pre-version calls API. :param string endpoint: A user-supplied endpoint URL for the keystone service. :param integer timeout: Allows customization of the timeout for client http requests. (optional) Example:: >>> from keystoneclient.generic import client >>> root = client.Client(auth_url=KEYSTONE_URL) >>> versions = root.discover() ... >>> from keystoneclient.v2_0 import client as v2client >>> keystone = v2client.Client(auth_url=versions['v2.0']['url']) ... >>> user = keystone.users.get(USER_ID) >>> user.delete() """ def __init__(self, endpoint=None, **kwargs): """Initialize a new client for the Keystone v2.0 API.""" super(Client, self).__init__(endpoint=endpoint, **kwargs) self.endpoint = endpoint def discover(self, url=None): """Discover Keystone servers and return API versions supported. :param url: optional url to test (without version) Returns:: { 'message': 'Keystone found at http://127.0.0.1:5000/', 'v2.0': { 'status': 'beta', 'url': 'http://127.0.0.1:5000/v2.0/', 'id': 'v2.0' }, } """ if url: return self._check_keystone_versions(url) else: return self._local_keystone_exists() def _local_keystone_exists(self): """Check if Keystone is available on default local port 35357.""" results = self._check_keystone_versions("http://localhost:35357") if results is None: results = self._check_keystone_versions("https://localhost:35357") return results def _check_keystone_versions(self, url): """Call Keystone URL and detects the available API versions.""" try: resp, body = self._request(url, "GET", headers={'Accept': 'application/json'}) # Multiple Choices status code is returned by the root # identity endpoint, with references to one or more # Identity API versions -- v3 spec # some cases we get No Content if resp.status_code in (200, 204, 300): try: results = {} if 'version' in body: results['message'] = _("Keystone found at %s") % url version = body['version'] # Stable/diablo incorrect format id, status, version_url = ( self._get_version_info(version, url)) results[str(id)] = {"id": id, "status": status, "url": version_url} return results elif 'versions' in body: # Correct format results['message'] = _("Keystone found at %s") % url for version in body['versions']['values']: id, status, version_url = ( self._get_version_info(version, url)) results[str(id)] = {"id": id, "status": status, "url": version_url} return results else: results['message'] = ( _("Unrecognized response from %s") % url) return results except KeyError: raise exceptions.AuthorizationFailure() elif resp.status_code == 305: return self._check_keystone_versions(resp['location']) else: raise exceptions.from_response(resp, "GET", url) except Exception: _logger.exception('Failed to detect available versions.') def discover_extensions(self, url=None): """Discover Keystone extensions supported. :param url: optional url to test (should have a version in it) Returns:: { 'message': 'Keystone extensions at http://127.0.0.1:35357/v2', 'OS-KSEC2': 'OpenStack EC2 Credentials Extension', } """ if url: return self._check_keystone_extensions(url) def _check_keystone_extensions(self, url): """Call Keystone URL and detects the available extensions.""" try: if not url.endswith("/"): url += '/' resp, body = self._request("%sextensions" % url, "GET", headers={'Accept': 'application/json'}) if resp.status_code in (200, 204): # some cases we get No Content if 'extensions' in body and 'values' in body['extensions']: # Parse correct format (per contract) extensions = body['extensions']['values'] elif 'extensions' in body: # Support incorrect, but prevalent format extensions = body['extensions'] else: return dict(message=( _('Unrecognized extensions response from %s') % url)) return dict(self._get_extension_info(e) for e in extensions) elif resp.status_code == 305: return self._check_keystone_extensions(resp['location']) else: raise exceptions.from_response( resp, "GET", "%sextensions" % url) except Exception: _logger.exception('Failed to check keystone extensions.') @staticmethod def _get_version_info(version, root_url): """Parse version information. :param version: a dict of a Keystone version response :param root_url: string url used to construct the version if no URL is provided. :returns: tuple - (verionId, versionStatus, versionUrl) """ id = version['id'] status = version['status'] ref = urlparse.urljoin(root_url, id) if 'links' in version: for link in version['links']: if link['rel'] == 'self': ref = link['href'] break return (id, status, ref) @staticmethod def _get_extension_info(extension): """Parse extension information. :param extension: a dict of a Keystone extension response :returns: tuple - (alias, name) """ alias = extension['alias'] name = extension['name'] return (alias, name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/httpclient.py0000664000175000017500000011466600000000000024014 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2011 Piston Cloud Computing, Inc. # Copyright 2011 Nebula, Inc. # 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. """OpenStack Client interface. Handles the REST calls and responses.""" import importlib.metadata import logging import warnings from debtcollector import removals from debtcollector import renames from keystoneauth1 import adapter from oslo_serialization import jsonutils import packaging.version import requests try: import pickle # nosec(cjschaef): see bug 1534288 for details # NOTE(sdague): The conditional keyring import needs to only # trigger if it's a version of keyring that's supported in global # requirements. Update _min and _bad when that changes. keyring_v = packaging.version.Version( importlib.metadata.version('keyring') ) keyring_min = packaging.version.Version('5.5.1') # This is a list of versions, e.g., pkg_resources.parse_version('3.3') keyring_bad = [] if keyring_v >= keyring_min and keyring_v not in keyring_bad: import keyring else: keyring = None except (ImportError, importlib.metadata.PackageNotFoundError): keyring = None pickle = None from keystoneclient import _discover from keystoneclient import access from keystoneclient.auth import base from keystoneclient import baseclient from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session _logger = logging.getLogger(__name__) USER_AGENT = client_session.USER_AGENT """Default user agent string. This property is deprecated as of the 1.7.0 release in favor of :data:`keystoneclient.session.USER_AGENT` and may be removed in the 2.0.0 release. """ @removals.remove(message='Use keystoneclient.session.request instead.', version='1.7.0', removal_version='2.0.0') def request(*args, **kwargs): """Make a request. This function is deprecated as of the 1.7.0 release in favor of :func:`keystoneclient.session.request` and may be removed in the 2.0.0 release. """ return client_session.request(*args, **kwargs) class _FakeRequestSession(object): """This object is a temporary hack that should be removed later. Keystoneclient has a cyclical dependency with its managers which is preventing it from being cleaned up correctly. This is always bad but when we switched to doing connection pooling this object wasn't getting cleaned either and so we had left over TCP connections hanging around. Until we can fix the client cleanup we rollback the use of a requests session and do individual connections like we used to. """ def request(self, *args, **kwargs): return requests.request(*args, **kwargs) class _KeystoneAdapter(adapter.LegacyJsonAdapter): """A wrapper layer to interface keystoneclient with a session. An adapter provides a generic interface between a client and the session to provide client specific defaults. This object is passed to the managers. Keystoneclient managers have some additional requirements of variables that they expect to be present on the passed object. Subclass the existing adapter to provide those values that keystoneclient managers expect. """ @property def user_id(self): """Best effort to retrieve the user_id from the plugin. Some managers rely on being able to get the currently authenticated user id. This is a problem when we are trying to abstract away the details of an auth plugin. For example changing a user's password can require access to the currently authenticated user_id. Perform a best attempt to fetch this data. It will work in the legacy case and with identity plugins and be None otherwise which is the same as the historical behavior. """ # the identity plugin case try: return self.session.auth.get_access(self.session).user_id except AttributeError: # nosec(cjschaef): attempt legacy retrival, or # return None pass # there is a case that we explicitly allow (tested by our unit tests) # that says you should be able to set the user_id on a legacy client # and it should overwrite the one retrieved via authentication. If it's # a legacy then self.session.auth is a client and we retrieve user_id. try: return self.session.auth.user_id except AttributeError: # nosec(cjschaef): retrivals failed, return # None pass return None class HTTPClient(baseclient.Client, base.BaseAuthPlugin): """HTTP client. .. warning:: Creating an instance of this class without using the session argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. :param string user_id: User ID for authentication. (optional) :param string username: Username for authentication. (optional) :param string user_domain_id: User's domain ID for authentication. (optional) :param string user_domain_name: User's domain name for authentication. (optional) :param string password: Password for authentication. (optional) :param string domain_id: Domain ID for domain scoping. (optional) :param string domain_name: Domain name for domain scoping. (optional) :param string project_id: Project ID for project scoping. (optional) :param string project_name: Project name for project scoping. (optional) :param string project_domain_id: Project's domain ID for project scoping. (optional) :param string project_domain_name: Project's domain name for project scoping. (optional) :param string auth_url: Identity service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param integer timeout: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string endpoint: A user-supplied endpoint URL for the identity service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation. (optional) :param string token: Token for authentication. (optional) :param string cacert: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string key: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string cert: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param boolean insecure: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param string original_ip: This argument is deprecated as of the 1.7.0 release in favor of session and may be removed in the 2.0.0 release. (optional) :param dict auth_ref: To allow for consumers of the client to manage their own caching strategy, you may initialize a client with a previously captured auth_reference (token). If there are keyword arguments passed that also exist in auth_ref, the value from the argument will take precedence. :param boolean use_keyring: Enables caching auth_ref into keyring. default: False (optional) :param boolean force_new_token: Keyring related parameter, forces request for new token. default: False (optional) :param integer stale_duration: Gap in seconds to determine if token from keyring is about to expire. default: 30 (optional) :param string tenant_name: Tenant name. (optional) The tenant_name keyword argument is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. :param string tenant_id: Tenant id. (optional) The tenant_id keyword argument is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. :param string trust_id: Trust ID for trust scoping. (optional) :param object session: A Session object to be used for communicating with the identity service. :type session: keystoneclient.session.Session :param string service_name: The default service_name for URL discovery. default: None (optional) :param string interface: The default interface for URL discovery. default: admin (v2), public (v3). (optional) :param string endpoint_override: Always use this endpoint URL for requests for this client. (optional) :param auth: An auth plugin to use instead of the session one. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :param string user_agent: The User-Agent string to set. default: python-keystoneclient (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. Default None - use session default which is don't retry. (optional) """ version = None @renames.renamed_kwarg('tenant_name', 'project_name', version='1.7.0', removal_version='2.0.0') @renames.renamed_kwarg('tenant_id', 'project_id', version='1.7.0', removal_version='2.0.0') def __init__(self, username=None, tenant_id=None, tenant_name=None, password=None, auth_url=None, region_name=None, endpoint=None, token=None, auth_ref=None, use_keyring=False, force_new_token=False, stale_duration=None, user_id=None, user_domain_id=None, user_domain_name=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, trust_id=None, session=None, service_name=None, interface='default', endpoint_override=None, auth=None, user_agent=USER_AGENT, connect_retries=None, **kwargs): # set baseline defaults self.user_id = None self.username = None self.user_domain_id = None self.user_domain_name = None self.domain_id = None self.domain_name = None self.project_id = None self.project_name = None self.project_domain_id = None self.project_domain_name = None self.auth_url = None self._endpoint = None self._management_url = None self.trust_id = None # if loading from a dictionary passed in via auth_ref, # load values from AccessInfo parsing that dictionary if auth_ref: self.auth_ref = access.AccessInfo.factory(**auth_ref) self.version = self.auth_ref.version self.user_id = self.auth_ref.user_id self.username = self.auth_ref.username self.user_domain_id = self.auth_ref.user_domain_id self.domain_id = self.auth_ref.domain_id self.domain_name = self.auth_ref.domain_name self.project_id = self.auth_ref.project_id self.project_name = self.auth_ref.project_name self.project_domain_id = self.auth_ref.project_domain_id auth_urls = self.auth_ref.service_catalog.get_urls( service_type='identity', endpoint_type='public', region_name=region_name) self.auth_url = auth_urls[0] management_urls = self.auth_ref.service_catalog.get_urls( service_type='identity', endpoint_type='admin', region_name=region_name) self._management_url = management_urls[0] self.auth_token_from_user = self.auth_ref.auth_token self.trust_id = self.auth_ref.trust_id # TODO(blk-u): Using self.auth_ref.service_catalog._region_name is # deprecated and this code must be removed when the property is # actually removed. if self.auth_ref.has_service_catalog() and not region_name: region_name = self.auth_ref.service_catalog._region_name else: self.auth_ref = None # allow override of the auth_ref defaults from explicit # values provided to the client # apply deprecated variables first, so modern variables override them if tenant_id: self.project_id = tenant_id if tenant_name: self.project_name = tenant_name # user-related attributes self.password = password if user_id: self.user_id = user_id if username: self.username = username if user_domain_id: self.user_domain_id = user_domain_id elif not (user_id or user_domain_name): self.user_domain_id = 'default' if user_domain_name: self.user_domain_name = user_domain_name # domain-related attributes if domain_id: self.domain_id = domain_id if domain_name: self.domain_name = domain_name # project-related attributes if project_id: self.project_id = project_id if project_name: self.project_name = project_name if project_domain_id: self.project_domain_id = project_domain_id elif not (project_id or project_domain_name): self.project_domain_id = 'default' if project_domain_name: self.project_domain_name = project_domain_name # trust-related attributes if trust_id: self.trust_id = trust_id # endpoint selection if auth_url: self.auth_url = auth_url.rstrip('/') if token: self.auth_token_from_user = token else: self.auth_token_from_user = None if endpoint: self._endpoint = endpoint.rstrip('/') self._auth_token = None if not session: warnings.warn( 'Constructing an HTTPClient instance without using a session ' 'is deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) kwargs['session'] = _FakeRequestSession() session = client_session.Session._construct(kwargs) session.auth = self self.session = session self.domain = '' version = ( _discover.normalize_version_number(self.version) if self.version else None) # NOTE(frickler): If we know we have v3, use the public interface as # default, otherwise keep the historic default of admin if interface == 'default': if version == (3, 0): interface = 'public' else: interface = 'admin' # NOTE(jamielennox): unfortunately we can't just use **kwargs here as # it would incompatibly limit the kwargs that can be passed to __init__ # try and keep this list in sync with adapter.Adapter.__init__ self._adapter = _KeystoneAdapter(session, service_type='identity', service_name=service_name, interface=interface, region_name=region_name, endpoint_override=endpoint_override, version=version, auth=auth, user_agent=user_agent, connect_retries=connect_retries) # NOTE(dstanek): This allows me to not have to change keystoneauth or # to write an adapter to the adapter here. Splitting thing into # multiple project isn't always all sunshine and roses. self._adapter.include_metadata = kwargs.pop('include_metadata', False) # keyring setup if use_keyring and keyring is None: _logger.warning('Failed to load keyring modules.') self.use_keyring = use_keyring and keyring is not None self.force_new_token = force_new_token self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION self.stale_duration = int(self.stale_duration) def get_token(self, session, **kwargs): return self.auth_token @property def auth_token(self): if self._auth_token: return self._auth_token if self.auth_ref: if self.auth_ref.will_expire_soon(self.stale_duration): self.authenticate() return self.auth_ref.auth_token if self.auth_token_from_user: return self.auth_token_from_user def get_endpoint(self, session, interface=None, **kwargs): if interface == 'public' or interface is base.AUTH_INTERFACE: return self.auth_url else: return self.management_url def get_user_id(self, session, **kwargs): return self.auth_ref.user_id def get_project_id(self, session, **kwargs): return self.auth_ref.project_id @auth_token.setter def auth_token(self, value): """Override the auth_token. If an application sets auth_token explicitly then it will always be used and override any past or future retrieved token. """ self._auth_token = value @auth_token.deleter def auth_token(self): self._auth_token = None self.auth_token_from_user = None @property def service_catalog(self): """Return this client's service catalog.""" try: return self.auth_ref.service_catalog except AttributeError: return None def has_service_catalog(self): """Return True if this client provides a service catalog.""" return self.auth_ref and self.auth_ref.has_service_catalog() @property def tenant_id(self): """Provide read-only backwards compatibility for tenant_id. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. """ warnings.warn( 'tenant_id is deprecated as of the 1.7.0 release in favor of ' 'project_id and may be removed in the 2.0.0 release.', DeprecationWarning) return self.project_id @property def tenant_name(self): """Provide read-only backwards compatibility for tenant_name. .. warning:: This is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. """ warnings.warn( 'tenant_name is deprecated as of the 1.7.0 release in favor of ' 'project_name and may be removed in the 2.0.0 release.', DeprecationWarning) return self.project_name def authenticate(self, username=None, password=None, tenant_name=None, tenant_id=None, auth_url=None, token=None, user_id=None, domain_name=None, domain_id=None, project_name=None, project_id=None, user_domain_id=None, user_domain_name=None, project_domain_id=None, project_domain_name=None, trust_id=None, region_name=None): """Authenticate user. Uses the data provided at instantiation to authenticate against the Identity server. This may use either a username and password or token for authentication. If a tenant name or id was provided then the resulting authenticated client will be scoped to that tenant and contain a service catalog of available endpoints. With the v2.0 API, if a tenant name or ID is not provided, the authentication token returned will be 'unscoped' and limited in capabilities until a fully-scoped token is acquired. With the v3 API, if a domain name or id was provided then the resulting authenticated client will be scoped to that domain. If a project name or ID is not provided, and the authenticating user has a default project configured, the authentication token returned will be 'scoped' to the default project. Otherwise, the authentication token returned will be 'unscoped' and limited in capabilities until a fully-scoped token is acquired. With the v3 API, with the OS-TRUST extension enabled, the trust_id can be provided to allow project-specific role delegation between users If successful, sets the self.auth_ref and self.auth_token with the returned token. If not already set, will also set self.management_url from the details provided in the token. :returns: ``True`` if authentication was successful. :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token :raises keystoneclient.exceptions.ValueError: if insufficient parameters are used. If keyring is used, token is retrieved from keyring instead. Authentication will only be necessary if any of the following conditions are met: * keyring is not used * if token is not found in keyring * if token retrieved from keyring is expired or about to expired (as determined by stale_duration) * if force_new_token is true """ auth_url = auth_url or self.auth_url user_id = user_id or self.user_id username = username or self.username password = password or self.password user_domain_id = user_domain_id or self.user_domain_id user_domain_name = user_domain_name or self.user_domain_name domain_id = domain_id or self.domain_id domain_name = domain_name or self.domain_name project_id = project_id or tenant_id or self.project_id project_name = project_name or tenant_name or self.project_name project_domain_id = project_domain_id or self.project_domain_id project_domain_name = project_domain_name or self.project_domain_name trust_id = trust_id or self.trust_id region_name = region_name or self._adapter.region_name if not token: token = self.auth_token_from_user if (not token and self.auth_ref and not self.auth_ref.will_expire_soon(self.stale_duration)): token = self.auth_ref.auth_token kwargs = { 'auth_url': auth_url, 'user_id': user_id, 'username': username, 'user_domain_id': user_domain_id, 'user_domain_name': user_domain_name, 'domain_id': domain_id, 'domain_name': domain_name, 'project_id': project_id, 'project_name': project_name, 'project_domain_id': project_domain_id, 'project_domain_name': project_domain_name, 'token': token, 'trust_id': trust_id, } (keyring_key, auth_ref) = self.get_auth_ref_from_keyring(**kwargs) new_token_needed = False if auth_ref is None or self.force_new_token: new_token_needed = True kwargs['password'] = password resp = self.get_raw_token_from_identity_service(**kwargs) if isinstance(resp, access.AccessInfo): self.auth_ref = resp else: self.auth_ref = access.AccessInfo.factory(*resp) # NOTE(jamielennox): The original client relies on being able to # push the region name into the service catalog but new auth # it in. if region_name: self.auth_ref.service_catalog._region_name = region_name else: self.auth_ref = auth_ref self.process_token(region_name=region_name) if new_token_needed: self.store_auth_ref_into_keyring(keyring_key) return True def _build_keyring_key(self, **kwargs): """Create a unique key for keyring. Used to store and retrieve auth_ref from keyring. Return a slash-separated string of values ordered by key name. """ return '/'.join([kwargs[k] or '?' for k in sorted(kwargs)]) def get_auth_ref_from_keyring(self, **kwargs): """Retrieve auth_ref from keyring. If auth_ref is found in keyring, (keyring_key, auth_ref) is returned. Otherwise, (keyring_key, None) is returned. :returns: (keyring_key, auth_ref) or (keyring_key, None) :returns: or (None, None) if use_keyring is not set in the object """ keyring_key = None auth_ref = None if self.use_keyring: keyring_key = self._build_keyring_key(**kwargs) try: auth_ref = keyring.get_password("keystoneclient_auth", keyring_key) if auth_ref: auth_ref = pickle.loads(auth_ref) # nosec(cjschaef): see # bug 1534288 if auth_ref.will_expire_soon(self.stale_duration): # token has expired, don't use it auth_ref = None except Exception as e: auth_ref = None _logger.warning('Unable to retrieve token from keyring %s', e) return (keyring_key, auth_ref) def store_auth_ref_into_keyring(self, keyring_key): """Store auth_ref into keyring.""" if self.use_keyring: try: keyring.set_password("keystoneclient_auth", keyring_key, pickle.dumps(self.auth_ref)) # nosec # (cjschaef): see bug 1534288 except Exception as e: _logger.warning("Failed to store token into keyring %s", e) def _process_management_url(self, region_name): try: self._management_url = self.auth_ref.service_catalog.url_for( service_type='identity', endpoint_type='admin', region_name=region_name) except exceptions.EndpointNotFound as e: _logger.debug("Failed to find endpoint for management url %s", e) def process_token(self, region_name=None): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ # if we got a response without a service catalog, set the local # list of tenants for introspection, and leave to client user # to determine what to do. Otherwise, load up the service catalog if self.auth_ref.project_scoped: if not self.auth_ref.tenant_id: raise exceptions.AuthorizationFailure( _("Token didn't provide tenant_id")) self._process_management_url(region_name) self.project_name = self.auth_ref.tenant_name self.project_id = self.auth_ref.tenant_id if not self.auth_ref.user_id: raise exceptions.AuthorizationFailure( _("Token didn't provide user_id")) self.user_id = self.auth_ref.user_id self.auth_domain_id = self.auth_ref.domain_id self.auth_tenant_id = self.auth_ref.tenant_id self.auth_user_id = self.auth_ref.user_id @property def management_url(self): return self._endpoint or self._management_url @management_url.setter def management_url(self, value): # NOTE(jamielennox): it's debatable here whether we should set # _endpoint or _management_url. As historically management_url was set # permanently setting _endpoint would better match that behaviour. self._endpoint = value def get_raw_token_from_identity_service(self, auth_url, username=None, password=None, tenant_name=None, tenant_id=None, token=None, user_id=None, user_domain_id=None, user_domain_name=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, trust_id=None): """Authenticate against the Identity API and get a token. Not implemented here because auth protocols should be API version-specific. Expected to authenticate or validate an existing authentication reference already associated with the client. Invoking this call *always* makes a call to the Identity service. :returns: (``resp``, ``body``) """ raise NotImplementedError def serialize(self, entity): return jsonutils.dumps(entity) @removals.remove(version='1.7.0', removal_version='2.0.0') def request(self, *args, **kwargs): """Send an http request with the specified characteristics. Wrapper around requests.request to handle tasks such as setting headers, JSON encoding/decoding, and error handling. .. warning:: *DEPRECATED*: This function is no longer used. It was designed to be used only by the managers and the managers now receive an adapter so this function is no longer on the standard request path. This may be removed in the 2.0.0 release. """ return self._request(*args, **kwargs) def _request(self, *args, **kwargs): kwargs.setdefault('authenticated', False) return self._adapter.request(*args, **kwargs) def _cs_request(self, url, method, management=True, **kwargs): """Make an authenticated request to keystone endpoint. Request are made to keystone endpoint by concatenating self.management_url and url and passing in method and any associated kwargs. """ if not management: endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', 'public') kwargs.setdefault('authenticated', None) return self._request(url, method, **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def get(self, url, **kwargs): """Perform an authenticated GET request. This calls :py:meth:`.request()` with ``method`` set to ``GET`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'GET', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def head(self, url, **kwargs): """Perform an authenticated HEAD request. This calls :py:meth:`.request()` with ``method`` set to ``HEAD`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'HEAD', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def post(self, url, **kwargs): """Perform an authenticate POST request. This calls :py:meth:`.request()` with ``method`` set to ``POST`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'POST', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def put(self, url, **kwargs): """Perform an authenticate PUT request. This calls :py:meth:`.request()` with ``method`` set to ``PUT`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'PUT', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def patch(self, url, **kwargs): """Perform an authenticate PATCH request. This calls :py:meth:`.request()` with ``method`` set to ``PATCH`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'PATCH', **kwargs) @removals.remove(version='1.7.0', removal_version='2.0.0') def delete(self, url, **kwargs): """Perform an authenticate DELETE request. This calls :py:meth:`.request()` with ``method`` set to ``DELETE`` and an authentication token if one is available. .. warning:: *DEPRECATED*: This function is no longer used and is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. It was designed to be used by the managers and the managers now receive an adapter so this function is no longer on the standard request path. """ return self._cs_request(url, 'DELETE', **kwargs) deprecated_session_variables = {'original_ip': None, 'cert': None, 'timeout': None, 'verify_cert': 'verify'} deprecated_adapter_variables = {'region_name': None} def __getattr__(self, name): """Fetch deprecated session variables.""" try: var_name = self.deprecated_session_variables[name] except KeyError: # nosec(cjschaef): try adapter variable or raise # an AttributeError pass else: warnings.warn( 'The %s session variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return getattr(self.session, var_name or name) try: var_name = self.deprecated_adapter_variables[name] except KeyError: # nosec(cjschaef): raise an AttributeError pass else: warnings.warn( 'The %s adapter variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return getattr(self._adapter, var_name or name) raise AttributeError(_("Unknown Attribute: %s") % name) def __setattr__(self, name, val): """Assign value to deprecated seesion variables.""" try: var_name = self.deprecated_session_variables[name] except KeyError: # nosec(cjschaef): try adapter variable or call # parent class's __setattr__ pass else: warnings.warn( 'The %s session variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return setattr(self.session, var_name or name) try: var_name = self.deprecated_adapter_variables[name] except KeyError: # nosec(cjschaef): call parent class's __setattr__ pass else: warnings.warn( 'The %s adapter variable is deprecated as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release' % name, DeprecationWarning) return setattr(self._adapter, var_name or name) super(HTTPClient, self).__setattr__(name, val) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/i18n.py0000664000175000017500000000153300000000000022401 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/index.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='keystoneclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/service_catalog.py0000664000175000017500000004054100000000000024756 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011, Piston Cloud Computing, Inc. # Copyright 2011 Nebula, Inc. # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import abc import warnings from keystoneclient import exceptions from keystoneclient.i18n import _ class ServiceCatalog(object, metaclass=abc.ABCMeta): """Helper methods for dealing with a Keystone Service Catalog. .. warning:: Setting region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ @classmethod def factory(cls, resource_dict, token=None, region_name=None): """Create ServiceCatalog object given an auth token. .. warning:: Setting region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ if ServiceCatalogV3.is_valid(resource_dict): return ServiceCatalogV3(token, resource_dict, region_name) elif ServiceCatalogV2.is_valid(resource_dict): return ServiceCatalogV2(resource_dict, region_name) else: raise NotImplementedError(_('Unrecognized auth response')) def __init__(self, region_name=None): if region_name: warnings.warn( 'Setting region_name on the service catalog is deprecated in ' 'favor of passing the region name as a parameter to calls ' 'made to the service catalog as of the 1.7.0 release and may ' 'be removed in the 2.0.0 release.', DeprecationWarning) self._region_name = region_name @property def region_name(self): """Region name. .. warning:: region_name is deprecated in favor of passing the region name as a parameter to calls made to the service catalog as of the 1.7.0 release and may be removed in the 2.0.0 release. """ warnings.warn( 'region_name is deprecated in favor of passing the region name as ' 'a parameter to calls made to the service catalog as of the 1.7.0 ' 'release and may be removed in the 2.0.0 release.', DeprecationWarning) return self._region_name def _get_endpoint_region(self, endpoint): return endpoint.get('region_id') or endpoint.get('region') @abc.abstractmethod def get_token(self): """Fetch token details from service catalog. Returns a dictionary containing the following:: - `id`: Token's ID - `expires`: Token's expiration - `user_id`: Authenticated user's ID - `tenant_id`: Authorized project's ID - `domain_id`: Authorized domain's ID """ raise NotImplementedError() # pragma: no cover @abc.abstractmethod def _is_endpoint_type_match(self, endpoint, endpoint_type): """Helper function to normalize endpoint matching across v2 and v3. :returns: True if the provided endpoint matches the required endpoint_type otherwise False. """ pass # pragma: no cover @abc.abstractmethod def _normalize_endpoint_type(self, endpoint_type): """Handle differences in the way v2 and v3 catalogs specify endpoint. Both v2 and v3 must be able to handle the endpoint style of the other. For example v2 must be able to handle a 'public' endpoint_type and v3 must be able to handle a 'publicURL' endpoint_type. :returns: the endpoint string in the format appropriate for this service catalog. """ pass # pragma: no cover def get_endpoints(self, service_type=None, endpoint_type=None, region_name=None, service_name=None): """Fetch and filter endpoints for the specified service(s). Returns endpoints for the specified service (or all) containing the specified type (or all) and region (or all) and service name. If there is no name in the service catalog the service_name check will be skipped. This allows compatibility with services that existed before the name was available in the catalog. """ endpoint_type = self._normalize_endpoint_type(endpoint_type) region_name = region_name or self._region_name sc = {} for service in (self.get_data() or []): try: st = service['type'] except KeyError: continue if service_type and service_type != st: continue # NOTE(jamielennox): service_name is different. It is not available # in API < v3.3. If it is in the catalog then we enforce it, if it # is not then we don't because the name could be correct we just # don't have that information to check against. if service_name: try: sn = service['name'] except KeyError: # nosec(cjschaef) # assume that we're in v3.0-v3.2 and don't have the name in # the catalog. Skip the check. pass else: if service_name != sn: continue endpoints = sc.setdefault(st, []) for endpoint in service.get('endpoints', []): if (endpoint_type and not self._is_endpoint_type_match(endpoint, endpoint_type)): continue if (region_name and region_name != self._get_endpoint_region(endpoint)): continue endpoints.append(endpoint) return sc def _get_service_endpoints(self, attr, filter_value, service_type, endpoint_type, region_name, service_name): """Fetch the endpoints of a particular service_type. Endpoints returned are also filtered based on the attr and filter_value provided. """ sc_endpoints = self.get_endpoints(service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) try: endpoints = sc_endpoints[service_type] except KeyError: return if attr and not filter_value: warnings.warn( 'Providing attr without filter_value to get_urls() is ' 'deprecated as of the 1.7.0 release and may be removed in the ' '2.0.0 release. Either both should be provided or neither ' 'should be provided.') if filter_value: return [endpoint for endpoint in endpoints if endpoint.get(attr) == filter_value] return endpoints @abc.abstractmethod def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): """Fetch endpoint urls from the service catalog. Fetch the endpoints from the service catalog for a particular endpoint attribute. If no attribute is given, return the first endpoint of the specified type. :param string attr: Endpoint attribute name. :param string filter_value: Endpoint attribute value. :param string service_type: Service type of the endpoint. :param string endpoint_type: Type of endpoint. Possible values: public or publicURL, internal or internalURL, admin or adminURL :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. :returns: tuple of urls or None (if no match found) """ raise NotImplementedError() # pragma: no cover def url_for(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): """Fetch an endpoint from the service catalog. Fetch the specified endpoint from the service catalog for a particular endpoint attribute. If no attribute is given, return the first endpoint of the specified type. Valid endpoint types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` :param string attr: Endpoint attribute name. :param string filter_value: Endpoint attribute value. :param string service_type: Service type of the endpoint. :param string endpoint_type: Type of endpoint. :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. """ if not self.get_data(): raise exceptions.EmptyCatalog(_('The service catalog is empty.')) urls = self.get_urls(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) try: return urls[0] except Exception: if service_name and region_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service named %(service_name)s in %(region_name)s ' 'region not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'service_name': service_name, 'region_name': region_name}) elif service_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service named %(service_name)s not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'service_name': service_name}) elif region_name: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service in %(region_name)s region not found') % {'endpoint_type': endpoint_type, 'service_type': service_type, 'region_name': region_name}) else: msg = (_('%(endpoint_type)s endpoint for %(service_type)s ' 'service not found') % {'endpoint_type': endpoint_type, 'service_type': service_type}) raise exceptions.EndpointNotFound(msg) @abc.abstractmethod def get_data(self): """Get the raw catalog structure. Get the version dependent catalog structure as it is presented within the resource. :returns: list containing raw catalog data entries or None """ raise NotImplementedError() # pragma: no cover class ServiceCatalogV2(ServiceCatalog): """An object for encapsulating the v2 service catalog. The object is created using raw v2 auth token from Keystone. """ def __init__(self, resource_dict, region_name=None): self.catalog = resource_dict super(ServiceCatalogV2, self).__init__(region_name=region_name) @classmethod def is_valid(cls, resource_dict): # This class is also used for reading token info of an unscoped token. # Unscoped token does not have 'serviceCatalog' in V2, checking this # will not work. Use 'token' attribute instead. return 'token' in resource_dict def _normalize_endpoint_type(self, endpoint_type): if endpoint_type and 'URL' not in endpoint_type: endpoint_type += 'URL' return endpoint_type def _is_endpoint_type_match(self, endpoint, endpoint_type): return endpoint_type in endpoint def get_data(self): return self.catalog.get('serviceCatalog') def get_token(self): token = {'id': self.catalog['token']['id'], 'expires': self.catalog['token']['expires']} try: token['user_id'] = self.catalog['user']['id'] token['tenant_id'] = self.catalog['token']['tenant']['id'] except KeyError: # nosec(cjschaef) # just leave the tenant and user out if it doesn't exist pass return token def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='publicURL', region_name=None, service_name=None): endpoint_type = self._normalize_endpoint_type(endpoint_type) endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) if endpoints: return tuple([endpoint[endpoint_type] for endpoint in endpoints]) else: return None class ServiceCatalogV3(ServiceCatalog): """An object for encapsulating the v3 service catalog. The object is created using raw v3 auth token from Keystone. """ def __init__(self, token, resource_dict, region_name=None): super(ServiceCatalogV3, self).__init__(region_name=region_name) self._auth_token = token self.catalog = resource_dict @classmethod def is_valid(cls, resource_dict): # This class is also used for reading token info of an unscoped token. # Unscoped token does not have 'catalog', checking this # will not work. Use 'methods' attribute instead. return 'methods' in resource_dict def _normalize_endpoint_type(self, endpoint_type): if endpoint_type: endpoint_type = endpoint_type.rstrip('URL') return endpoint_type def _is_endpoint_type_match(self, endpoint, endpoint_type): try: return endpoint_type == endpoint['interface'] except KeyError: return False def get_data(self): return self.catalog.get('catalog') def get_token(self): token = {'id': self._auth_token, 'expires': self.catalog['expires_at']} try: token['user_id'] = self.catalog['user']['id'] domain = self.catalog.get('domain') if domain: token['domain_id'] = domain['id'] project = self.catalog.get('project') if project: token['tenant_id'] = project['id'] except KeyError: # nosec(cjschaef) # just leave the domain, project and user out if it doesn't exist pass return token def get_urls(self, attr=None, filter_value=None, service_type='identity', endpoint_type='public', region_name=None, service_name=None): endpoints = self._get_service_endpoints(attr=attr, filter_value=filter_value, service_type=service_type, endpoint_type=endpoint_type, region_name=region_name, service_name=service_name) if endpoints: return tuple([endpoint['url'] for endpoint in endpoints]) else: return None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/session.py0000664000175000017500000012444100000000000023311 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 argparse import functools import hashlib import logging import os import socket import time import urllib.parse import warnings from debtcollector import removals from oslo_config import cfg from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils import requests from keystoneclient import exceptions from keystoneclient.i18n import _ osprofiler_web = importutils.try_import("osprofiler.web") USER_AGENT = 'python-keystoneclient' # NOTE(jamielennox): Clients will likely want to print more than json. Please # propose a patch if you have a content type you think is reasonable to print # here and we'll add it to the list as required. _LOG_CONTENT_TYPES = set(['application/json']) _logger = logging.getLogger(__name__) def _positive_non_zero_float(argument_value): if argument_value is None: return None try: value = float(argument_value) except ValueError: msg = _("%s must be a float") % argument_value raise argparse.ArgumentTypeError(msg) if value <= 0: msg = _("%s must be greater than 0") % argument_value raise argparse.ArgumentTypeError(msg) return value def request(url, method='GET', **kwargs): return Session().request(url, method=method, **kwargs) def _remove_service_catalog(body): try: data = jsonutils.loads(body) # V3 token if 'token' in data and 'catalog' in data['token']: data['token']['catalog'] = '' return jsonutils.dumps(data) # V2 token if 'serviceCatalog' in data['access']: data['access']['serviceCatalog'] = '' return jsonutils.dumps(data) except Exception: # nosec(cjschaef): multiple exceptions can be raised # Don't fail trying to clean up the request body. pass return body class Session(object): """Maintains client communication state and common functionality. As much as possible the parameters to this class reflect and are passed directly to the requests library. :param auth: An authentication plugin to authenticate the session with. (optional, defaults to None) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :param requests.Session session: A requests session object that can be used for issuing requests. (optional) :param string original_ip: The original IP of the requesting user which will be sent to identity service in a 'Forwarded' header. (optional) :param verify: The verification arguments to pass to requests. These are of the same form as requests expects, so True or False to verify (or not) against system certificates or a path to a bundle or CA certs to check against or None for requests to attempt to locate and use certificates. (optional, defaults to True) :param cert: A client certificate to pass to requests. These are of the same form as requests expects. Either a single filename containing both the certificate and key or a tuple containing the path to the certificate then a path to the key. (optional) :param float timeout: A timeout to pass to requests. This should be a numerical value indicating some amount (or fraction) of seconds or 0 for no timeout. (optional, defaults to 0) :param string user_agent: A User-Agent header string to use for the request. If not provided a default is used. (optional, defaults to 'python-keystoneclient') :param int/bool redirect: Controls the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional, default to 30) """ user_agent = None _REDIRECT_STATUSES = (301, 302, 303, 305, 307) REDIRECT_STATUSES = _REDIRECT_STATUSES """This property is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release.""" _DEFAULT_REDIRECT_LIMIT = 30 DEFAULT_REDIRECT_LIMIT = _DEFAULT_REDIRECT_LIMIT """This property is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release.""" def __init__(self, auth=None, session=None, original_ip=None, verify=True, cert=None, timeout=None, user_agent=None, redirect=_DEFAULT_REDIRECT_LIMIT): warnings.warn( 'keystoneclient.session.Session is deprecated as of the 2.1.0 ' 'release in favor of keystoneauth1.session.Session. It will be ' 'removed in future releases.', DeprecationWarning) if not session: session = requests.Session() # Use TCPKeepAliveAdapter to fix bug 1323862 for scheme in list(session.adapters): session.mount(scheme, TCPKeepAliveAdapter()) self.auth = auth self.session = session self.original_ip = original_ip self.verify = verify self.cert = cert self.timeout = None self.redirect = redirect if timeout is not None: self.timeout = float(timeout) # don't override the class variable if none provided if user_agent is not None: self.user_agent = user_agent @staticmethod def _process_header(header): """Redact the secure headers to be logged.""" secure_headers = ('authorization', 'x-auth-token', 'x-subject-token', 'x-service-token') if header[0].lower() in secure_headers: # hashlib.sha1() bandit nosec, as it is HMAC-SHA1 in # keystone, which is considered secure (unlike just sha1) token_hasher = hashlib.sha1() # nosec(lhinds) token_hasher.update(header[1].encode('utf-8')) token_hash = token_hasher.hexdigest() return (header[0], '{SHA1}%s' % token_hash) return header def _http_log_request(self, url, method=None, data=None, headers=None, logger=_logger): if not logger.isEnabledFor(logging.DEBUG): # NOTE(morganfainberg): This whole debug section is expensive, # there is no need to do the work if we're not going to emit a # debug log. return string_parts = ['REQ: curl -g -i'] # NOTE(jamielennox): None means let requests do its default validation # so we need to actually check that this is False. if self.verify is False: string_parts.append('--insecure') elif isinstance(self.verify, str): string_parts.append('--cacert "%s"' % self.verify) if method: string_parts.extend(['-X', method]) string_parts.append(url) if headers: for header in headers.items(): string_parts.append('-H "%s: %s"' % self._process_header(header)) if data: if isinstance(data, bytes): try: data = data.decode("ascii") except UnicodeDecodeError: data = "" string_parts.append("-d '%s'" % data) try: logger.debug(' '.join(string_parts)) except UnicodeDecodeError: logger.debug("Replaced characters that could not be decoded" " in log output, original caused UnicodeDecodeError") string_parts = [ encodeutils.safe_decode( part, errors='replace') for part in string_parts] logger.debug(' '.join(string_parts)) def _http_log_response(self, response, logger): if not logger.isEnabledFor(logging.DEBUG): return # NOTE(samueldmq): If the response does not provide enough info about # the content type to decide whether it is useful and safe to log it # or not, just do not log the body. Trying to# read the response body # anyways may result on reading a long stream of bytes and getting an # unexpected MemoryError. See bug 1616105 for further details. content_type = response.headers.get('content-type', None) # NOTE(lamt): Per [1], the Content-Type header can be of the form # Content-Type := type "/" subtype *[";" parameter] # [1] https://www.w3.org/Protocols/rfc1341/4_Content-Type.html for log_type in _LOG_CONTENT_TYPES: if content_type is not None and content_type.startswith(log_type): text = _remove_service_catalog(response.text) break else: text = ('Omitted, Content-Type is set to %s. Only ' '%s responses have their bodies logged.') text = text % (content_type, ', '.join(_LOG_CONTENT_TYPES)) string_parts = [ 'RESP:', '[%s]' % response.status_code ] for header in response.headers.items(): string_parts.append('%s: %s' % self._process_header(header)) string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text)) logger.debug(' '.join(string_parts)) # NOTE(artmr): parameter 'original_ip' value is never used def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, authenticated=None, endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. Arguments that are not handled are passed through to the requests library. :param string url: Path or fully qualified URL of HTTP request. If only a path is provided then endpoint_filter must also be provided such that the base URL can be determined. If a fully qualified URL is provided then endpoint_filter will be ignored. :param string method: The http method to use. (e.g. 'GET', 'POST') :param string original_ip: Mark this request as forwarded for this ip. (optional) :param dict headers: Headers to be included in the request. (optional) :param json: Some data to be represented as JSON. (optional) :param string user_agent: A user_agent to use for the request. If present will override one present in headers. (optional) :param int/bool redirect: the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. (optional, defaults to 0 - never retry). :param bool authenticated: True if a token should be attached to this request, False if not or None for attach if an auth_plugin is available. (optional, defaults to None) :param dict endpoint_filter: Data to be provided to an auth plugin with which it should be able to determine an endpoint to use for this request. If not provided then URL is expected to be a fully qualified URL. (optional) :param str endpoint_override: The URL to use instead of looking up the endpoint in the auth plugin. This will be ignored if a fully qualified URL is provided but take priority over an endpoint_filter. (optional) :param auth: The auth plugin to use when authenticating this request. This will override the plugin that is attached to the session (if any). (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :param requests_auth: A requests library auth plugin that cannot be passed via kwarg because the `auth` kwarg collides with our own auth plugins. (optional) :type requests_auth: :py:class:`requests.auth.AuthBase` :param bool raise_exc: If True then raise an appropriate exception for failed HTTP requests. If False then return the request object. (optional, default True) :param bool allow_reauth: Allow fetching a new token and retrying the request on receiving a 401 Unauthorized response. (optional, default True) :param bool log: If True then log the request and response data to the debug log. (optional, default True) :param logger: The logger object to use to log request and responses. If not provided the keystoneclient.session default logger will be used. :type logger: logging.Logger :param kwargs: any other parameter that can be passed to requests.Session.request (such as `headers`). Except: 'data' will be overwritten by the data in 'json' param. 'allow_redirects' is ignored as redirects are handled by the session. :raises keystoneclient.exceptions.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) if authenticated is None: authenticated = bool(auth or self.auth) if authenticated: auth_headers = self.get_auth_headers(auth) if auth_headers is None: msg = _('No valid authentication is available') raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) # if we are passed a fully qualified URL and an endpoint_filter we # should ignore the filter. This will make it easier for clients who # want to overrule the default endpoint_filter data added to all client # requests. We check fully qualified here by the presence of a host. if not urllib.parse.urlparse(url).netloc: base_url = None if endpoint_override: base_url = endpoint_override elif endpoint_filter: base_url = self.get_endpoint(auth, **endpoint_filter) if not base_url: service_type = (endpoint_filter or {}).get('service_type', 'unknown') msg = _('Endpoint for %s service') % service_type raise exceptions.EndpointNotFound(msg) url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: user_agent = headers.setdefault('User-Agent', USER_AGENT) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers['Content-Type'] = 'application/json' kwargs['data'] = jsonutils.dumps(json) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, logger=logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, logger, connect_retries) try: connection_params = self.get_auth_connection_params(auth=auth) except exceptions.MissingAuthPlugin: # nosec(cjschaef) # NOTE(jamielennox): If we've gotten this far without an auth # plugin then we should be happy with allowing no additional # connection params. This will be the typical case for plugins # anyway. pass else: if connection_params: kwargs.update(connection_params) resp = send(**kwargs) # handle getting a 401 Unauthorized response by invalidating the plugin # and then retrying the request. This is only tried once. if resp.status_code == 401 and authenticated and allow_reauth: if self.invalidate(auth): auth_headers = self.get_auth_headers(auth) if auth_headers is not None: headers.update(auth_headers) resp = send(**kwargs) if raise_exc and resp.status_code >= 400: logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp def _send_request(self, url, method, redirect, log, logger, connect_retries, connect_retry_delay=0.5, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get # NOTE(jamielennox): The interaction between retries and redirects are # handled naively. We will attempt only a maximum number of retries and # redirects rather than per request limits. Otherwise the extreme case # could be redirects * retries requests. This will be sufficient in # most cases and can be fixed properly if there's ever a need. try: try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError as e: msg = _('SSL exception connecting to %(url)s: ' '%(error)s') % {'url': url, 'error': e} raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise logger.info('Failure: %(e)s. Retrying in %(delay).1fs.', {'e': e, 'delay': connect_retry_delay}) time.sleep(connect_retry_delay) return self._send_request( url, method, redirect, log, logger, connect_retries=connect_retries - 1, connect_retry_delay=connect_retry_delay * 2, **kwargs) if log: self._http_log_response(resp, logger) if resp.status_code in self._REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: logger.warning("Failed to redirect request to %s as new " "location was not provided.", resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. new_resp = self._send_request( location, method, redirect, log, logger, connect_retries=connect_retries, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp def head(self, url, **kwargs): """Perform a HEAD request. This calls :py:meth:`.request()` with ``method`` set to ``HEAD``. """ return self.request(url, 'HEAD', **kwargs) def get(self, url, **kwargs): """Perform a GET request. This calls :py:meth:`.request()` with ``method`` set to ``GET``. """ return self.request(url, 'GET', **kwargs) def post(self, url, **kwargs): """Perform a POST request. This calls :py:meth:`.request()` with ``method`` set to ``POST``. """ return self.request(url, 'POST', **kwargs) def put(self, url, **kwargs): """Perform a PUT request. This calls :py:meth:`.request()` with ``method`` set to ``PUT``. """ return self.request(url, 'PUT', **kwargs) def delete(self, url, **kwargs): """Perform a DELETE request. This calls :py:meth:`.request()` with ``method`` set to ``DELETE``. """ return self.request(url, 'DELETE', **kwargs) def patch(self, url, **kwargs): """Perform a PATCH request. This calls :py:meth:`.request()` with ``method`` set to ``PATCH``. """ return self.request(url, 'PATCH', **kwargs) @classmethod def construct(cls, kwargs): """Handle constructing a session from both old and new arguments. Support constructing a session from the old :py:class:`~keystoneclient.httpclient.HTTPClient` args as well as the new request-style arguments. .. warning:: *DEPRECATED as of 1.7.0*: This function is purely for bridging the gap between older client arguments and the session arguments that they relate to. It is not intended to be used as a generic Session Factory. This function may be removed in the 2.0.0 release. This function purposefully modifies the input kwargs dictionary so that the remaining kwargs dict can be reused and passed on to other functions without session arguments. """ warnings.warn( 'Session.construct() is deprecated as of the 1.7.0 release in ' 'favor of using session constructor and may be removed in the ' '2.0.0 release.', DeprecationWarning) return cls._construct(kwargs) @classmethod def _construct(cls, kwargs): params = {} for attr in ('verify', 'cacert', 'cert', 'key', 'insecure', 'timeout', 'session', 'original_ip', 'user_agent'): try: params[attr] = kwargs.pop(attr) except KeyError: # nosec(cjschaef): we are brute force # identifying possible attributes for kwargs pass return cls._make(**params) @classmethod def _make(cls, insecure=False, verify=None, cacert=None, cert=None, key=None, **kwargs): """Create a session with individual certificate parameters. Some parameters used to create a session don't lend themselves to be loaded from config/CLI etc. Create a session by converting those parameters into session __init__ parameters. """ if verify is None: if insecure: verify = False else: verify = cacert or True if cert and key: warnings.warn( 'Passing cert and key together is deprecated as of the 1.7.0 ' 'release in favor of the requests library form of having the ' 'cert and key as a tuple and may be removed in the 2.0.0 ' 'release.', DeprecationWarning) cert = (cert, key) return cls(verify=verify, cert=cert, **kwargs) def _auth_required(self, auth, msg): if not auth: auth = self.auth if not auth: raise exceptions.MissingAuthPlugin(msg) return auth def get_auth_headers(self, auth=None, **kwargs): """Return auth headers as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: Authentication headers or None for failure. :rtype: dict """ msg = _('An auth plugin is required to fetch a token') auth = self._auth_required(auth, msg) return auth.get_headers(self, **kwargs) @removals.remove(message='Use get_auth_headers instead.', version='1.7.0', removal_version='2.0.0') def get_token(self, auth=None): """Return a token as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. .. warning:: This method is deprecated as of the 1.7.0 release in favor of :meth:`get_auth_headers` and may be removed in the 2.0.0 release. This method assumes that the only header that is used to authenticate a message is 'X-Auth-Token' which may not be correct. :returns: A valid token. :rtype: string """ return (self.get_auth_headers(auth) or {}).get('X-Auth-Token') def get_endpoint(self, auth=None, **kwargs): """Get an endpoint as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: An endpoint if available or None. :rtype: string """ msg = _('An auth plugin is required to determine endpoint URL') auth = self._auth_required(auth, msg) return auth.get_endpoint(self, **kwargs) def get_auth_connection_params(self, auth=None, **kwargs): """Return auth connection params as provided by the auth plugin. An auth plugin may specify connection parameters to the request like providing a client certificate for communication. We restrict the values that may be returned from this function to prevent an auth plugin overriding values unrelated to connection parameters. The values that are currently accepted are: - `cert`: a path to a client certificate, or tuple of client certificate and key pair that are used with this request. - `verify`: a boolean value to indicate verifying SSL certificates against the system CAs or a path to a CA file to verify with. These values are passed to the requests library and further information on accepted values may be found there. :param auth: The auth plugin to use for tokens. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :raises keystoneclient.exceptions.UnsupportedParameters: if the plugin returns a parameter that is not supported by this session. :returns: Authentication headers or None for failure. :rtype: dict """ msg = _('An auth plugin is required to fetch connection params') auth = self._auth_required(auth, msg) params = auth.get_connection_params(self, **kwargs) # NOTE(jamielennox): There needs to be some consensus on what # parameters are allowed to be modified by the auth plugin here. # Ideally I think it would be only the send() parts of the request # flow. For now lets just allow certain elements. params_copy = params.copy() for arg in ('cert', 'verify'): try: kwargs[arg] = params_copy.pop(arg) except KeyError: # nosec(cjschaef): we are brute force # identifying and removing values in params_copy pass if params_copy: raise exceptions.UnsupportedParameters(list(params_copy)) return params def invalidate(self, auth=None): """Invalidate an authentication plugin. :param auth: The auth plugin to invalidate. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` """ msg = _('An auth plugin is required to validate') auth = self._auth_required(auth, msg) return auth.invalidate() def get_user_id(self, auth=None): """Return the authenticated user_id as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns string: Current user_id or None if not supported by plugin. """ msg = _('An auth plugin is required to get user_id') auth = self._auth_required(auth, msg) return auth.get_user_id(self) def get_project_id(self, auth=None): """Return the authenticated project_id as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns string: Current project_id or None if not supported by plugin. """ msg = _('An auth plugin is required to get project_id') auth = self._auth_required(auth, msg) return auth.get_project_id(self) @classmethod def get_conf_options(cls, deprecated_opts=None): """Get oslo_config options that are needed for a :py:class:`.Session`. These may be useful without being registered for config file generation or to manipulate the options before registering them yourself. The options that are set are: :cafile: The certificate authority filename. :certfile: The client certificate file to present. :keyfile: The key for the client certificate. :insecure: Whether to ignore SSL verification. :timeout: The max time to wait for HTTP connections. :param dict deprecated_opts: Deprecated options that should be included in the definition of new options. This should be a dict from the name of the new option to a list of oslo.DeprecatedOpts that correspond to the new option. (optional) For example, to support the ``ca_file`` option pointing to the new ``cafile`` option name:: old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group') deprecated_opts={'cafile': [old_opt]} :returns: A list of oslo_config options. """ if deprecated_opts is None: deprecated_opts = {} return [cfg.StrOpt('cafile', deprecated_opts=deprecated_opts.get('cafile'), help='PEM encoded Certificate Authority to use ' 'when verifying HTTPs connections.'), cfg.StrOpt('certfile', deprecated_opts=deprecated_opts.get('certfile'), help='PEM encoded client certificate cert file'), cfg.StrOpt('keyfile', deprecated_opts=deprecated_opts.get('keyfile'), help='PEM encoded client certificate key file'), cfg.BoolOpt('insecure', default=False, deprecated_opts=deprecated_opts.get('insecure'), help='Verify HTTPS connections.'), cfg.IntOpt('timeout', deprecated_opts=deprecated_opts.get('timeout'), help='Timeout value for http requests'), ] @classmethod def register_conf_options(cls, conf, group, deprecated_opts=None): """Register the oslo_config options that are needed for a session. The options that are set are: :cafile: The certificate authority filename. :certfile: The client certificate file to present. :keyfile: The key for the client certificate. :insecure: Whether to ignore SSL verification. :timeout: The max time to wait for HTTP connections. :param oslo_config.Cfg conf: config object to register with. :param string group: The ini group to register options in. :param dict deprecated_opts: Deprecated options that should be included in the definition of new options. This should be a dict from the name of the new option to a list of oslo.DeprecatedOpts that correspond to the new option. (optional) For example, to support the ``ca_file`` option pointing to the new ``cafile`` option name:: old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group') deprecated_opts={'cafile': [old_opt]} :returns: The list of options that was registered. """ opts = cls.get_conf_options(deprecated_opts=deprecated_opts) conf.register_group(cfg.OptGroup(group)) conf.register_opts(opts, group=group) return opts @classmethod def load_from_conf_options(cls, conf, group, **kwargs): """Create a session object from an oslo_config object. The options must have been previously registered with register_conf_options. :param oslo_config.Cfg conf: config object to register with. :param string group: The ini group to register options in. :param dict kwargs: Additional parameters to pass to session construction. :returns: A new session object. :rtype: :py:class:`.Session` """ c = conf[group] kwargs['insecure'] = c.insecure kwargs['cacert'] = c.cafile if c.certfile and c.keyfile: kwargs['cert'] = (c.certfile, c.keyfile) kwargs['timeout'] = c.timeout return cls._make(**kwargs) @staticmethod def register_cli_options(parser): """Register the argparse arguments that are needed for a session. :param argparse.ArgumentParser parser: parser to add to. """ parser.add_argument('--insecure', default=False, action='store_true', help='Explicitly allow client to perform ' '"insecure" TLS (https) requests. The ' 'server\'s certificate will not be verified ' 'against any certificate authorities. This ' 'option should be used with caution.') parser.add_argument('--os-cacert', metavar='', default=os.environ.get('OS_CACERT'), help='Specify a CA bundle file to use in ' 'verifying a TLS (https) server certificate. ' 'Defaults to env[OS_CACERT].') parser.add_argument('--os-cert', metavar='', default=os.environ.get('OS_CERT'), help='Defaults to env[OS_CERT].') parser.add_argument('--os-key', metavar='', default=os.environ.get('OS_KEY'), help='Defaults to env[OS_KEY].') parser.add_argument('--timeout', default=600, type=_positive_non_zero_float, metavar='', help='Set request timeout (in seconds).') @classmethod def load_from_cli_options(cls, args, **kwargs): """Create a :py:class:`.Session` object from CLI arguments. The CLI arguments must have been registered with :py:meth:`.register_cli_options`. :param Namespace args: result of parsed arguments. :returns: A new session object. :rtype: :py:class:`.Session` """ kwargs['insecure'] = args.insecure kwargs['cacert'] = args.os_cacert if args.os_cert and args.os_key: kwargs['cert'] = (args.os_cert, args.os_key) kwargs['timeout'] = args.timeout return cls._make(**kwargs) class TCPKeepAliveAdapter(requests.adapters.HTTPAdapter): """The custom adapter used to set TCP Keep-Alive on all connections. This Adapter also preserves the default behaviour of Requests which disables Nagle's Algorithm. See also: http://blogs.msdn.com/b/windowsazurestorage/archive/2010/06/25/nagle-s-algorithm-is-not-friendly-towards-small-requests.aspx """ def init_poolmanager(self, *args, **kwargs): if 'socket_options' not in kwargs: socket_options = [ # Keep Nagle's algorithm off (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), # Turn on TCP Keep-Alive (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] # Some operating systems (e.g., OSX) do not support setting # keepidle if hasattr(socket, 'TCP_KEEPIDLE'): socket_options += [ # Wait 60 seconds before sending keep-alive probes (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) ] # TODO(claudiub): Windows does not contain the TCP_KEEPCNT and # TCP_KEEPINTVL socket attributes. Instead, it contains # SIO_KEEPALIVE_VALS, which can be set via ioctl, which should be # set once it is available in requests. # https://msdn.microsoft.com/en-us/library/dd877220%28VS.85%29.aspx if hasattr(socket, 'TCP_KEEPCNT'): socket_options += [ # Set the maximum number of keep-alive probes (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4) ] if hasattr(socket, 'TCP_KEEPINTVL'): socket_options += [ # Send keep-alive probes every 15 seconds (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15) ] # After waiting 60 seconds, and then sending a probe once every 15 # seconds 4 times, these options should ensure that a connection # hands for no longer than 2 minutes before a ConnectionError is # raised. kwargs['socket_options'] = socket_options super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2190037 python_keystoneclient-5.6.0/keystoneclient/tests/0000775000175000017500000000000000000000000022410 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/__init__.py0000664000175000017500000000000000000000000024507 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2190037 python_keystoneclient-5.6.0/keystoneclient/tests/functional/0000775000175000017500000000000000000000000024552 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/__init__.py0000664000175000017500000000000000000000000026651 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/base.py0000664000175000017500000000553300000000000026044 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 testtools from keystoneclient import client import os_client_config IDENTITY_CLIENT = 'identity' OPENSTACK_CLOUDS = ('functional_admin', 'devstack-admin', 'envvars') def get_client(version): """Create a keystoneclient instance to run functional tests. The client is instantiated via os-client-config either based on a clouds.yaml config file or from the environment variables. First, look for a 'functional_admin' cloud, as this is a cloud that the user may have defined for functional testing with admin credentials. If that is not found, check for the 'devstack-admin' cloud. Finally, fall back to looking for environment variables. """ for cloud in OPENSTACK_CLOUDS: try: cloud_config = os_client_config.get_config( cloud=cloud, identity_api_version=version) return cloud_config.get_legacy_client(service_key=IDENTITY_CLIENT, constructor=client.Client) except os_client_config.exceptions.OpenStackConfigException: pass raise Exception("Could not find any cloud definition for clouds named" " functional_admin or devstack-admin. Check your" " clouds.yaml file or your envvars and try again.") class ClientTestCase(testtools.TestCase): def setUp(self): super(ClientTestCase, self).setUp() if not self.auth_ref.project_scoped: raise Exception("Could not run functional tests, which are " "run based on the scope provided for " "authentication. Please provide a project " "scope information.") @property def client(self): if not hasattr(self, '_client'): self._client = get_client(self.version) return self._client @property def auth_ref(self): return self.client.session.auth.get_auth_ref(self.client.session) @property def project_domain_id(self): return self.auth_ref.project_domain_id @property def project_id(self): return self.client.session.get_project_id() @property def user_id(self): return self.client.session.get_user_id() class V3ClientTestCase(ClientTestCase): version = '3' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/test_base.py0000664000175000017500000000150300000000000027074 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 keystoneclient from keystoneclient.tests.functional import base class V3ClientVersionTestCase(base.V3ClientTestCase): def test_version(self): self.assertIsInstance(self.client, keystoneclient.v3.client.Client) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2230036 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/0000775000175000017500000000000000000000000025102 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/__init__.py0000664000175000017500000000000000000000000027201 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/client_fixtures.py0000664000175000017500000001736700000000000030701 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 fixtures import uuid RESOURCE_NAME_PREFIX = 'keystoneclient-functional-' class Base(fixtures.Fixture): def __init__(self, client, domain_id=None): super(Base, self).__init__() self.client = client self.domain_id = domain_id self.ref = None self.entity = None def __getattr__(self, name): """Return the attribute from the represented entity.""" return getattr(self.entity, name) class User(Base): def setUp(self): super(User, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id} self.entity = self.client.users.create(**self.ref) self.addCleanup(self.client.users.delete, self.entity) class Group(Base): def setUp(self): super(Group, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id} self.entity = self.client.groups.create(**self.ref) self.addCleanup(self.client.groups.delete, self.entity) class Domain(Base): def setUp(self): super(Domain, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'description': uuid.uuid4().hex, 'enabled': True} self.entity = self.client.domains.create(**self.ref) # Only disabled domains can be deleted self.addCleanup(self.client.domains.delete, self.entity) self.addCleanup(self.client.domains.update, self.entity, enabled=False) class Project(Base): def __init__(self, client, domain_id=None, parent=None, tags=None): super(Project, self).__init__(client, domain_id) self.parent = parent self.tags = tags if tags else [] def setUp(self): super(Project, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.domain_id, 'enabled': True, 'parent': self.parent, 'tags': self.tags} self.entity = self.client.projects.create(**self.ref) self.addCleanup(self.client.projects.delete, self.entity) class Role(Base): def __init__(self, client, name=None, domain=None): super(Role, self).__init__(client) self.name = name or RESOURCE_NAME_PREFIX + uuid.uuid4().hex self.domain = domain def setUp(self): super(Role, self).setUp() self.ref = {'name': self.name, 'domain': self.domain} self.entity = self.client.roles.create(**self.ref) self.addCleanup(self.client.roles.delete, self.entity) class InferenceRule(Base): def __init__(self, client, prior_role, implied_role): super(InferenceRule, self).__init__(client) self.prior_role = prior_role self.implied_role = implied_role def setUp(self): super(InferenceRule, self).setUp() self.ref = {'prior_role': self.prior_role, 'implied_role': self.implied_role} self.entity = self.client.inference_rules.create(**self.ref) self.addCleanup(self.client.inference_rules.delete, self.prior_role, self.implied_role) class Service(Base): def setUp(self): super(Service, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'type': uuid.uuid4().hex, 'enabled': True, 'description': uuid.uuid4().hex} self.entity = self.client.services.create(**self.ref) self.addCleanup(self.client.services.delete, self.entity) class Policy(Base): def setUp(self): super(Policy, self).setUp() self.ref = {'blob': uuid.uuid4().hex, 'type': uuid.uuid4().hex} self.entity = self.client.policies.create(**self.ref) self.addCleanup(self.client.policies.delete, self.entity) class Region(Base): def __init__(self, client, parent_region=None): super(Region, self).__init__(client) self.parent_region = parent_region def setUp(self): super(Region, self).setUp() self.ref = {'description': uuid.uuid4().hex, 'parent_region': self.parent_region} self.entity = self.client.regions.create(**self.ref) self.addCleanup(self.client.regions.delete, self.entity) class Endpoint(Base): def __init__(self, client, service, interface, region=None): super(Endpoint, self).__init__(client) self.service = service self.interface = interface self.region = region def setUp(self): super(Endpoint, self).setUp() self.ref = {'service': self.service, 'url': 'http://' + uuid.uuid4().hex, 'enabled': True, 'interface': self.interface, 'region': self.region} self.entity = self.client.endpoints.create(**self.ref) self.addCleanup(self.client.endpoints.delete, self.entity) class EndpointGroup(Base): def setUp(self): super(EndpointGroup, self).setUp() self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'filters': {'interface': 'public'}, 'description': uuid.uuid4().hex} self.entity = self.client.endpoint_groups.create(**self.ref) self.addCleanup(self.client.endpoint_groups.delete, self.entity) class Credential(Base): def __init__(self, client, user, type, project=None): super(Credential, self).__init__(client) self.user = user self.type = type self.project = project if type == 'ec2': self.blob = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") else: self.blob = uuid.uuid4().hex def setUp(self): super(Credential, self).setUp() self.ref = {'user': self.user, 'type': self.type, 'blob': self.blob, 'project': self.project} self.entity = self.client.credentials.create(**self.ref) self.addCleanup(self.client.credentials.delete, self.entity) class EC2(Base): def __init__(self, client, user_id, project_id): super(EC2, self).__init__(client) self.user_id = user_id self.project_id = project_id def setUp(self): super(EC2, self).setUp() self.ref = {'user_id': self.user_id, 'project_id': self.project_id} self.entity = self.client.ec2.create(**self.ref) self.addCleanup(self.client.ec2.delete, self.user_id, self.entity.access) class DomainConfig(Base): def __init__(self, client, domain_id): super(DomainConfig, self).__init__(client, domain_id=domain_id) self.domain_id = domain_id def setUp(self): super(DomainConfig, self).setUp() self.ref = {'identity': {'driver': uuid.uuid4().hex}, 'ldap': {'url': uuid.uuid4().hex}} self.entity = self.client.domain_configs.create( self.domain_id, self.ref) self.addCleanup(self.client.domain_configs.delete, self.domain_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_credentials.py0000664000175000017500000002012700000000000031012 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class CredentialsTestCase(base.V3ClientTestCase): def setUp(self): super(CredentialsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) def check_credential(self, credential, credential_ref=None): self.assertIsNotNone(credential.id) self.assertIn('self', credential.links) self.assertIn('/credentials/' + credential.id, credential.links['self']) if credential_ref: self.assertEqual(credential_ref['user'], credential.user_id) self.assertEqual(credential_ref['type'], credential.type) self.assertEqual(credential_ref['blob'], credential.blob) # There is no guarantee below attributes are present in credential if credential_ref['type'] == 'ec2' or hasattr(credential_ref, 'project'): self.assertEqual(credential_ref['project'], credential.project_id) else: # Only check remaining mandatory attributes self.assertIsNotNone(credential.user_id) self.assertIsNotNone(credential.type) self.assertIsNotNone(credential.blob) if credential.type == 'ec2': self.assertIsNotNone(credential.project_id) def test_create_credential_of_cert_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, 'type': 'cert', 'blob': uuid.uuid4().hex} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_create_credential_of_ec2_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) # project is mandatory attribute if the credential type is ec2 credential_ref = {'user': user.id, 'type': 'ec2', 'blob': ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}")} self.assertRaises(http.BadRequest, self.client.credentials.create, **credential_ref) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) credential_ref = {'user': user.id, 'type': 'ec2', 'blob': ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}"), 'project': project.id} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_create_credential_of_totp_type(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, 'type': 'totp', 'blob': uuid.uuid4().hex} credential = self.client.credentials.create(**credential_ref) self.addCleanup(self.client.credentials.delete, credential) self.check_credential(credential, credential_ref) def test_get_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: credential = fixtures.Credential(self.client, user=user.id, type=credential_type, project=project.id) self.useFixture(credential) credential_ret = self.client.credentials.get(credential.id) self.check_credential(credential_ret, credential.ref) def test_list_credentials(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) cert_credential = fixtures.Credential(self.client, user=user.id, type='cert') self.useFixture(cert_credential) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) ec2_credential = fixtures.Credential(self.client, user=user.id, type='ec2', project=project.id) self.useFixture(ec2_credential) totp_credential = fixtures.Credential(self.client, user=user.id, type='totp') self.useFixture(totp_credential) credentials = self.client.credentials.list() # All credentials are valid for credential in credentials: self.check_credential(credential) self.assertIn(cert_credential.entity, credentials) self.assertIn(ec2_credential.entity, credentials) self.assertIn(totp_credential.entity, credentials) def test_update_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) new_user = fixtures.User(self.client, self.test_domain.id) self.useFixture(new_user) new_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(new_project) credential = fixtures.Credential(self.client, user=user.id, type='cert') self.useFixture(credential) new_type = 'ec2' new_blob = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") credential_ret = self.client.credentials.update(credential.id, user=new_user.id, type=new_type, blob=new_blob, project=new_project.id) credential.ref.update({'user': new_user.id, 'type': new_type, 'blob': new_blob, 'project': new_project.id}) self.check_credential(credential_ret, credential.ref) def test_delete_credential(self): user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: if credential_type == 'ec2': blob_value = ("{\"access\":\"" + uuid.uuid4().hex + "\",\"secret\":\"secretKey\"}") else: blob_value = uuid.uuid4().hex credential = self.client.credentials.create(user=user.id, type=credential_type, blob=blob_value, project=project.id) self.client.credentials.delete(credential.id) self.assertRaises(http.NotFound, self.client.credentials.get, credential.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_domain_configs.py0000664000175000017500000001027300000000000031475 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class DomainConfigsTestCase(base.V3ClientTestCase): def setUp(self): super(DomainConfigsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) def check_domain_config(self, config, config_ref): for attr in config_ref: self.assertEqual( getattr(config, attr), config_ref[attr], 'Expected different %s' % attr) def _new_ref(self): return {'identity': {'driver': uuid.uuid4().hex}, 'ldap': {'url': uuid.uuid4().hex}} def test_create_domain_config(self): config_ref = self._new_ref() config = self.client.domain_configs.create( self.test_domain.id, config_ref) self.addCleanup( self.client.domain_configs.delete, self.test_domain.id) self.check_domain_config(config, config_ref) def test_create_invalid_domain_config(self): invalid_groups_ref = { uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}, uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.create, self.test_domain.id, invalid_groups_ref) invalid_options_ref = { 'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.create, self.test_domain.id, invalid_options_ref) def test_get_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) config_ret = self.client.domain_configs.get(self.test_domain.id) self.check_domain_config(config_ret, config.ref) def test_update_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) update_config_ref = self._new_ref() config_ret = self.client.domain_configs.update( self.test_domain.id, update_config_ref) self.check_domain_config(config_ret, update_config_ref) def test_update_invalid_domain_config(self): config = fixtures.DomainConfig(self.client, self.test_domain.id) self.useFixture(config) invalid_groups_ref = { uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}, uuid.uuid4().hex: {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.update, self.test_domain.id, invalid_groups_ref) invalid_options_ref = { 'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} self.assertRaises(http.Forbidden, self.client.domain_configs.update, self.test_domain.id, invalid_options_ref) def test_domain_config_delete(self): config_ref = self._new_ref() self.client.domain_configs.create(self.test_domain.id, config_ref) self.client.domain_configs.delete(self.test_domain.id) self.assertRaises(http.NotFound, self.client.domain_configs.get, self.project_domain_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_domains.py0000664000175000017500000000712300000000000030150 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class DomainsTestCase(base.V3ClientTestCase): def check_domain(self, domain, domain_ref=None): self.assertIsNotNone(domain.id) self.assertIn('self', domain.links) self.assertIn('/domains/' + domain.id, domain.links['self']) if domain_ref: self.assertEqual(domain_ref['name'], domain.name) self.assertEqual(domain_ref['enabled'], domain.enabled) # There is no guarantee description is present in domain if hasattr(domain_ref, 'description'): self.assertEqual(domain_ref['description'], domain.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(domain.name) self.assertIsNotNone(domain.enabled) def test_create_domain(self): domain_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'description': uuid.uuid4().hex, 'enabled': True} domain = self.client.domains.create(**domain_ref) self.check_domain(domain, domain_ref) # Only disabled domains can be deleted self.addCleanup(self.client.domains.delete, domain) self.addCleanup(self.client.domains.update, domain, enabled=False) def test_get_domain(self): domain_id = self.project_domain_id domain_ret = self.client.domains.get(domain_id) self.check_domain(domain_ret) def test_list_domains(self): domain_one = fixtures.Domain(self.client) self.useFixture(domain_one) domain_two = fixtures.Domain(self.client) self.useFixture(domain_two) domains = self.client.domains.list() # All domains are valid for domain in domains: self.check_domain(domain) self.assertIn(domain_one.entity, domains) self.assertIn(domain_two.entity, domains) def test_update_domain(self): domain = fixtures.Domain(self.client) self.useFixture(domain) new_description = uuid.uuid4().hex domain_ret = self.client.domains.update(domain.id, description=new_description) domain.ref.update({'description': new_description}) self.check_domain(domain_ret, domain.ref) def test_delete_domain(self): domain = self.client.domains.create(name=uuid.uuid4().hex, description=uuid.uuid4().hex, enabled=True) # Only disabled domains can be deleted self.assertRaises(http.Forbidden, self.client.domains.delete, domain.id) self.client.domains.update(domain, enabled=False) self.client.domains.delete(domain.id) self.assertRaises(http.NotFound, self.client.domains.get, domain.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_ec2.py0000664000175000017500000000655100000000000027173 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EC2TestCase(base.V3ClientTestCase): def check_ec2(self, ec2, ec2_ref=None): self.assertIn('self', ec2.links) self.assertIn('/users/%s/credentials/OS-EC2/%s' % (ec2.user_id, ec2.access), ec2.links['self']) if ec2_ref: self.assertEqual(ec2_ref['user_id'], ec2.user_id) self.assertEqual(ec2_ref['project_id'], ec2.tenant_id) else: self.assertIsNotNone(ec2.user_id) self.assertIsNotNone(ec2.tenant_id) def test_create_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2_ref = {'user_id': user.id, 'project_id': project.id} ec2 = self.client.ec2.create(**ec2_ref) self.addCleanup(self.client.ec2.delete, user.id, ec2.access) self.check_ec2(ec2, ec2_ref) def test_get_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2 = fixtures.EC2(self.client, user_id=user.id, project_id=project.id) self.useFixture(ec2) ec2_ret = self.client.ec2.get(user.id, ec2.access) self.check_ec2(ec2_ret, ec2.ref) def test_list_ec2(self): user_one = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_one) ec2_one = fixtures.EC2(self.client, user_id=user_one.id, project_id=self.project_domain_id) self.useFixture(ec2_one) user_two = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_two) ec2_two = fixtures.EC2(self.client, user_id=user_two.id, project_id=self.project_domain_id) self.useFixture(ec2_two) ec2_list = self.client.ec2.list(user_one.id) # All ec2 are valid for ec2 in ec2_list: self.check_ec2(ec2) self.assertIn(ec2_one.entity, ec2_list) self.assertNotIn(ec2_two.entity, ec2_list) def test_delete_ec2(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) ec2 = self.client.ec2.create(user.id, project.id) self.client.ec2.delete(user.id, ec2.access) self.assertRaises(http.NotFound, self.client.ec2.get, user.id, ec2.access) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_endpoint_filters.py0000664000175000017500000000656300000000000032075 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures from keystoneclient.tests.functional.v3 import test_endpoint_groups from keystoneclient.tests.functional.v3 import test_projects class EndpointFiltersTestCase(base.V3ClientTestCase, test_endpoint_groups.EndpointGroupsTestMixin, test_projects.ProjectsTestMixin): def setUp(self): super(EndpointFiltersTestCase, self).setUp() self.project = fixtures.Project(self.client) self.endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(self.project) self.useFixture(self.endpoint_group) self.client.endpoint_filter.add_endpoint_group_to_project( self.endpoint_group, self.project) def test_add_endpoint_group_to_project(self): project = fixtures.Project(self.client) endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(project) self.useFixture(endpoint_group) self.client.endpoint_filter.add_endpoint_group_to_project( endpoint_group, project) self.client.endpoint_filter.check_endpoint_group_in_project( endpoint_group, project) def test_delete_endpoint_group_from_project(self): self.client.endpoint_filter.delete_endpoint_group_from_project( self.endpoint_group, self.project) self.assertRaises( http.NotFound, self.client.endpoint_filter.check_endpoint_group_in_project, self.endpoint_group, self.project) def test_list_endpoint_groups_for_project(self): endpoint_group_two = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_two) self.client.endpoint_filter.add_endpoint_group_to_project( endpoint_group_two, self.project) endpoint_groups = ( self.client.endpoint_filter.list_endpoint_groups_for_project( self.project ) ) for endpoint_group in endpoint_groups: self.check_endpoint_group(endpoint_group) self.assertIn(self.endpoint_group.entity, endpoint_groups) self.assertIn(endpoint_group_two.entity, endpoint_groups) def test_list_projects_for_endpoint_group(self): project_two = fixtures.Project(self.client) self.useFixture(project_two) self.client.endpoint_filter.add_endpoint_group_to_project( self.endpoint_group, project_two) f = self.client.endpoint_filter.list_projects_for_endpoint_group projects = f(self.endpoint_group) for project in projects: self.check_project(project) self.assertIn(self.project.entity, projects) self.assertIn(project_two.entity, projects) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_endpoint_groups.py0000664000175000017500000001160100000000000031731 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EndpointGroupsTestMixin(object): def check_endpoint_group(self, endpoint_group, endpoint_group_ref=None): self.assertIsNotNone(endpoint_group.id) self.assertIn('self', endpoint_group.links) self.assertIn('/endpoint_groups/' + endpoint_group.id, endpoint_group.links['self']) if endpoint_group_ref: self.assertEqual(endpoint_group_ref['name'], endpoint_group.name) self.assertEqual(endpoint_group_ref['filters'], endpoint_group.filters) # There is no guarantee description is present in endpoint groups if hasattr(endpoint_group_ref, 'description'): self.assertEqual(endpoint_group_ref['description'], endpoint_group.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(endpoint_group.name) self.assertIsNotNone(endpoint_group.filters) class EndpointGroupsTestCase(base.V3ClientTestCase, EndpointGroupsTestMixin): def test_create_endpoint_group(self): endpoint_group_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'filters': {'interface': 'internal'}, 'description': uuid.uuid4().hex} endpoint_group = self.client.endpoint_groups.create( **endpoint_group_ref) self.addCleanup(self.client.endpoint_groups.delete, endpoint_group) self.check_endpoint_group(endpoint_group, endpoint_group_ref) def test_get_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) endpoint_ret = self.client.endpoint_groups.get(endpoint_group.id) self.check_endpoint_group(endpoint_ret, endpoint_group.ref) self.assertRaises(http.NotFound, self.client.endpoint_groups.get, uuid.uuid4().hex) def test_check_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) self.client.endpoint_groups.check(endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.check, uuid.uuid4().hex) def test_list_endpoint_groups(self): endpoint_group_one = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_one) endpoint_group_two = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group_two) endpoint_groups = self.client.endpoint_groups.list() # All endpoints are valid for endpoint_group in endpoint_groups: self.check_endpoint_group(endpoint_group) self.assertIn(endpoint_group_one.entity, endpoint_groups) self.assertIn(endpoint_group_two.entity, endpoint_groups) def test_update_endpoint_group(self): endpoint_group = fixtures.EndpointGroup(self.client) self.useFixture(endpoint_group) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_filters = {'interface': 'public'} new_description = uuid.uuid4().hex endpoint_group_ret = self.client.endpoint_groups.update( endpoint_group, name=new_name, filters=new_filters, description=new_description) endpoint_group.ref.update({'name': new_name, 'filters': new_filters, 'description': new_description}) self.check_endpoint_group(endpoint_group_ret, endpoint_group.ref) def test_delete_endpoint_group(self): endpoint_group = self.client.endpoint_groups.create( name=fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, filters={'interface': 'admin'}, description=uuid.uuid4().hex) self.client.endpoint_groups.delete(endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.check, endpoint_group.id) self.assertRaises(http.NotFound, self.client.endpoint_groups.get, endpoint_group.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_endpoints.py0000664000175000017500000001310100000000000030512 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class EndpointsTestCase(base.V3ClientTestCase): def check_endpoint(self, endpoint, endpoint_ref=None): self.assertIsNotNone(endpoint.id) self.assertIn('self', endpoint.links) self.assertIn('/endpoints/' + endpoint.id, endpoint.links['self']) if endpoint_ref: self.assertEqual(endpoint_ref['service'], endpoint.service_id) self.assertEqual(endpoint_ref['url'], endpoint.url) self.assertEqual(endpoint_ref['interface'], endpoint.interface) self.assertEqual(endpoint_ref['enabled'], endpoint.enabled) # There is no guarantee below attributes are present in endpoint if hasattr(endpoint_ref, 'region'): self.assertEqual(endpoint_ref['region'], endpoint.region) else: # Only check remaining mandatory attributes self.assertIsNotNone(endpoint.service_id) self.assertIsNotNone(endpoint.url) self.assertIsNotNone(endpoint.interface) self.assertIsNotNone(endpoint.enabled) def test_create_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) endpoint_ref = {'service': service.id, 'url': 'http://' + uuid.uuid4().hex, 'enabled': True, 'interface': 'public'} endpoint = self.client.endpoints.create(**endpoint_ref) self.addCleanup(self.client.endpoints.delete, endpoint) self.check_endpoint(endpoint, endpoint_ref) def test_get_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) interfaces = ['public', 'admin', 'internal'] for interface in interfaces: endpoint = fixtures.Endpoint(self.client, service.id, interface) self.useFixture(endpoint) endpoint_ret = self.client.endpoints.get(endpoint.id) # All endpoints are valid self.check_endpoint(endpoint_ret, endpoint.ref) def test_list_endpoints(self): service = fixtures.Service(self.client) self.useFixture(service) region = fixtures.Region(self.client) self.useFixture(region) endpoint_one = fixtures.Endpoint(self.client, service.id, 'public', region=region.id) self.useFixture(endpoint_one) endpoint_two = fixtures.Endpoint(self.client, service.id, 'admin', region=region.id) self.useFixture(endpoint_two) endpoint_three = fixtures.Endpoint(self.client, service.id, 'internal', region=region.id) self.useFixture(endpoint_three) endpoints = self.client.endpoints.list() # All endpoints are valid for endpoint in endpoints: self.check_endpoint(endpoint) self.assertIn(endpoint_one.entity, endpoints) self.assertIn(endpoint_two.entity, endpoints) self.assertIn(endpoint_three.entity, endpoints) def test_update_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) new_service = fixtures.Service(self.client) self.useFixture(new_service) new_region = fixtures.Region(self.client) self.useFixture(new_region) endpoint = fixtures.Endpoint(self.client, service.id, 'public') self.useFixture(endpoint) new_url = 'http://' + uuid.uuid4().hex new_interface = 'internal' new_enabled = False endpoint_ret = self.client.endpoints.update(endpoint.id, service=new_service.id, url=new_url, interface=new_interface, enabled=new_enabled, region=new_region.id) endpoint.ref.update({'service': new_service.id, 'url': new_url, 'interface': new_interface, 'enabled': new_enabled, 'region': new_region.entity}) self.check_endpoint(endpoint_ret, endpoint.ref) def test_delete_endpoint(self): service = fixtures.Service(self.client) self.useFixture(service) endpoint = self.client.endpoints.create(service=service.id, url='http://' + uuid.uuid4().hex, enabled=True, interface='public') self.client.endpoints.delete(endpoint.id) self.assertRaises(http.NotFound, self.client.endpoints.get, endpoint.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_federation.py0000664000175000017500000001003400000000000030631 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.exceptions import http from keystoneclient.tests.functional import base class TestIdentityProviders(base.V3ClientTestCase): def test_idp_create(self): idp_id = uuid.uuid4().hex # Create an identity provider just passing an ID idp = self.client.federation.identity_providers.create(id=idp_id) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertEqual([], idp.remote_ids) self.assertFalse(idp.enabled) def test_idp_create_enabled_true(self): # Create an enabled identity provider idp_id = uuid.uuid4().hex idp = self.client.federation.identity_providers.create( id=idp_id, enabled=True) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertEqual([], idp.remote_ids) self.assertTrue(idp.enabled) def test_idp_create_with_remote_ids(self): # Create an enabled identity provider with remote IDs idp_id = uuid.uuid4().hex remote_ids = [uuid.uuid4().hex, uuid.uuid4().hex] idp = self.client.federation.identity_providers.create( id=idp_id, enabled=True, remote_ids=remote_ids) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) self.assertEqual(idp_id, idp.id) self.assertIn(remote_ids[0], idp.remote_ids) self.assertIn(remote_ids[1], idp.remote_ids) self.assertTrue(idp.enabled) def test_idp_list(self): idp_ids = [] for _ in range(3): idp_id = uuid.uuid4().hex self.client.federation.identity_providers.create(id=idp_id) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) idp_ids.append(idp_id) idp_list = self.client.federation.identity_providers.list() fetched_ids = [fetched_idp.id for fetched_idp in idp_list] for idp_id in idp_ids: self.assertIn(idp_id, fetched_ids) def test_idp_get(self): idp_id = uuid.uuid4().hex remote_ids = [uuid.uuid4().hex, uuid.uuid4().hex] idp_create = self.client.federation.identity_providers.create( id=idp_id, enabled=True, remote_ids=remote_ids) self.addCleanup( self.client.federation.identity_providers.delete, idp_id) idp_get = self.client.federation.identity_providers.get(idp_id) self.assertEqual(idp_create.id, idp_get.id) self.assertEqual(idp_create.enabled, idp_get.enabled) self.assertIn(idp_create.remote_ids[0], idp_get.remote_ids) self.assertIn(idp_create.remote_ids[1], idp_get.remote_ids) def test_idp_delete(self): idp_id = uuid.uuid4().hex self.client.federation.identity_providers.create(id=idp_id) # Get should not raise an error self.client.federation.identity_providers.get(idp_id) # Delete it self.client.federation.identity_providers.delete(idp_id) # Now get should raise an error self.assertRaises( http.NotFound, self.client.federation.identity_providers.get, idp_id) # Should not be present in the identity provider list idp_list = self.client.federation.identity_providers.list() fetched_ids = [fetched_idp.id for fetched_idp in idp_list] self.assertNotIn(idp_id, fetched_ids) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_groups.py0000664000175000017500000000663200000000000030041 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class GroupsTestCase(base.V3ClientTestCase): def check_group(self, group, group_ref=None): self.assertIsNotNone(group.id) self.assertIn('self', group.links) self.assertIn('/groups/' + group.id, group.links['self']) if group_ref: self.assertEqual(group_ref['name'], group.name) self.assertEqual(group_ref['domain'], group.domain_id) # There is no guarantee description is present in group if hasattr(group_ref, 'description'): self.assertEqual(group_ref['description'], group.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(group.name) self.assertIsNotNone(group.domain_id) def test_create_group(self): group_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'description': uuid.uuid4().hex} group = self.client.groups.create(**group_ref) self.addCleanup(self.client.groups.delete, group) self.check_group(group, group_ref) def test_get_group(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) group_ret = self.client.groups.get(group.id) self.check_group(group_ret, group.ref) def test_list_groups(self): group_one = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group_one) group_two = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group_two) groups = self.client.groups.list() # All groups are valid for group in groups: self.check_group(group) self.assertIn(group_one.entity, groups) self.assertIn(group_two.entity, groups) def test_update_group(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_description = uuid.uuid4().hex group_ret = self.client.groups.update(group.id, name=new_name, description=new_description) group.ref.update({'name': new_name, 'description': new_description}) self.check_group(group_ret, group.ref) def test_delete_group(self): group = self.client.groups.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.groups.delete(group.id) self.assertRaises(http.NotFound, self.client.groups.get, group.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_implied_roles.py0000664000175000017500000000516100000000000031345 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 keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures role_defs = ["test_admin", "test_id_manager", "test_resource_manager", "test_role_manager", "test_assignment_manager", "test_domain_manager", "test_project_manager", "test_catalog_manager", "test_policy_manager", "test_observer", "test_domain_tech_lead", "test_project_observer", "test_member"] inference_rules = {"test_admin": "test_policy_manager", "test_id_manager": "test_project_observer", "test_resource_manager": "test_project_observer", "test_role_manager": "test_project_observer", "test_catalog_manager": "test_project_observer", "test_policy_manager": "test_project_observer", "test_project_observer": "test_observer", "test_member": "test_observer"} class TestImpliedRoles(base.V3ClientTestCase): def setUp(self): super(TestImpliedRoles, self).setUp() def test_implied_roles(self): initial_rule_count = ( len(self.client.inference_rules.list_inference_roles())) self.create_roles() self.create_rules() rule_count = len(self.client.inference_rules.list_inference_roles()) self.assertEqual(initial_rule_count + len(inference_rules), rule_count) def role_dict(self): roles = {role.name: role.id for role in self.client.roles.list()} return roles def create_roles(self): for role_def in role_defs: role = fixtures.Role(self.client, name=role_def) self.useFixture(role) def create_rules(self): roles = self.role_dict() for prior, implied in inference_rules.items(): rule = fixtures.InferenceRule(self.client, roles[prior], roles[implied]) self.useFixture(rule) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_policies.py0000664000175000017500000000605000000000000030323 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class PoliciesTestCase(base.V3ClientTestCase): def check_policy(self, policy, policy_ref=None): self.assertIsNotNone(policy.id) self.assertIn('self', policy.links) self.assertIn('/policies/' + policy.id, policy.links['self']) if policy_ref: self.assertEqual(policy_ref['blob'], policy.blob) self.assertEqual(policy_ref['type'], policy.type) else: # Only check remaining mandatory attributes self.assertIsNotNone(policy.blob) self.assertIsNotNone(policy.type) def test_create_policy(self): policy_ref = {'blob': uuid.uuid4().hex, 'type': uuid.uuid4().hex} policy = self.client.policies.create(**policy_ref) self.addCleanup(self.client.policies.delete, policy) self.check_policy(policy, policy_ref) def test_get_policy(self): policy = fixtures.Policy(self.client) self.useFixture(policy) policy_ret = self.client.policies.get(policy.id) self.check_policy(policy_ret, policy.ref) def test_list_policies(self): policy_one = fixtures.Policy(self.client) self.useFixture(policy_one) policy_two = fixtures.Policy(self.client) self.useFixture(policy_two) policies = self.client.policies.list() # All policies are valid for policy in policies: self.check_policy(policy) self.assertIn(policy_one.entity, policies) self.assertIn(policy_two.entity, policies) def test_update_policy(self): policy = fixtures.Policy(self.client) self.useFixture(policy) new_blob = uuid.uuid4().hex new_type = uuid.uuid4().hex policy_ret = self.client.policies.update(policy.id, blob=new_blob, type=new_type) policy.ref.update({'blob': new_blob, 'type': new_type}) self.check_policy(policy_ret, policy.ref) def test_delete_policy(self): policy = self.client.policies.create(blob=uuid.uuid4().hex, type=uuid.uuid4().hex) self.client.policies.delete(policy.id) self.assertRaises(http.NotFound, self.client.policies.get, policy.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_projects.py0000664000175000017500000004262700000000000030357 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.exceptions import http from keystoneclient import exceptions from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class ProjectsTestMixin(object): def check_project(self, project, project_ref=None): self.assertIsNotNone(project.id) self.assertIn('self', project.links) self.assertIn('/projects/' + project.id, project.links['self']) if project_ref: self.assertEqual(project_ref['name'], project.name) self.assertEqual(project_ref['domain'], project.domain_id) self.assertEqual(project_ref['enabled'], project.enabled) # There is no guarantee the attributes below are present in project if hasattr(project_ref, 'description'): self.assertEqual(project_ref['description'], project.description) if hasattr(project_ref, 'parent'): self.assertEqual(project_ref['parent'], project.parent) else: # Only check remaining mandatory attributes self.assertIsNotNone(project.name) self.assertIsNotNone(project.domain_id) self.assertIsNotNone(project.enabled) class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin): def setUp(self): super(ProjectsTestCase, self).setUp() self.test_domain = fixtures.Domain(self.client) self.useFixture(self.test_domain) self.test_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(self.test_project) self.special_tag = '~`!@#$%^&*()-_+=<>.? \'"' def test_create_subproject(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'parent': self.test_project.id} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) self.check_project(project, project_ref) def test_create_project(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) self.check_project(project, project_ref) def test_get_project(self): project_ret = self.client.projects.get(self.test_project.id) self.check_project(project_ret, self.test_project.ref) def test_get_project_invalid_params(self): self.assertRaises(exceptions.ValidationError, self.client.projects.get, self.test_project.id, subtree_as_list=True, subtree_as_ids=True) self.assertRaises(exceptions.ValidationError, self.client.projects.get, self.test_project.id, parents_as_list=True, parents_as_ids=True) def test_get_hierarchy_as_list(self): project = fixtures.Project(self.client, self.test_domain.id, parent=self.test_project.id) self.useFixture(project) child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) # Only parents and subprojects that the current user has role # assingments on are returned when asked for subtree_as_list and # parents_as_list. role = fixtures.Role(self.client) self.useFixture(role) self.client.roles.grant(role.id, user=self.user_id, project=self.test_project.id) self.client.roles.grant(role.id, user=self.user_id, project=project.id) self.client.roles.grant(role.id, user=self.user_id, project=child_project.id) project_ret = self.client.projects.get(project.id, subtree_as_list=True, parents_as_list=True) self.check_project(project_ret, project.ref) self.assertCountEqual( [{'project': self.test_project.entity.to_dict()}], project_ret.parents) self.assertCountEqual( [{'project': child_project.entity.to_dict()}], project_ret.subtree) def test_get_hierarchy_as_ids(self): project = fixtures.Project(self.client, self.test_domain.id, parent=self.test_project.id) self.useFixture(project) child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) project_ret = self.client.projects.get(project.id, subtree_as_ids=True, parents_as_ids=True) self.assertCountEqual([self.test_project.id], project_ret.parents) self.assertCountEqual([child_project.id], project_ret.subtree) def test_list_projects(self): project_one = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_one) project_two = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_two) projects = self.client.projects.list() # All projects are valid for project in projects: self.check_project(project) self.assertIn(project_one.entity, projects) self.assertIn(project_two.entity, projects) def test_list_subprojects(self): parent_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(parent_project) child_project_one = fixtures.Project(self.client, self.test_domain.id, parent=parent_project.id) self.useFixture(child_project_one) child_project_two = fixtures.Project(self.client, self.test_domain.id, parent=parent_project.id) self.useFixture(child_project_two) projects = self.client.projects.list(parent=parent_project.id) # All projects are valid for project in projects: self.check_project(project) self.assertIn(child_project_one.entity, projects) self.assertIn(child_project_two.entity, projects) # Parent project should not be included in the result self.assertNotIn(parent_project.entity, projects) def test_update_project(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_description = uuid.uuid4().hex project_ret = self.client.projects.update(project.id, name=new_name, enabled=False, description=new_description) project.ref.update({'name': new_name, 'enabled': False, 'description': new_description}) self.check_project(project_ret, project.ref) def test_update_project_domain_not_allowed(self): domain = fixtures.Domain(self.client) self.useFixture(domain) # Cannot update domain after project is created. self.assertRaises(http.BadRequest, self.client.projects.update, self.test_project.id, domain=domain.id) def test_delete_project(self): project = self.client.projects.create(name=uuid.uuid4().hex, domain=self.test_domain.id, enabled=True) self.client.projects.delete(project.id) self.assertRaises(http.NotFound, self.client.projects.get, project.id) def test_list_projects_with_tag_filters(self): project_one = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) project_two = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', 'tag2']) project_three = fixtures.Project( self.client, self.test_domain.id, tags=['tag2', 'tag3']) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) projects = self.client.projects.list(tags='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertIn(project_one.id, project_ids) projects = self.client.projects.list(tags_any='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertIn(project_one.id, project_ids) self.assertIn(project_two.id, project_ids) projects = self.client.projects.list(not_tags='tag1') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) projects = self.client.projects.list(not_tags_any='tag1,tag2') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) self.assertNotIn(project_two.id, project_ids) self.assertNotIn(project_three.id, project_ids) projects = self.client.projects.list(tags='tag1,tag2') project_ids = [] for project in projects: project_ids.append(project.id) self.assertNotIn(project_one.id, project_ids) self.assertIn(project_two.id, project_ids) self.assertNotIn(project_three.id, project_ids) def test_add_tag(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) project.add_tag('tag1') tags = self.client.projects.get(project.id).tags self.assertEqual(['tag1'], tags) # verify there is an error when you try to add the same tag self.assertRaises(http.BadRequest, project.add_tag, 'tag1') def test_update_tags(self): project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) project.update_tags(['tag1', 'tag2', self.special_tag]) tags = self.client.projects.get(project.id).tags self.assertIn('tag1', tags) self.assertIn('tag2', tags) self.assertIn(self.special_tag, tags) self.assertEqual(3, len(tags)) project.update_tags([]) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) # cannot have duplicate tags in update self.assertRaises(http.BadRequest, project.update_tags, ['tag1', 'tag1']) def test_delete_tag(self): project = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', self.special_tag]) self.useFixture(project) project.delete_tag('tag1') tags = self.client.projects.get(project.id).tags self.assertEqual([self.special_tag], tags) project.delete_tag(self.special_tag) tags = self.client.projects.get(project.id).tags self.assertEqual([], tags) def test_delete_all_tags(self): project_one = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) project_two = fixtures.Project( self.client, self.test_domain.id, tags=['tag1', 'tag2', self.special_tag]) project_three = fixtures.Project( self.client, self.test_domain.id, tags=[]) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) result_one = project_one.delete_all_tags() tags_one = self.client.projects.get(project_one.id).tags tags_two = self.client.projects.get(project_two.id).tags self.assertEqual([], result_one) self.assertEqual([], tags_one) self.assertIn('tag1', tags_two) result_two = project_two.delete_all_tags() tags_two = self.client.projects.get(project_two.id).tags self.assertEqual([], result_two) self.assertEqual([], tags_two) result_three = project_three.delete_all_tags() tags_three = self.client.projects.get(project_three.id).tags self.assertEqual([], result_three) self.assertEqual([], tags_three) def test_list_tags(self): tags_one = ['tag1'] project_one = fixtures.Project( self.client, self.test_domain.id, tags=tags_one) tags_two = ['tag1', 'tag2'] project_two = fixtures.Project( self.client, self.test_domain.id, tags=tags_two) tags_three = [] project_three = fixtures.Project( self.client, self.test_domain.id, tags=tags_three) self.useFixture(project_one) self.useFixture(project_two) self.useFixture(project_three) result_one = project_one.list_tags() result_two = project_two.list_tags() result_three = project_three.list_tags() for tag in tags_one: self.assertIn(tag, result_one) self.assertEqual(1, len(result_one)) for tag in tags_two: self.assertIn(tag, result_two) self.assertEqual(2, len(result_two)) for tag in tags_three: self.assertIn(tag, result_three) self.assertEqual(0, len(result_three)) def test_check_tag(self): project = fixtures.Project( self.client, self.test_domain.id, tags=['tag1']) self.useFixture(project) tags = self.client.projects.get(project.id).tags self.assertEqual(['tag1'], tags) self.assertTrue(project.check_tag('tag1')) self.assertFalse(project.check_tag('tag2')) self.assertFalse(project.check_tag(self.special_tag)) def test_add_invalid_tags(self): project_one = fixtures.Project( self.client, self.test_domain.id) self.useFixture(project_one) self.assertRaises(exceptions.BadRequest, project_one.add_tag, ',') self.assertRaises(exceptions.BadRequest, project_one.add_tag, '/') self.assertRaises(exceptions.BadRequest, project_one.add_tag, '') def test_update_invalid_tags(self): tags_comma = ['tag1', ','] tags_slash = ['tag1', '/'] tags_blank = ['tag1', ''] project_one = fixtures.Project( self.client, self.test_domain.id) self.useFixture(project_one) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_comma) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_slash) self.assertRaises(exceptions.BadRequest, project_one.update_tags, tags_blank) def test_create_project_invalid_tags(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': ','} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': '/'} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, 'tags': ''} self.assertRaises(exceptions.BadRequest, self.client.projects.create, **project_ref) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_regions.py0000664000175000017500000000575300000000000030173 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class RegionsTestCase(base.V3ClientTestCase): def check_region(self, region, region_ref=None): self.assertIsNotNone(region.id) self.assertIn('self', region.links) self.assertIn('/regions/' + region.id, region.links['self']) # There is no guarantee the below attributes are present in region if hasattr(region_ref, 'description'): self.assertEqual(region_ref['description'], region.description) if hasattr(region_ref, 'parent_region'): self.assertEqual( region_ref['parent_region'], region.parent_region) def test_create_region(self): region_ref = {'description': uuid.uuid4().hex} region = self.client.regions.create(**region_ref) self.addCleanup(self.client.regions.delete, region) self.check_region(region, region_ref) def test_get_region(self): region = fixtures.Region(self.client) self.useFixture(region) region_ret = self.client.regions.get(region.id) self.check_region(region_ret, region.ref) def test_list_regions(self): region_one = fixtures.Region(self.client) self.useFixture(region_one) region_two = fixtures.Region(self.client, parent_region=region_one.id) self.useFixture(region_two) regions = self.client.regions.list() # All regions are valid for region in regions: self.check_region(region) self.assertIn(region_one.entity, regions) self.assertIn(region_two.entity, regions) def test_update_region(self): parent = fixtures.Region(self.client) self.useFixture(parent) region = fixtures.Region(self.client) self.useFixture(region) new_description = uuid.uuid4().hex region_ret = self.client.regions.update(region.id, description=new_description, parent_region=parent.id) self.check_region(region_ret, region.ref) def test_delete_region(self): region = self.client.regions.create() self.client.regions.delete(region.id) self.assertRaises(http.NotFound, self.client.regions.get, region.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_roles.py0000664000175000017500000002177000000000000027646 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.exceptions import http from keystoneclient import exceptions from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class RolesTestCase(base.V3ClientTestCase): def check_role(self, role, role_ref=None): self.assertIsNotNone(role.id) self.assertIn('self', role.links) self.assertIn('/roles/' + role.id, role.links['self']) if role_ref: self.assertEqual(role_ref['name'], role.name) # There is no guarantee domain is present in role if hasattr(role_ref, 'domain'): self.assertEqual(role_ref['domain'], role.domain_id) else: # Only check remaining mandatory attribute self.assertIsNotNone(role.name) def test_create_role(self): role_ref = {'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex} role = self.client.roles.create(**role_ref) self.addCleanup(self.client.roles.delete, role) self.check_role(role, role_ref) def test_create_domain_role(self): role_ref = {'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id} role = self.client.roles.create(**role_ref) self.addCleanup(self.client.roles.delete, role) self.check_role(role, role_ref) def test_get_role(self): role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) role_ret = self.client.roles.get(role.id) self.check_role(role_ret, role.ref) def test_update_role_name(self): role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex role_ret = self.client.roles.update(role.id, name=new_name) role.ref.update({'name': new_name}) self.check_role(role_ret, role.ref) def test_update_role_domain(self): role = fixtures.Role(self.client) self.useFixture(role) domain = fixtures.Domain(self.client) self.useFixture(domain) new_domain = domain.id role_ret = self.client.roles.update(role.id, domain=new_domain) role.ref.update({'domain': new_domain}) self.check_role(role_ret, role.ref) def test_list_roles_invalid_params(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) # Only filter in role grants for a user on a resource. # Domain or project should be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.list, user=user.id) # Only filter in role grants for a group on a resource. # Domain or project should be specified. group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) self.assertRaises(exceptions.ValidationError, self.client.roles.list, group=group.id) def test_list_roles(self): global_role = fixtures.Role(self.client) self.useFixture(global_role) domain = fixtures.Domain(self.client) self.useFixture(domain) domain_role = fixtures.Role(self.client, domain=domain.id) self.useFixture(domain_role) global_roles = self.client.roles.list() domain_roles = self.client.roles.list(domain_id=domain.id) roles = global_roles + domain_roles # All roles are valid for role in roles: self.check_role(role) self.assertIn(global_role.entity, global_roles) self.assertIn(domain_role.entity, domain_roles) def test_delete_role(self): role = self.client.roles.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.roles.delete(role.id) self.assertRaises(http.NotFound, self.client.roles.get, role.id) def test_grant_role_invalid_params(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) # Only grant role to a group on a resource. # Domain or project must be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.grant, role.id, user=user.id) group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) # Only grant role to a group on a resource. # Domain or project must be specified. self.assertRaises(exceptions.ValidationError, self.client.roles.grant, role.id, group=group.id) def test_user_domain_grant_and_revoke(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) domain = fixtures.Domain(self.client) self.useFixture(domain) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, user=user.id, domain=domain.id) roles_after_grant = self.client.roles.list(user=user.id, domain=domain.id) self.assertCountEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, user=user.id, domain=domain.id) roles_after_revoke = self.client.roles.list(user=user.id, domain=domain.id) self.assertEqual(roles_after_revoke, []) def test_user_project_grant_and_revoke(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, user=user.id, project=project.id) roles_after_grant = self.client.roles.list(user=user.id, project=project.id) self.assertCountEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, user=user.id, project=project.id) roles_after_revoke = self.client.roles.list(user=user.id, project=project.id) self.assertEqual(roles_after_revoke, []) def test_group_domain_grant_and_revoke(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) domain = fixtures.Domain(self.client) self.useFixture(domain) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, group=group.id, domain=domain.id) roles_after_grant = self.client.roles.list(group=group.id, domain=domain.id) self.assertCountEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, group=group.id, domain=domain.id) roles_after_revoke = self.client.roles.list(group=group.id, domain=domain.id) self.assertEqual(roles_after_revoke, []) def test_group_project_grant_and_revoke(self): group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(group) project = fixtures.Project(self.client, self.project_domain_id) self.useFixture(project) role = fixtures.Role(self.client, domain=self.project_domain_id) self.useFixture(role) self.client.roles.grant(role, group=group.id, project=project.id) roles_after_grant = self.client.roles.list(group=group.id, project=project.id) self.assertCountEqual(roles_after_grant, [role.entity]) self.client.roles.revoke(role, group=group.id, project=project.id) roles_after_revoke = self.client.roles.list(group=group.id, project=project.id) self.assertEqual(roles_after_revoke, []) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_services.py0000664000175000017500000000764400000000000030351 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class ServicesTestCase(base.V3ClientTestCase): def check_service(self, service, service_ref=None): self.assertIsNotNone(service.id) self.assertIn('self', service.links) self.assertIn('/services/' + service.id, service.links['self']) if service_ref: self.assertEqual(service_ref['name'], service.name) self.assertEqual(service_ref['enabled'], service.enabled) self.assertEqual(service_ref['type'], service.type) # There is no guarantee description is present in service if hasattr(service_ref, 'description'): self.assertEqual(service_ref['description'], service.description) else: # Only check remaining mandatory attributes self.assertIsNotNone(service.name) self.assertIsNotNone(service.enabled) self.assertIsNotNone(service.type) def test_create_service(self): service_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'type': uuid.uuid4().hex, 'enabled': True, 'description': uuid.uuid4().hex} service = self.client.services.create(**service_ref) self.addCleanup(self.client.services.delete, service) self.check_service(service, service_ref) def test_get_service(self): service = fixtures.Service(self.client) self.useFixture(service) service_ret = self.client.services.get(service.id) self.check_service(service_ret, service.ref) def test_list_services(self): service_one = fixtures.Service(self.client) self.useFixture(service_one) service_two = fixtures.Service(self.client) self.useFixture(service_two) services = self.client.services.list() # All services are valid for service in services: self.check_service(service) self.assertIn(service_one.entity, services) self.assertIn(service_two.entity, services) def test_update_service(self): service = fixtures.Service(self.client) self.useFixture(service) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex new_type = uuid.uuid4().hex new_enabled = False new_description = uuid.uuid4().hex service_ret = self.client.services.update(service.id, name=new_name, type=new_type, enabled=new_enabled, description=new_description) service.ref.update({'name': new_name, 'type': new_type, 'enabled': new_enabled, 'description': new_description}) self.check_service(service_ret, service.ref) def test_delete_service(self): service = self.client.services.create(name=uuid.uuid4().hex, type=uuid.uuid4().hex) self.client.services.delete(service.id) self.assertRaises(http.NotFound, self.client.services.get, service.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/functional/v3/test_users.py0000664000175000017500000001235600000000000027663 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.exceptions import http from keystoneclient.tests.functional import base from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class UsersTestCase(base.V3ClientTestCase): def check_user(self, user, user_ref=None): self.assertIsNotNone(user.id) self.assertIsNotNone(user.enabled) self.assertIn('self', user.links) self.assertIn('/users/' + user.id, user.links['self']) if user_ref: self.assertEqual(user_ref['name'], user.name) self.assertEqual(user_ref['domain'], user.domain_id) # There is no guarantee the attributes below are present in user if hasattr(user_ref, 'description'): self.assertEqual(user_ref['description'], user.description) if hasattr(user_ref, 'email'): self.assertEqual(user_ref['email'], user.email) if hasattr(user_ref, 'default_project'): self.assertEqual(user_ref['default_project'], user.default_project_id) else: # Only check remaining mandatory attributes self.assertIsNotNone(user.name) self.assertIsNotNone(user.domain_id) def test_create_user(self): user_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user = self.client.users.create(**user_ref) self.addCleanup(self.client.users.delete, user) self.check_user(user, user_ref) def test_get_user(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) user_ret = self.client.users.get(user.id) self.check_user(user_ret, user.ref) def test_list_users(self): user_one = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_one) user_two = fixtures.User(self.client, self.project_domain_id) self.useFixture(user_two) users = self.client.users.list() # All users are valid for user in users: self.check_user(user) self.assertIn(user_one.entity, users) self.assertIn(user_two.entity, users) def test_list_users_with_filters(self): suffix = uuid.uuid4().hex user1_ref = { 'name': 'test_user' + suffix, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user2_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, 'domain': self.project_domain_id, 'default_project': self.project_id, 'password': uuid.uuid4().hex, 'description': uuid.uuid4().hex} user1 = self.client.users.create(**user1_ref) self.client.users.create(**user2_ref) users = self.client.users.list(name__contains=['test_user', suffix]) self.assertEqual(1, len(users)) self.assertIn(user1, users) def test_update_user(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) new_description = uuid.uuid4().hex user_ret = self.client.users.update(user.id, description=new_description) user.ref.update({'description': new_description}) self.check_user(user_ret, user.ref) def test_user_grouping(self): # keystoneclient.v3.users owns user grouping operations, this is why # this test case belongs to this class user = fixtures.User(self.client, self.project_domain_id) group = fixtures.Group(self.client, self.project_domain_id) self.useFixture(user) self.useFixture(group) self.assertRaises(http.NotFound, self.client.users.check_in_group, user.id, group.id) self.client.users.add_to_group(user.id, group.id) self.client.users.check_in_group(user.id, group.id) self.client.users.remove_from_group(user.id, group.id) self.assertRaises(http.NotFound, self.client.users.check_in_group, user.id, group.id) def test_delete_user(self): user = self.client.users.create(name=uuid.uuid4().hex, domain=self.project_domain_id) self.client.users.delete(user.id) self.assertRaises(http.NotFound, self.client.users.get, user.id) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2270036 python_keystoneclient-5.6.0/keystoneclient/tests/unit/0000775000175000017500000000000000000000000023367 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/__init__.py0000664000175000017500000000000000000000000025466 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2270036 python_keystoneclient-5.6.0/keystoneclient/tests/unit/apiclient/0000775000175000017500000000000000000000000025337 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/apiclient/__init__.py0000664000175000017500000000000000000000000027436 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/apiclient/test_exceptions.py0000664000175000017500000000460200000000000031133 0ustar00zuulzuul00000000000000# Copyright 2012 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. from keystoneclient import exceptions from keystoneclient.tests.unit import utils class FakeResponse(object): json_data = {} def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def json(self): return self.json_data class ExceptionsArgsTest(utils.TestCase): def assert_exception(self, ex_cls, method, url, status_code, json_data): ex = exceptions.from_response( FakeResponse(status_code=status_code, headers={"Content-Type": "application/json"}, json_data=json_data), method, url) self.assertIsInstance(ex, ex_cls) self.assertIn(json_data["error"]["message"], ex.message) self.assertEqual(ex.details, json_data["error"]["details"]) self.assertEqual(ex.method, method) self.assertEqual(ex.url, url) self.assertEqual(ex.http_status, status_code) def test_from_response_known(self): method = "GET" url = "/fake" status_code = 400 json_data = {"error": {"message": "fake message", "details": "fake details"}} self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data) def test_from_response_unknown(self): method = "POST" url = "/fake-unknown" status_code = 499 json_data = {"error": {"message": "fake unknown message", "details": "fake unknown details"}} self.assert_exception( exceptions.HTTPClientError, method, url, status_code, json_data) status_code = 600 self.assert_exception( exceptions.HTTPError, method, url, status_code, json_data) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2310035 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/0000775000175000017500000000000000000000000024330 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/__init__.py0000664000175000017500000000000000000000000026427 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_access.py0000664000175000017500000000456400000000000027213 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 plugin from keystoneclient import access from keystoneclient.auth.identity import access as access_plugin from keystoneclient import session from keystoneclient.tests.unit import utils class AccessInfoPluginTests(utils.TestCase): def setUp(self): super(AccessInfoPluginTests, self).setUp() with self.deprecations.expect_deprecations_here(): self.session = session.Session() self.auth_token = uuid.uuid4().hex def _plugin(self, **kwargs): token = fixture.V3Token() s = token.add_service('identity') s.add_standard_endpoints(public=self.TEST_ROOT_URL) auth_ref = access.AccessInfo.factory(body=token, auth_token=self.auth_token) with self.deprecations.expect_deprecations_here(): return access_plugin.AccessInfoPlugin(auth_ref, **kwargs) def test_auth_ref(self): plugin = self._plugin() self.assertEqual(self.TEST_ROOT_URL, plugin.get_endpoint(self.session, service_type='identity', interface='public')) self.assertEqual(self.auth_token, plugin.get_token(session)) def test_auth_url(self): auth_url = 'http://keystone.test.url' plug = self._plugin(auth_url=auth_url) self.assertEqual(auth_url, plug.get_endpoint(self.session, interface=plugin.AUTH_INTERFACE)) def test_invalidate(self): plugin = self._plugin() auth_ref = plugin.auth_ref self.assertIsInstance(auth_ref, access.AccessInfo) self.assertFalse(plugin.invalidate()) self.assertIs(auth_ref, plugin.auth_ref) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_auth.py0000664000175000017500000000136000000000000026702 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 keystoneclient.tests.unit.auth import utils class AuthTests(utils.TestCase): def test_plugin_names_in_available(self): pass def test_plugin_classes_in_available(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_cli.py0000664000175000017500000001544300000000000026517 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 argparse from unittest import mock import uuid import fixtures from oslo_config import cfg from keystoneclient.auth import base from keystoneclient.auth import cli from keystoneclient.tests.unit.auth import utils class TesterPlugin(base.BaseAuthPlugin): def get_token(self, *args, **kwargs): return None @classmethod def get_options(cls): # NOTE(jamielennox): this is kind of horrible. If you specify this as # a deprecated_name= value it will convert - to _ which is not what we # want for a CLI option. deprecated = [cfg.DeprecatedOpt('test-other')] return [ cfg.StrOpt('test-opt', help='tester', deprecated_opts=deprecated) ] class CliTests(utils.TestCase): def setUp(self): super(CliTests, self).setUp() self.deprecations.expect_deprecations() self.p = argparse.ArgumentParser() def env(self, name, value=None): if value is not None: # environment variables are always strings value = str(value) return self.useFixture(fixtures.EnvironmentVariable(name, value)) def test_creating_with_no_args(self): ret = cli.register_argparse_arguments(self.p, []) self.assertIsNone(ret) self.assertIn('--os-auth-plugin', self.p.format_usage()) def test_load_with_nothing(self): cli.register_argparse_arguments(self.p, []) opts = self.p.parse_args([]) self.assertIsNone(cli.load_from_argparse_arguments(opts)) @utils.mock_plugin def test_basic_params_added(self, m): name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] ret = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, ret) for n in ('--os-a-int', '--os-a-bool', '--os-a-float'): self.assertIn(n, self.p.format_usage()) m.assert_called_once_with(name) @utils.mock_plugin def test_param_loading(self, m): name = uuid.uuid4().hex argv = ['--os-auth-plugin', name, '--os-a-int', str(self.a_int), '--os-a-float', str(self.a_float), '--os-a-bool', str(self.a_bool)] klass = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) a = cli.load_from_argparse_arguments(opts) self.assertTestVals(a) self.assertEqual(name, opts.os_auth_plugin) self.assertEqual(str(self.a_int), opts.os_a_int) self.assertEqual(str(self.a_float), opts.os_a_float) self.assertEqual(str(self.a_bool), opts.os_a_bool) @utils.mock_plugin def test_default_options(self, m): name = uuid.uuid4().hex argv = ['--os-auth-plugin', name, '--os-a-float', str(self.a_float)] klass = cli.register_argparse_arguments(self.p, argv) self.assertIs(utils.MockPlugin, klass) opts = self.p.parse_args(argv) self.assertEqual(name, opts.os_auth_plugin) a = cli.load_from_argparse_arguments(opts) self.assertEqual(self.a_float, a['a_float']) self.assertEqual(3, a['a_int']) @utils.mock_plugin def test_with_default_string_value(self, m): name = uuid.uuid4().hex klass = cli.register_argparse_arguments(self.p, [], default=name) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @utils.mock_plugin def test_overrides_default_string_value(self, m): name = uuid.uuid4().hex default = uuid.uuid4().hex argv = ['--os-auth-plugin', name] klass = cli.register_argparse_arguments(self.p, argv, default=default) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @utils.mock_plugin def test_with_default_type_value(self, m): klass = cli.register_argparse_arguments(self.p, [], default=utils.MockPlugin) self.assertIs(utils.MockPlugin, klass) self.assertEqual(0, m.call_count) @utils.mock_plugin def test_overrides_default_type_value(self, m): # using this test plugin would fail if called because there # is no get_options() function class TestPlugin(object): pass name = uuid.uuid4().hex argv = ['--os-auth-plugin', name] klass = cli.register_argparse_arguments(self.p, argv, default=TestPlugin) self.assertIs(utils.MockPlugin, klass) m.assert_called_once_with(name) @utils.mock_plugin def test_env_overrides_default_opt(self, m): name = uuid.uuid4().hex val = uuid.uuid4().hex self.env('OS_A_STR', val) klass = cli.register_argparse_arguments(self.p, [], default=name) opts = self.p.parse_args([]) a = klass.load_from_argparse_arguments(opts) self.assertEqual(val, a['a_str']) def test_deprecated_cli_options(self): TesterPlugin.register_argparse_arguments(self.p) val = uuid.uuid4().hex opts = self.p.parse_args(['--os-test-other', val]) self.assertEqual(val, opts.os_test_opt) def test_deprecated_multi_cli_options(self): TesterPlugin.register_argparse_arguments(self.p) val1 = uuid.uuid4().hex val2 = uuid.uuid4().hex # argarse rules say that the last specified wins. opts = self.p.parse_args(['--os-test-other', val2, '--os-test-opt', val1]) self.assertEqual(val1, opts.os_test_opt) def test_deprecated_env_options(self): val = uuid.uuid4().hex with mock.patch.dict('os.environ', {'OS_TEST_OTHER': val}): TesterPlugin.register_argparse_arguments(self.p) opts = self.p.parse_args([]) self.assertEqual(val, opts.os_test_opt) def test_deprecated_env_multi_options(self): val1 = uuid.uuid4().hex val2 = uuid.uuid4().hex with mock.patch.dict('os.environ', {'OS_TEST_OPT': val1, 'OS_TEST_OTHER': val2}): TesterPlugin.register_argparse_arguments(self.p) opts = self.p.parse_args([]) self.assertEqual(val1, opts.os_test_opt) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_conf.py0000664000175000017500000001037200000000000026671 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 oslo_config import fixture as config from keystoneclient.auth import base from keystoneclient.auth import conf from keystoneclient import exceptions from keystoneclient.tests.unit.auth import utils class ConfTests(utils.TestCase): def setUp(self): super(ConfTests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) # NOTE(jamielennox): we register the basic config options first because # we need them in place before we can stub them. We will need to run # the register again after we stub the auth section and auth plugin so # it can load the plugin specific options. conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) def test_loading_v2(self): pass def test_loading_v3(self): pass def test_loading_invalid_plugin(self): auth_plugin = uuid.uuid4().hex self.conf_fixture.config(auth_plugin=auth_plugin, group=self.GROUP) e = self.assertRaises(exceptions.NoMatchingPlugin, conf.load_from_conf_options, self.conf_fixture.conf, self.GROUP) self.assertEqual(auth_plugin, e.name) def test_loading_with_no_data(self): self.assertIsNone(conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP)) @mock.patch('stevedore.DriverManager') def test_other_params(self, m): m.return_value = utils.MockManager(utils.MockPlugin) driver_name = uuid.uuid4().hex self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=self.GROUP) self.conf_fixture.config(auth_plugin=driver_name, group=self.GROUP, **self.TEST_VALS) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) m.assert_called_once_with(namespace=base.PLUGIN_NAMESPACE, name=driver_name, invoke_on_load=False) @utils.mock_plugin def test_same_section(self, m): self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.config(auth_plugin=uuid.uuid4().hex, group=self.GROUP, **self.TEST_VALS) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) @utils.mock_plugin def test_diff_section(self, m): section = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(utils.MockPlugin.get_options(), group=section) self.conf_fixture.config(group=section, auth_plugin=uuid.uuid4().hex, **self.TEST_VALS) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertTestVals(a) def test_plugins_are_all_opts(self): pass def test_get_common(self): opts = conf.get_common_conf_options() for opt in opts: self.assertIsInstance(opt, cfg.Opt) self.assertEqual(2, len(opts)) def test_get_named(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_default_cli.py0000664000175000017500000000624600000000000030224 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 argparse from unittest import mock import uuid from keystoneclient.auth.identity.generic import cli from keystoneclient import exceptions from keystoneclient.tests.unit import utils class DefaultCliTests(utils.TestCase): def setUp(self): super(DefaultCliTests, self).setUp() self.deprecations.expect_deprecations() def new_plugin(self, argv): parser = argparse.ArgumentParser() cli.DefaultCLI.register_argparse_arguments(parser) opts = parser.parse_args(argv) return cli.DefaultCLI.load_from_argparse_arguments(opts) def test_endpoint_override(self): password = uuid.uuid4().hex url = uuid.uuid4().hex p = self.new_plugin(['--os-auth-url', 'url', '--os-endpoint', url, '--os-password', password]) self.assertEqual(url, p.get_endpoint(None)) self.assertEqual(password, p._password) def test_token_only_override(self): self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-token', uuid.uuid4().hex]) def test_token_endpoint_override(self): token = uuid.uuid4().hex endpoint = uuid.uuid4().hex p = self.new_plugin(['--os-endpoint', endpoint, '--os-token', token]) self.assertEqual(endpoint, p.get_endpoint(None)) self.assertEqual(token, p.get_token(None)) def test_no_auth_url(self): exc = self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-username', uuid.uuid4().hex]) self.assertIn('auth-url', str(exc)) @mock.patch('sys.stdin', autospec=True) @mock.patch('getpass.getpass') def test_prompt_password(self, mock_getpass, mock_stdin): password = uuid.uuid4().hex mock_stdin.isatty = lambda: True mock_getpass.return_value = password p = self.new_plugin(['--os-auth-url', uuid.uuid4().hex, '--os-username', uuid.uuid4().hex]) self.assertEqual(password, p._password) @mock.patch('sys.stdin', autospec=True) @mock.patch('getpass.getpass') def test_prompt_no_password(self, mock_getpass, mock_stdin): mock_stdin.isatty = lambda: True mock_getpass.return_value = '' exc = self.assertRaises(exceptions.CommandError, self.new_plugin, ['--os-auth-url', uuid.uuid4().hex, '--os-username', uuid.uuid4().hex]) self.assertIn('password', str(exc)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_identity_common.py0000664000175000017500000004231500000000000031147 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 datetime from unittest import mock import uuid from keystoneauth1 import fixture from keystoneauth1 import plugin from oslo_utils import timeutils from keystoneclient import access from keystoneclient.auth import base from keystoneclient.auth import identity from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils class CommonIdentityTests(object, metaclass=abc.ABCMeta): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_COMPUTE_PUBLIC = 'http://nova/novapi/public' TEST_COMPUTE_INTERNAL = 'http://nova/novapi/internal' TEST_COMPUTE_ADMIN = 'http://nova/novapi/admin' TEST_PASS = uuid.uuid4().hex def setUp(self): super(CommonIdentityTests, self).setUp() self.deprecations.expect_deprecations() self.TEST_URL = '%s%s' % (self.TEST_ROOT_URL, self.version) self.TEST_ADMIN_URL = '%s%s' % (self.TEST_ROOT_ADMIN_URL, self.version) self.TEST_DISCOVERY = fixture.DiscoveryList(href=self.TEST_ROOT_URL) self.stub_auth_data() @abc.abstractmethod def create_auth_plugin(self, **kwargs): """Create an auth plugin that makes sense for the auth data. It doesn't really matter what auth mechanism is used but it should be appropriate to the API version. """ pass @abc.abstractmethod def get_auth_data(self, **kwargs): """Return fake authentication data. This should register a valid token response and ensure that the compute endpoints are set to TEST_COMPUTE_PUBLIC, _INTERNAL and _ADMIN. """ pass def stub_auth_data(self, **kwargs): token = self.get_auth_data(**kwargs) self.user_id = token.user_id try: self.project_id = token.project_id except AttributeError: self.project_id = token.tenant_id self.stub_auth(json=token) @property @abc.abstractmethod def version(self): """The API version being tested.""" def test_discovering(self): self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=self.TEST_DISCOVERY) body = 'SUCCESS' # which gives our sample values self.stub_url('GET', ['path'], text=body) a = self.create_auth_plugin() s = session.Session(auth=a) resp = s.get('/path', endpoint_filter={'service_type': 'compute', 'interface': 'admin', 'version': self.version}) self.assertEqual(200, resp.status_code) self.assertEqual(body, resp.text) new_body = 'SC SUCCESS' # if we don't specify a version, we use the URL from the SC self.stub_url('GET', ['path'], base_url=self.TEST_COMPUTE_ADMIN, text=new_body) resp = s.get('/path', endpoint_filter={'service_type': 'compute', 'interface': 'admin'}) self.assertEqual(200, resp.status_code) self.assertEqual(new_body, resp.text) def test_discovery_uses_session_cache(self): # register responses such that if the discovery URL is hit more than # once then the response will be invalid and not point to COMPUTE_ADMIN resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) body = 'SUCCESS' self.stub_url('GET', ['path'], text=body) # now either of the two plugins I use, it should not cause a second # request to the discovery url. s = session.Session() a = self.create_auth_plugin() b = self.create_auth_plugin() for auth in (a, b): resp = s.get('/path', auth=auth, endpoint_filter={'service_type': 'compute', 'interface': 'admin', 'version': self.version}) self.assertEqual(200, resp.status_code) self.assertEqual(body, resp.text) def test_discovery_uses_plugin_cache(self): # register responses such that if the discovery URL is hit more than # once then the response will be invalid and not point to COMPUTE_ADMIN resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) body = 'SUCCESS' self.stub_url('GET', ['path'], text=body) # now either of the two sessions I use, it should not cause a second # request to the discovery url. sa = session.Session() sb = session.Session() auth = self.create_auth_plugin() for sess in (sa, sb): resp = sess.get('/path', auth=auth, endpoint_filter={'service_type': 'compute', 'interface': 'admin', 'version': self.version}) self.assertEqual(200, resp.status_code) self.assertEqual(body, resp.text) def test_discovering_with_no_data(self): # which returns discovery information pointing to TEST_URL but there is # no data there. self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, status_code=400) # so the url that will be used is the same TEST_COMPUTE_ADMIN body = 'SUCCESS' self.stub_url('GET', ['path'], base_url=self.TEST_COMPUTE_ADMIN, text=body, status_code=200) a = self.create_auth_plugin() s = session.Session(auth=a) resp = s.get('/path', endpoint_filter={'service_type': 'compute', 'interface': 'admin', 'version': self.version}) self.assertEqual(200, resp.status_code) self.assertEqual(body, resp.text) def test_asking_for_auth_endpoint_ignores_checks(self): a = self.create_auth_plugin() s = session.Session(auth=a) auth_url = s.get_endpoint(service_type='compute', interface=plugin.AUTH_INTERFACE) self.assertEqual(self.TEST_URL, auth_url) def _create_expired_auth_plugin(self, **kwargs): expires = timeutils.utcnow() - datetime.timedelta(minutes=20) expired_token = self.get_auth_data(expires=expires) expired_auth_ref = access.AccessInfo.factory(body=expired_token) body = 'SUCCESS' self.stub_url('GET', ['path'], base_url=self.TEST_COMPUTE_ADMIN, text=body) a = self.create_auth_plugin(**kwargs) a.auth_ref = expired_auth_ref return a def test_reauthenticate(self): a = self._create_expired_auth_plugin() expired_auth_ref = a.auth_ref s = session.Session(auth=a) self.assertIsNot(expired_auth_ref, a.get_access(s)) def test_no_reauthenticate(self): a = self._create_expired_auth_plugin(reauthenticate=False) expired_auth_ref = a.auth_ref s = session.Session(auth=a) self.assertIs(expired_auth_ref, a.get_access(s)) def test_invalidate(self): a = self.create_auth_plugin() s = session.Session(auth=a) # trigger token fetching s.get_auth_headers() self.assertTrue(a.auth_ref) self.assertTrue(a.invalidate()) self.assertIsNone(a.auth_ref) self.assertFalse(a.invalidate()) def test_get_auth_properties(self): a = self.create_auth_plugin() s = session.Session() self.assertEqual(self.user_id, a.get_user_id(s)) self.assertEqual(self.project_id, a.get_project_id(s)) class V3(CommonIdentityTests, utils.TestCase): @property def version(self): return 'v3' def get_auth_data(self, **kwargs): token = fixture.V3Token(**kwargs) region = 'RegionOne' svc = token.add_service('identity') svc.add_standard_endpoints(admin=self.TEST_ADMIN_URL, region=region) svc = token.add_service('compute') svc.add_standard_endpoints(admin=self.TEST_COMPUTE_ADMIN, public=self.TEST_COMPUTE_PUBLIC, internal=self.TEST_COMPUTE_INTERNAL, region=region) return token def stub_auth(self, subject_token=None, **kwargs): if not subject_token: subject_token = self.TEST_TOKEN kwargs.setdefault('headers', {})['X-Subject-Token'] = subject_token self.stub_url('POST', ['auth', 'tokens'], **kwargs) def create_auth_plugin(self, **kwargs): kwargs.setdefault('auth_url', self.TEST_URL) kwargs.setdefault('username', self.TEST_USER) kwargs.setdefault('password', self.TEST_PASS) return identity.V3Password(**kwargs) class V2(CommonIdentityTests, utils.TestCase): @property def version(self): return 'v2.0' def create_auth_plugin(self, **kwargs): kwargs.setdefault('auth_url', self.TEST_URL) kwargs.setdefault('username', self.TEST_USER) kwargs.setdefault('password', self.TEST_PASS) return identity.V2Password(**kwargs) def get_auth_data(self, **kwargs): token = fixture.V2Token(**kwargs) region = 'RegionOne' svc = token.add_service('identity') svc.add_endpoint(self.TEST_ADMIN_URL, region=region) svc = token.add_service('compute') svc.add_endpoint(public=self.TEST_COMPUTE_PUBLIC, internal=self.TEST_COMPUTE_INTERNAL, admin=self.TEST_COMPUTE_ADMIN, region=region) return token def stub_auth(self, **kwargs): self.stub_url('POST', ['tokens'], **kwargs) class CatalogHackTests(utils.TestCase): TEST_URL = 'http://keystone.server:5000/v2.0' OTHER_URL = 'http://other.server:5000/path' IDENTITY = 'identity' BASE_URL = 'http://keystone.server:5000/' V2_URL = BASE_URL + 'v2.0' V3_URL = BASE_URL + 'v3' def setUp(self): super(CatalogHackTests, self).setUp() self.deprecations.expect_deprecations() def test_getting_endpoints(self): disc = fixture.DiscoveryList(href=self.BASE_URL) self.stub_url('GET', ['/'], base_url=self.BASE_URL, json=disc) token = fixture.V2Token() service = token.add_service(self.IDENTITY) service.add_endpoint(public=self.V2_URL, admin=self.V2_URL, internal=self.V2_URL) self.stub_url('POST', ['tokens'], base_url=self.V2_URL, json=token) v2_auth = identity.V2Password(self.V2_URL, username=uuid.uuid4().hex, password=uuid.uuid4().hex) sess = session.Session(auth=v2_auth) endpoint = sess.get_endpoint(service_type=self.IDENTITY, interface='public', version=(3, 0)) self.assertEqual(self.V3_URL, endpoint) def test_returns_original_when_discover_fails(self): token = fixture.V2Token() service = token.add_service(self.IDENTITY) service.add_endpoint(public=self.V2_URL, admin=self.V2_URL, internal=self.V2_URL) self.stub_url('POST', ['tokens'], base_url=self.V2_URL, json=token) self.stub_url('GET', [], base_url=self.BASE_URL, status_code=404) v2_auth = identity.V2Password(self.V2_URL, username=uuid.uuid4().hex, password=uuid.uuid4().hex) sess = session.Session(auth=v2_auth) endpoint = sess.get_endpoint(service_type=self.IDENTITY, interface='public', version=(3, 0)) self.assertEqual(self.V2_URL, endpoint) def test_getting_endpoints_on_auth_interface(self): disc = fixture.DiscoveryList(href=self.BASE_URL) self.stub_url('GET', ['/'], base_url=self.BASE_URL, status_code=300, json=disc) token = fixture.V2Token() service = token.add_service(self.IDENTITY) service.add_endpoint(public=self.V2_URL, admin=self.V2_URL, internal=self.V2_URL) self.stub_url('POST', ['tokens'], base_url=self.V2_URL, json=token) v2_auth = identity.V2Password(self.V2_URL, username=uuid.uuid4().hex, password=uuid.uuid4().hex) sess = session.Session(auth=v2_auth) endpoint = sess.get_endpoint(interface=plugin.AUTH_INTERFACE, version=(3, 0)) self.assertEqual(self.V3_URL, endpoint) class GenericPlugin(base.BaseAuthPlugin): BAD_TOKEN = uuid.uuid4().hex def __init__(self): super(GenericPlugin, self).__init__() self.endpoint = 'http://keystone.host:5000' self.headers = {'headerA': 'valueA', 'headerB': 'valueB'} self.cert = '/path/to/cert' self.connection_params = {'cert': self.cert, 'verify': False} def url(self, prefix): return '%s/%s' % (self.endpoint, prefix) def get_token(self, session, **kwargs): # NOTE(jamielennox): by specifying get_headers this should not be used return self.BAD_TOKEN def get_headers(self, session, **kwargs): return self.headers def get_endpoint(self, session, **kwargs): return self.endpoint def get_connection_params(self, session, **kwargs): return self.connection_params class GenericAuthPluginTests(utils.TestCase): # filter doesn't matter to GenericPlugin, but we have to specify one ENDPOINT_FILTER = {uuid.uuid4().hex: uuid.uuid4().hex} def setUp(self): super(GenericAuthPluginTests, self).setUp() self.auth = GenericPlugin() with self.deprecations.expect_deprecations_here(): self.session = session.Session(auth=self.auth) def test_setting_headers(self): text = uuid.uuid4().hex self.stub_url('GET', base_url=self.auth.url('prefix'), text=text) resp = self.session.get('prefix', endpoint_filter=self.ENDPOINT_FILTER) self.assertEqual(text, resp.text) for k, v in self.auth.headers.items(): self.assertRequestHeaderEqual(k, v) with self.deprecations.expect_deprecations_here(): self.assertIsNone(self.session.get_token()) self.assertEqual(self.auth.headers, self.session.get_auth_headers()) self.assertNotIn('X-Auth-Token', self.requests_mock.last_request.headers) def test_setting_connection_params(self): text = uuid.uuid4().hex with mock.patch.object(self.session.session, 'request') as mocked: mocked.return_value = utils.test_response(text=text) resp = self.session.get('prefix', endpoint_filter=self.ENDPOINT_FILTER) self.assertEqual(text, resp.text) # the cert and verify values passed to request are those that were # returned from the auth plugin as connection params. mocked.assert_called_once_with('GET', self.auth.url('prefix'), headers=mock.ANY, allow_redirects=False, cert=self.auth.cert, verify=False) def test_setting_bad_connection_params(self): # The uuid name parameter here is unknown and not in the allowed params # to be returned to the session and so an error will be raised. name = uuid.uuid4().hex self.auth.connection_params[name] = uuid.uuid4().hex e = self.assertRaises(exceptions.UnsupportedParameters, self.session.get, 'prefix', endpoint_filter=self.ENDPOINT_FILTER) self.assertIn(name, str(e)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_identity_v2.py0000664000175000017500000003164200000000000030207 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 argparse import copy from unittest import mock import uuid from keystoneclient.auth.identity import v2 from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils class V2IdentityPlugin(utils.TestCase): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v2.0') TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v2.0') TEST_PASS = 'password' TEST_SERVICE_CATALOG = [{ "endpoints": [{ "adminURL": "http://cdn.admin-nets.local:8774/v1.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:8774/v1.0", "publicURL": "http://cdn.admin-nets.local:8774/v1.0/" }], "type": "nova_compat", "name": "nova_compat" }, { "endpoints": [{ "adminURL": "http://nova/novapi/admin", "region": "RegionOne", "internalURL": "http://nova/novapi/internal", "publicURL": "http://nova/novapi/public" }], "type": "compute", "name": "nova" }, { "endpoints": [{ "adminURL": "http://glance/glanceapi/admin", "region": "RegionOne", "internalURL": "http://glance/glanceapi/internal", "publicURL": "http://glance/glanceapi/public" }], "type": "image", "name": "glance" }, { "endpoints": [{ "adminURL": TEST_ADMIN_URL, "region": "RegionOne", "internalURL": "http://127.0.0.1:5000/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" }], "type": "identity", "name": "keystone" }, { "endpoints": [{ "adminURL": "http://swift/swiftapi/admin", "region": "RegionOne", "internalURL": "http://swift/swiftapi/internal", "publicURL": "http://swift/swiftapi/public" }], "type": "object-store", "name": "swift" }] def setUp(self): super(V2IdentityPlugin, self).setUp() self.deprecations.expect_deprecations() self.TEST_RESPONSE_DICT = { "access": { "token": { "expires": "2999-01-01T00:00:10.000123Z", "id": self.TEST_TOKEN, "tenant": { "id": self.TEST_TENANT_ID }, }, "user": { "id": self.TEST_USER }, "serviceCatalog": self.TEST_SERVICE_CATALOG, }, } def stub_auth(self, **kwargs): self.stub_url('POST', ['tokens'], **kwargs) def test_authenticate_with_username_password(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) self.assertIsNone(a.user_id) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, 'password': self.TEST_PASS}}} self.assertRequestBodyIs(json=req) self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('Accept', 'application/json') self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_user_id_password(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, user_id=self.TEST_USER, password=self.TEST_PASS) self.assertIsNone(a.username) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'passwordCredentials': {'userId': self.TEST_USER, 'password': self.TEST_PASS}}} self.assertRequestBodyIs(json=req) self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('Accept', 'application/json') self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_username_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, tenant_id=self.TEST_TENANT_ID) self.assertIsNone(a.user_id) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, 'password': self.TEST_PASS}, 'tenantId': self.TEST_TENANT_ID}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_user_id_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, user_id=self.TEST_USER, password=self.TEST_PASS, tenant_id=self.TEST_TENANT_ID) self.assertIsNone(a.username) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'passwordCredentials': {'userId': self.TEST_USER, 'password': self.TEST_PASS}, 'tenantId': self.TEST_TENANT_ID}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_token(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Token(self.TEST_URL, 'foo') s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'token': {'id': 'foo'}}} self.assertRequestBodyIs(json=req) self.assertRequestHeaderEqual('x-Auth-Token', 'foo') self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('Accept', 'application/json') self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_with_trust_id(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, trust_id='trust') s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, 'password': self.TEST_PASS}, 'trust_id': 'trust'}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def _do_service_url_test(self, base_url, endpoint_filter): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', ['path'], base_url=base_url, text='SUCCESS', status_code=200) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) resp = s.get('/path', endpoint_filter=endpoint_filter) self.assertEqual(resp.status_code, 200) self.assertEqual(self.requests_mock.last_request.url, base_url + '/path') def test_service_url(self): endpoint_filter = {'service_type': 'compute', 'interface': 'admin', 'service_name': 'nova'} self._do_service_url_test('http://nova/novapi/admin', endpoint_filter) def test_service_url_defaults_to_public(self): endpoint_filter = {'service_type': 'compute'} self._do_service_url_test('http://nova/novapi/public', endpoint_filter) def test_endpoint_filter_without_service_type_fails(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.EndpointNotFound, s.get, '/path', endpoint_filter={'interface': 'admin'}) def test_full_url_overrides_endpoint_filter(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [], base_url='http://testurl/', text='SUCCESS', status_code=200) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) resp = s.get('http://testurl/', endpoint_filter={'service_type': 'compute'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.text, 'SUCCESS') def test_invalid_auth_response_dict(self): self.stub_auth(json={'hello': 'world'}) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', authenticated=True) def test_invalid_auth_response_type(self): self.stub_url('POST', ['tokens'], text='testdata') a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', authenticated=True) def test_invalidate_response(self): resp_data1 = copy.deepcopy(self.TEST_RESPONSE_DICT) resp_data2 = copy.deepcopy(self.TEST_RESPONSE_DICT) resp_data1['access']['token']['id'] = 'token1' resp_data2['access']['token']['id'] = 'token2' auth_responses = [{'json': resp_data1}, {'json': resp_data2}] self.stub_auth(response_list=auth_responses) a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) with self.deprecations.expect_deprecations_here(): self.assertEqual('token1', s.get_token()) self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) a.invalidate() with self.deprecations.expect_deprecations_here(): self.assertEqual('token2', s.get_token()) self.assertEqual({'X-Auth-Token': 'token2'}, s.get_auth_headers()) def test_doesnt_log_password(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) password = uuid.uuid4().hex a = v2.Password(self.TEST_URL, username=self.TEST_USER, password=password) s = session.Session(auth=a) with self.deprecations.expect_deprecations_here(): self.assertEqual(self.TEST_TOKEN, s.get_token()) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) self.assertNotIn(password, self.logger.output) def test_password_with_no_user_id_or_name(self): self.assertRaises(TypeError, v2.Password, self.TEST_URL, password=self.TEST_PASS) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() v2.Password.register_argparse_arguments(parser) username = uuid.uuid4().hex auth_url = uuid.uuid4().hex tenant_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-tenant-id', tenant_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = v2.Password.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin.username) self.assertEqual(tenant_id, plugin.tenant_id) self.assertEqual(password, plugin.password) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_identity_v3.py0000664000175000017500000005265100000000000030213 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 argparse import copy from unittest import mock import uuid from keystoneauth1 import fixture from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import base as v3_base from keystoneclient import client from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils class V3IdentityPlugin(utils.TestCase): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3') TEST_PASS = 'password' TEST_SERVICE_CATALOG = [{ "endpoints": [{ "url": "http://cdn.admin-nets.local:8774/v1.0/", "region": "RegionOne", "interface": "public" }, { "url": "http://127.0.0.1:8774/v1.0", "region": "RegionOne", "interface": "internal" }, { "url": "http://cdn.admin-nets.local:8774/v1.0", "region": "RegionOne", "interface": "admin" }], "type": "nova_compat" }, { "endpoints": [{ "url": "http://nova/novapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://nova/novapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://nova/novapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "compute", "name": "nova", }, { "endpoints": [{ "url": "http://glance/glanceapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://glance/glanceapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://glance/glanceapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "image", "name": "glance" }, { "endpoints": [{ "url": "http://127.0.0.1:5000/v3", "region": "RegionOne", "interface": "public" }, { "url": "http://127.0.0.1:5000/v3", "region": "RegionOne", "interface": "internal" }, { "url": TEST_ADMIN_URL, "region": "RegionOne", "interface": "admin" }], "type": "identity" }, { "endpoints": [{ "url": "http://swift/swiftapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://swift/swiftapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://swift/swiftapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "object-store" }] def setUp(self): super(V3IdentityPlugin, self).setUp() self.TEST_DISCOVERY_RESPONSE = { 'versions': {'values': [fixture.V3Discovery(self.TEST_URL)]}} self.deprecations.expect_deprecations() self.TEST_RESPONSE_DICT = { "token": { "methods": [ "token", "password" ], "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": self.TEST_DOMAIN_ID, "name": self.TEST_DOMAIN_NAME }, "id": self.TEST_TENANT_ID, "name": self.TEST_TENANT_NAME }, "user": { "domain": { "id": self.TEST_DOMAIN_ID, "name": self.TEST_DOMAIN_NAME }, "id": self.TEST_USER, "name": self.TEST_USER }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": self.TEST_SERVICE_CATALOG }, } self.TEST_PROJECTS_RESPONSE = { "projects": [ { "domain_id": "1789d1", "enabled": "True", "id": "263fd9", "links": { "self": "https://identity:5000/v3/projects/263fd9" }, "name": "Dev Group A" }, { "domain_id": "1789d1", "enabled": "True", "id": "e56ad3", "links": { "self": "https://identity:5000/v3/projects/e56ad3" }, "name": "Dev Group B" } ], "links": { "self": "https://identity:5000/v3/projects", } } def stub_auth(self, subject_token=None, **kwargs): if not subject_token: subject_token = self.TEST_TOKEN self.stub_url('POST', ['auth', 'tokens'], headers={'X-Subject-Token': subject_token}, **kwargs) def test_authenticate_with_username_password(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}}}} self.assertRequestBodyIs(json=req) self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('Accept', 'application/json') self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_username_password_unscoped(self): del self.TEST_RESPONSE_DICT['token']['catalog'] del self.TEST_RESPONSE_DICT['token']['project'] self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url(method="GET", json=self.TEST_DISCOVERY_RESPONSE) test_user_id = self.TEST_RESPONSE_DICT['token']['user']['id'] self.stub_url(method="GET", json=self.TEST_PROJECTS_RESPONSE, parts=['users', test_user_id, 'projects']) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) cs = client.Client(session=s) # As a sanity check on the auth_ref, make sure client has the # proper user id, that it fetches the right project response self.assertEqual(test_user_id, a.auth_ref.user_id) t = cs.projects.list(user=a.auth_ref.user_id) self.assertEqual(2, len(t)) def test_authenticate_with_username_password_domain_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, domain_id=self.TEST_DOMAIN_ID) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}}, 'scope': {'domain': {'id': self.TEST_DOMAIN_ID}}}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_authenticate_with_username_password_project_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, project_id=self.TEST_TENANT_ID) s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}}, 'scope': {'project': {'id': self.TEST_TENANT_ID}}}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) self.assertEqual(s.auth.auth_ref.project_id, self.TEST_TENANT_ID) def test_authenticate_with_token(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Token(self.TEST_URL, self.TEST_TOKEN) s = session.Session(auth=a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['token'], 'token': {'id': self.TEST_TOKEN}}}} self.assertRequestBodyIs(json=req) self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('Accept', 'application/json') self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_with_expired(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) d = copy.deepcopy(self.TEST_RESPONSE_DICT) d['token']['expires_at'] = '2000-01-01T00:00:10.000123Z' a = v3.Password(self.TEST_URL, username='username', password='password') a.auth_ref = access.AccessInfo.factory(body=d) s = session.Session(auth=a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) self.assertEqual(a.auth_ref['expires_at'], self.TEST_RESPONSE_DICT['token']['expires_at']) def test_with_domain_and_project_scoping(self): a = v3.Password(self.TEST_URL, username='username', password='password', project_id='project', domain_id='domain') self.assertRaises(exceptions.AuthorizationFailure, a.get_token, None) self.assertRaises(exceptions.AuthorizationFailure, a.get_headers, None) def test_with_trust_id(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, trust_id='trust') s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}}, 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_with_multiple_mechanisms_factory(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) p = v3.PasswordMethod(username=self.TEST_USER, password=self.TEST_PASS) t = v3.TokenMethod(token='foo') a = v3.Auth(self.TEST_URL, [p, t], trust_id='trust') s = session.Session(a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password', 'token'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}, 'token': {'id': 'foo'}}, 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_with_multiple_mechanisms(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) p = v3.PasswordMethod(username=self.TEST_USER, password=self.TEST_PASS) t = v3.TokenMethod(token='foo') a = v3.Auth(self.TEST_URL, [p, t], trust_id='trust') s = session.Session(auth=a) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) req = {'auth': {'identity': {'methods': ['password', 'token'], 'password': {'user': {'name': self.TEST_USER, 'password': self.TEST_PASS}}, 'token': {'id': 'foo'}}, 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} self.assertRequestBodyIs(json=req) self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) def test_with_multiple_scopes(self): s = session.Session() a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, domain_id='x', project_id='x') self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, domain_id='x', trust_id='x') self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) def _do_service_url_test(self, base_url, endpoint_filter): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', ['path'], base_url=base_url, text='SUCCESS', status_code=200) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) resp = s.get('/path', endpoint_filter=endpoint_filter) self.assertEqual(resp.status_code, 200) self.assertEqual(self.requests_mock.last_request.url, base_url + '/path') def test_service_url(self): endpoint_filter = {'service_type': 'compute', 'interface': 'admin', 'service_name': 'nova'} self._do_service_url_test('http://nova/novapi/admin', endpoint_filter) def test_service_url_defaults_to_public(self): endpoint_filter = {'service_type': 'compute'} self._do_service_url_test('http://nova/novapi/public', endpoint_filter) def test_endpoint_filter_without_service_type_fails(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.EndpointNotFound, s.get, '/path', endpoint_filter={'interface': 'admin'}) def test_full_url_overrides_endpoint_filter(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [], base_url='http://testurl/', text='SUCCESS', status_code=200) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) resp = s.get('http://testurl/', endpoint_filter={'service_type': 'compute'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.text, 'SUCCESS') def test_invalid_auth_response_dict(self): self.stub_auth(json={'hello': 'world'}) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', authenticated=True) def test_invalid_auth_response_type(self): self.stub_url('POST', ['auth', 'tokens'], text='testdata') a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', authenticated=True) def test_invalidate_response(self): auth_responses = [{'status_code': 200, 'json': self.TEST_RESPONSE_DICT, 'headers': {'X-Subject-Token': 'token1'}}, {'status_code': 200, 'json': self.TEST_RESPONSE_DICT, 'headers': {'X-Subject-Token': 'token2'}}] self.requests_mock.post('%s/auth/tokens' % self.TEST_URL, auth_responses) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) s = session.Session(auth=a) with self.deprecations.expect_deprecations_here(): self.assertEqual('token1', s.get_token()) self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) a.invalidate() with self.deprecations.expect_deprecations_here(): self.assertEqual('token2', s.get_token()) self.assertEqual({'X-Auth-Token': 'token2'}, s.get_auth_headers()) def test_doesnt_log_password(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) password = uuid.uuid4().hex a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=password) s = session.Session(a) with self.deprecations.expect_deprecations_here(): self.assertEqual(self.TEST_TOKEN, s.get_token()) self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, s.get_auth_headers()) self.assertNotIn(password, self.logger.output) def test_sends_nocatalog(self): del self.TEST_RESPONSE_DICT['token']['catalog'] self.stub_auth(json=self.TEST_RESPONSE_DICT) a = v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS, include_catalog=False) s = session.Session(auth=a) s.get_auth_headers() auth_url = self.TEST_URL + '/auth/tokens' self.assertEqual(auth_url, a.token_url) self.assertEqual(auth_url + '?nocatalog', self.requests_mock.last_request.url) def test_symbols(self): self.assertIs(v3.AuthMethod, v3_base.AuthMethod) self.assertIs(v3.AuthConstructor, v3_base.AuthConstructor) self.assertIs(v3.Auth, v3_base.Auth) def test_unscoped_request(self): token = fixture.V3Token() self.stub_auth(json=token) password = uuid.uuid4().hex a = v3.Password(self.TEST_URL, user_id=token.user_id, password=password, unscoped=True) s = session.Session() auth_ref = a.get_access(s) with self.deprecations.expect_deprecations_here(): self.assertFalse(auth_ref.scoped) body = self.requests_mock.last_request.json() ident = body['auth']['identity'] self.assertEqual(['password'], ident['methods']) self.assertEqual(token.user_id, ident['password']['user']['id']) self.assertEqual(password, ident['password']['user']['password']) self.assertEqual({}, body['auth']['scope']['unscoped']) def test_unscoped_with_scope_data(self): a = v3.Password(self.TEST_URL, user_id=uuid.uuid4().hex, password=uuid.uuid4().hex, unscoped=True, project_id=uuid.uuid4().hex) s = session.Session() self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() v3.Password.register_argparse_arguments(parser) username = uuid.uuid4().hex user_domain_id = uuid.uuid4().hex auth_url = uuid.uuid4().hex project_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-user-domain-id', user_domain_id, '--os-project-id', project_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = v3.Password.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin.auth_methods[0].username) self.assertEqual(project_id, plugin.project_id) self.assertEqual(user_domain_id, plugin.auth_methods[0].user_domain_id) self.assertEqual(password, plugin.auth_methods[0].password) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_identity_v3_federated.py0000664000175000017500000000735100000000000032213 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 import uuid from keystoneauth1 import fixture from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient import session from keystoneclient.tests.unit import utils class TesterFederationPlugin(v3.FederatedBaseAuth): def get_unscoped_auth_ref(self, sess, **kwargs): # This would go and talk to an idp or something resp = sess.post(self.federated_token_url, authenticated=False) return access.AccessInfo.factory(resp=resp, body=resp.json()) class V3FederatedPlugin(utils.TestCase): AUTH_URL = 'http://keystone/v3' def setUp(self): super(V3FederatedPlugin, self).setUp() self.deprecations.expect_deprecations() self.unscoped_token = fixture.V3Token() self.unscoped_token_id = uuid.uuid4().hex self.scoped_token = copy.deepcopy(self.unscoped_token) self.scoped_token.set_project_scope() self.scoped_token.methods.append('token') self.scoped_token_id = uuid.uuid4().hex s = self.scoped_token.add_service('compute', name='nova') s.add_standard_endpoints(public='http://nova/public', admin='http://nova/admin', internal='http://nova/internal') self.idp = uuid.uuid4().hex self.protocol = uuid.uuid4().hex self.token_url = ('%s/OS-FEDERATION/identity_providers/%s/protocols/%s' '/auth' % (self.AUTH_URL, self.idp, self.protocol)) headers = {'X-Subject-Token': self.unscoped_token_id} self.unscoped_mock = self.requests_mock.post(self.token_url, json=self.unscoped_token, headers=headers) headers = {'X-Subject-Token': self.scoped_token_id} auth_url = self.AUTH_URL + '/auth/tokens' self.scoped_mock = self.requests_mock.post(auth_url, json=self.scoped_token, headers=headers) def get_plugin(self, **kwargs): kwargs.setdefault('auth_url', self.AUTH_URL) kwargs.setdefault('protocol', self.protocol) kwargs.setdefault('identity_provider', self.idp) return TesterFederationPlugin(**kwargs) def test_federated_url(self): plugin = self.get_plugin() self.assertEqual(self.token_url, plugin.federated_token_url) def test_unscoped_behaviour(self): sess = session.Session(auth=self.get_plugin()) self.assertEqual(self.unscoped_token_id, sess.get_token()) self.assertTrue(self.unscoped_mock.called) self.assertFalse(self.scoped_mock.called) def test_scoped_behaviour(self): auth = self.get_plugin(project_id=self.scoped_token.project_id) sess = session.Session(auth=auth) self.assertEqual(self.scoped_token_id, sess.get_token()) self.assertTrue(self.unscoped_mock.called) self.assertTrue(self.scoped_mock.called) def test_options(self): opts = [o.name for o in v3.FederatedBaseAuth.get_options()] self.assertIn('protocol', opts) self.assertIn('identity-provider', opts) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_loading.py0000664000175000017500000000265000000000000027361 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 keystoneclient.tests.unit.auth import utils class TestOtherLoading(utils.TestCase): def test_loading_getter(self): called_opts = [] vals = {'a-int': 44, 'a-bool': False, 'a-float': 99.99, 'a-str': 'value'} val = uuid.uuid4().hex def _getter(opt): called_opts.append(opt.name) # return str because oslo.config should convert them back return str(vals[opt.name]) p = utils.MockPlugin.load_from_options_getter(_getter, other=val) self.assertEqual(set(vals), set(called_opts)) for k, v in vals.items(): # replace - to _ because it's the dest used to create kwargs self.assertEqual(v, p[k.replace('-', '_')]) # check that additional kwargs get passed through self.assertEqual(val, p['other']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_password.py0000664000175000017500000000717300000000000027613 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 argparse from unittest import mock import uuid from keystoneclient.auth.identity.generic import password from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import password as v3_password from keystoneclient.tests.unit.auth import utils class PasswordTests(utils.GenericPluginTestCase): PLUGIN_CLASS = password.Password V2_PLUGIN_CLASS = v2.Password V3_PLUGIN_CLASS = v3.Password def new_plugin(self, **kwargs): kwargs.setdefault('username', uuid.uuid4().hex) kwargs.setdefault('password', uuid.uuid4().hex) return super(PasswordTests, self).new_plugin(**kwargs) def test_with_user_domain_params(self): self.stub_discovery() self.assertCreateV3(domain_id=uuid.uuid4().hex, user_domain_id=uuid.uuid4().hex) def test_v3_user_params_v2_url(self): self.stub_discovery(v3=False) self.assertDiscoveryFailure(user_domain_id=uuid.uuid4().hex) def test_options(self): opts = [o.name for o in self.PLUGIN_CLASS.get_options()] allowed_opts = ['username', 'user-domain-id', 'user-domain-name', 'user-id', 'password', 'domain-id', 'domain-name', 'tenant-id', 'tenant-name', 'project-id', 'project-name', 'project-domain-id', 'project-domain-name', 'trust-id', 'auth-url'] self.assertEqual(set(allowed_opts), set(opts)) self.assertEqual(len(allowed_opts), len(opts)) def test_symbols(self): self.assertIs(v3.Password, v3_password.Password) self.assertIs(v3.PasswordMethod, v3_password.PasswordMethod) @mock.patch('sys.stdin', autospec=True) def test_prompt_password(self, mock_stdin): parser = argparse.ArgumentParser() self.PLUGIN_CLASS.register_argparse_arguments(parser) username = uuid.uuid4().hex user_domain_id = uuid.uuid4().hex auth_url = uuid.uuid4().hex project_id = uuid.uuid4().hex password = uuid.uuid4().hex opts = parser.parse_args(['--os-username', username, '--os-auth-url', auth_url, '--os-user-domain-id', user_domain_id, '--os-project-id', project_id]) with mock.patch('getpass.getpass') as mock_getpass: mock_getpass.return_value = password mock_stdin.isatty = lambda: True plugin = self.PLUGIN_CLASS.load_from_argparse_arguments(opts) self.assertEqual(auth_url, plugin.auth_url) self.assertEqual(username, plugin._username) self.assertEqual(project_id, plugin._project_id) self.assertEqual(user_domain_id, plugin._user_domain_id) self.assertEqual(password, plugin._password) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_token.py0000664000175000017500000000352400000000000027065 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 keystoneclient.auth.identity.generic import token from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient.auth.identity.v3 import token as v3_token from keystoneclient.tests.unit.auth import utils class TokenTests(utils.GenericPluginTestCase): PLUGIN_CLASS = token.Token V2_PLUGIN_CLASS = v2.Token V3_PLUGIN_CLASS = v3.Token def new_plugin(self, **kwargs): kwargs.setdefault('token', uuid.uuid4().hex) return super(TokenTests, self).new_plugin(**kwargs) def test_options(self): opts = [o.name for o in self.PLUGIN_CLASS.get_options()] allowed_opts = ['token', 'domain-id', 'domain-name', 'tenant-id', 'tenant-name', 'project-id', 'project-name', 'project-domain-id', 'project-domain-name', 'trust-id', 'auth-url'] self.assertEqual(set(allowed_opts), set(opts)) self.assertEqual(len(allowed_opts), len(opts)) def test_symbols(self): self.assertIs(v3.Token, v3_token.Token) self.assertIs(v3.TokenMethod, v3_token.TokenMethod) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/test_token_endpoint.py0000664000175000017500000000446600000000000030773 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 testtools import matchers from keystoneclient.auth import token_endpoint from keystoneclient import session from keystoneclient.tests.unit import utils class TokenEndpointTest(utils.TestCase): TEST_TOKEN = 'aToken' TEST_URL = 'http://server/prefix' def setUp(self): super(TokenEndpointTest, self).setUp() self.deprecations.expect_deprecations() def test_basic_case(self): self.requests_mock.get(self.TEST_URL, text='body') a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) s = session.Session(auth=a) data = s.get(self.TEST_URL, authenticated=True) self.assertEqual(data.text, 'body') self.assertRequestHeaderEqual('X-Auth-Token', self.TEST_TOKEN) def test_basic_endpoint_case(self): self.stub_url('GET', ['p'], text='body') a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) s = session.Session(auth=a) data = s.get('/p', authenticated=True, endpoint_filter={'service': 'identity'}) self.assertEqual(self.TEST_URL, a.get_endpoint(s)) self.assertEqual('body', data.text) self.assertRequestHeaderEqual('X-Auth-Token', self.TEST_TOKEN) def test_token_endpoint_options(self): opt_names = [opt.name for opt in token_endpoint.Token.get_options()] self.assertThat(opt_names, matchers.HasLength(2)) self.assertIn('token', opt_names) self.assertIn('endpoint', opt_names) def test_token_endpoint_user_id(self): a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) s = session.Session() # we can't know this information about this sort of plugin self.assertIsNone(a.get_user_id(s)) self.assertIsNone(a.get_project_id(s)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/auth/utils.py0000664000175000017500000001462500000000000026052 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 functools from unittest import mock import uuid from keystoneauth1 import fixture from oslo_config import cfg from keystoneclient import access from keystoneclient.auth import base from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils class MockPlugin(base.BaseAuthPlugin): INT_DESC = 'test int' FLOAT_DESC = 'test float' BOOL_DESC = 'test bool' STR_DESC = 'test str' STR_DEFAULT = uuid.uuid4().hex def __init__(self, **kwargs): self._data = kwargs def __getitem__(self, key): """Get the data of the key.""" return self._data[key] def get_token(self, *args, **kwargs): return 'aToken' def get_endpoint(self, *args, **kwargs): return 'http://test' @classmethod def get_options(cls): return [ cfg.IntOpt('a-int', default='3', help=cls.INT_DESC), cfg.BoolOpt('a-bool', help=cls.BOOL_DESC), cfg.FloatOpt('a-float', help=cls.FLOAT_DESC), cfg.StrOpt('a-str', help=cls.STR_DESC, default=cls.STR_DEFAULT), ] class MockManager(object): def __init__(self, driver): self.driver = driver def mock_plugin(f): @functools.wraps(f) def inner(*args, **kwargs): with mock.patch.object(base, 'get_plugin_class') as m: m.return_value = MockPlugin args = list(args) + [m] return f(*args, **kwargs) return inner class TestCase(utils.TestCase): GROUP = 'auth' V2PASS = 'v2password' V3TOKEN = 'v3token' a_int = 88 a_float = 88.8 a_bool = False TEST_VALS = {'a_int': a_int, 'a_float': a_float, 'a_bool': a_bool} def assertTestVals(self, plugin, vals=TEST_VALS): for k, v in vals.items(): self.assertEqual(v, plugin[k]) class GenericPluginTestCase(utils.TestCase): TEST_URL = 'http://keystone.host:5000/' # OVERRIDE THESE IN SUB CLASSES PLUGIN_CLASS = None V2_PLUGIN_CLASS = None V3_PLUGIN_CLASS = None def setUp(self): super(GenericPluginTestCase, self).setUp() self.token_v2 = fixture.V2Token() self.token_v3 = fixture.V3Token() self.token_v3_id = uuid.uuid4().hex self.deprecations.expect_deprecations() self.session = session.Session() self.stub_url('POST', ['v2.0', 'tokens'], json=self.token_v2) self.stub_url('POST', ['v3', 'auth', 'tokens'], headers={'X-Subject-Token': self.token_v3_id}, json=self.token_v3) def new_plugin(self, **kwargs): kwargs.setdefault('auth_url', self.TEST_URL) return self.PLUGIN_CLASS(**kwargs) def stub_discovery(self, base_url=None, **kwargs): kwargs.setdefault('href', self.TEST_URL) disc = fixture.DiscoveryList(**kwargs) self.stub_url('GET', json=disc, base_url=base_url, status_code=300) return disc def assertCreateV3(self, **kwargs): auth = self.new_plugin(**kwargs) auth_ref = auth.get_auth_ref(self.session) self.assertIsInstance(auth_ref, access.AccessInfoV3) self.assertEqual(self.TEST_URL + 'v3/auth/tokens', self.requests_mock.last_request.url) self.assertIsInstance(auth._plugin, self.V3_PLUGIN_CLASS) return auth def assertCreateV2(self, **kwargs): auth = self.new_plugin(**kwargs) auth_ref = auth.get_auth_ref(self.session) self.assertIsInstance(auth_ref, access.AccessInfoV2) self.assertEqual(self.TEST_URL + 'v2.0/tokens', self.requests_mock.last_request.url) self.assertIsInstance(auth._plugin, self.V2_PLUGIN_CLASS) return auth def assertDiscoveryFailure(self, **kwargs): plugin = self.new_plugin(**kwargs) self.assertRaises(exceptions.DiscoveryFailure, plugin.get_auth_ref, self.session) def test_create_v3_if_domain_params(self): self.stub_discovery() self.assertCreateV3(domain_id=uuid.uuid4().hex) self.assertCreateV3(domain_name=uuid.uuid4().hex) self.assertCreateV3(project_name=uuid.uuid4().hex, project_domain_name=uuid.uuid4().hex) self.assertCreateV3(project_name=uuid.uuid4().hex, project_domain_id=uuid.uuid4().hex) def test_create_v2_if_no_domain_params(self): self.stub_discovery() self.assertCreateV2() self.assertCreateV2(project_id=uuid.uuid4().hex) self.assertCreateV2(project_name=uuid.uuid4().hex) self.assertCreateV2(tenant_id=uuid.uuid4().hex) self.assertCreateV2(tenant_name=uuid.uuid4().hex) def test_v3_params_v2_url(self): self.stub_discovery(v3=False) self.assertDiscoveryFailure(domain_name=uuid.uuid4().hex) def test_v2_params_v3_url(self): self.stub_discovery(v2=False) self.assertCreateV3() def test_no_urls(self): self.stub_discovery(v2=False, v3=False) self.assertDiscoveryFailure() def test_path_based_url_v2(self): self.stub_url('GET', ['v2.0'], status_code=403) self.assertCreateV2(auth_url=self.TEST_URL + 'v2.0') def test_path_based_url_v3(self): self.stub_url('GET', ['v3'], status_code=403) self.assertCreateV3(auth_url=self.TEST_URL + 'v3') def test_disc_error_for_failure(self): self.stub_url('GET', [], status_code=403) self.assertDiscoveryFailure() def test_v3_plugin_from_failure(self): url = self.TEST_URL + 'v3' self.stub_url('GET', [], base_url=url, status_code=403) self.assertCreateV3(auth_url=url) def test_unknown_discovery_version(self): # make a v4 entry that's mostly the same as a v3 self.stub_discovery(v2=False, v3_id='v4.0') self.assertDiscoveryFailure() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/client_fixtures.py0000664000175000017500000007505600000000000027165 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 contextlib import os import uuid import warnings import fixtures from keystoneauth1 import fixture from keystoneauth1 import identity as ksa_identity from keystoneauth1 import session as ksa_session from oslo_serialization import jsonutils from oslo_utils import timeutils import testresources from keystoneclient.auth import identity as ksc_identity from keystoneclient.common import cms from keystoneclient import session as ksc_session from keystoneclient import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client TEST_ROOT_URL = 'http://127.0.0.1:5000/' TESTDIR = os.path.dirname(os.path.abspath(__file__)) ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..', '..')) CERTDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'certs') CMSDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'cms') KEYDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'private') class BaseFixture(fixtures.Fixture): TEST_ROOT_URL = TEST_ROOT_URL def __init__(self, requests, deprecations): super(BaseFixture, self).__init__() self.requests = requests self.deprecations = deprecations self.user_id = uuid.uuid4().hex self.client = self.new_client() class BaseV2(BaseFixture): TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v2.0') class OriginalV2(BaseV2): def new_client(self): # Creating a Client not using session is deprecated. with self.deprecations.expect_deprecations_here(): return v2_client.Client(username=uuid.uuid4().hex, user_id=self.user_id, token=uuid.uuid4().hex, tenant_name=uuid.uuid4().hex, auth_url=self.TEST_URL, endpoint=self.TEST_URL) class KscSessionV2(BaseV2): def new_client(self): t = fixture.V2Token(user_id=self.user_id) t.set_scope() s = t.add_service('identity') s.add_endpoint(self.TEST_URL) d = fixture.V2Discovery(self.TEST_URL) self.requests.register_uri('POST', self.TEST_URL + '/tokens', json=t) # NOTE(jamielennox): Because of the versioned URL hack here even though # the V2 URL will be in the service catalog it will be the root URL # that will be queried for discovery. self.requests.register_uri('GET', self.TEST_ROOT_URL, json={'version': d}) with self.deprecations.expect_deprecations_here(): a = ksc_identity.V2Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksc_session.Session(auth=a) return v2_client.Client(session=s) class KsaSessionV2(BaseV2): def new_client(self): t = fixture.V2Token(user_id=self.user_id) t.set_scope() s = t.add_service('identity') s.add_endpoint(self.TEST_URL) d = fixture.V2Discovery(self.TEST_URL) self.requests.register_uri('POST', self.TEST_URL + '/tokens', json=t) # NOTE(jamielennox): Because of the versioned URL hack here even though # the V2 URL will be in the service catalog it will be the root URL # that will be queried for discovery. self.requests.register_uri('GET', self.TEST_ROOT_URL, json={'version': d}) a = ksa_identity.V2Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksa_session.Session(auth=a) return v2_client.Client(session=s) class BaseV3(BaseFixture): TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') class OriginalV3(BaseV3): def new_client(self): # Creating a Client not using session is deprecated. with self.deprecations.expect_deprecations_here(): return v3_client.Client(username=uuid.uuid4().hex, user_id=self.user_id, token=uuid.uuid4().hex, tenant_name=uuid.uuid4().hex, auth_url=self.TEST_URL, endpoint=self.TEST_URL) class KscSessionV3(BaseV3): def new_client(self): t = fixture.V3Token(user_id=self.user_id) t.set_project_scope() s = t.add_service('identity') s.add_standard_endpoints(public=self.TEST_URL, admin=self.TEST_URL) d = fixture.V3Discovery(self.TEST_URL) headers = {'X-Subject-Token': uuid.uuid4().hex} self.requests.register_uri('POST', self.TEST_URL + '/auth/tokens', headers=headers, json=t) self.requests.register_uri('GET', self.TEST_URL, json={'version': d}) with self.deprecations.expect_deprecations_here(): a = ksc_identity.V3Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, user_domain_id=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksc_session.Session(auth=a) return v3_client.Client(session=s) class KsaSessionV3(BaseV3): def new_client(self): t = fixture.V3Token(user_id=self.user_id) t.set_project_scope() s = t.add_service('identity') s.add_standard_endpoints(public=self.TEST_URL, admin=self.TEST_URL) d = fixture.V3Discovery(self.TEST_URL) headers = {'X-Subject-Token': uuid.uuid4().hex} self.requests.register_uri('POST', self.TEST_URL + '/auth/tokens', headers=headers, json=t) self.requests.register_uri('GET', self.TEST_URL, json={'version': d}) a = ksa_identity.V3Password(username=uuid.uuid4().hex, password=uuid.uuid4().hex, user_domain_id=uuid.uuid4().hex, auth_url=self.TEST_URL) s = ksa_session.Session(auth=a) return v3_client.Client(session=s) def _hash_signed_token_safe(signed_text, **kwargs): if isinstance(signed_text, str): signed_text = signed_text.encode('utf-8') return utils.hash_signed_token(signed_text, **kwargs) 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() # The data for several tests are signed using openssl and are stored in # files in the signing subdirectory. In order to keep the values # consistent between the tests and the signed documents, we read them # in for use in the tests. with open(os.path.join(CMSDIR, 'auth_token_scoped.json')) as f: self.TOKEN_SCOPED_DATA = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pem')) as f: self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_unscoped.pem')) as f: self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pem')) as f: self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read()) self.SIGNED_v3_TOKEN_SCOPED_HASH = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( self.SIGNED_v3_TOKEN_SCOPED, mode='sha256') with open(os.path.join(CMSDIR, 'auth_token_revoked.pem')) as f: self.REVOKED_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pem')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pem')) as f: self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f: self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f: self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_revoked.pkiz')) as f: self.REVOKED_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pkiz')) as f: self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pkiz')) as f: self.REVOKED_v3_TOKEN_PKIZ = cms.cms_to_token(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.json')) as f: self.REVOCATION_LIST = jsonutils.loads(f.read()) with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f: self.SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()}) self.SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem') with open(self.SIGNING_CERT_FILE) as f: self.SIGNING_CERT = f.read() self.KERBEROS_BIND = 'USER@REALM' self.SIGNING_KEY_FILE = os.path.join(KEYDIR, 'signing_key.pem') with open(self.SIGNING_KEY_FILE) as f: self.SIGNING_KEY = f.read() self.SIGNING_CA_FILE = os.path.join(CERTDIR, 'cacert.pem') with open(self.SIGNING_CA_FILE) as f: self.SIGNING_CA = f.read() 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.VALID_DIABLO_TOKEN = 'b0cf19b55dbb4f20a6ee18e6c6cf1726' 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' revoked_token = self.REVOKED_TOKEN if isinstance(revoked_token, str): revoked_token = revoked_token.encode('utf-8') self.REVOKED_TOKEN_HASH = utils.hash_signed_token(revoked_token) self.REVOKED_TOKEN_HASH_SHA256 = utils.hash_signed_token(revoked_token, mode='sha256') self.REVOKED_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_TOKEN_LIST_JSON = jsonutils.dumps(self.REVOKED_TOKEN_LIST) revoked_v3_token = self.REVOKED_v3_TOKEN if isinstance(revoked_v3_token, str): revoked_v3_token = revoked_v3_token.encode('utf-8') self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(revoked_v3_token) hash = utils.hash_signed_token(revoked_v3_token, mode='sha256') self.REVOKED_v3_TOKEN_HASH_SHA256 = hash self.REVOKED_v3_TOKEN_LIST = ( {'revoked': [{'id': self.REVOKED_v3_TOKEN_HASH, 'expires': timeutils.utcnow()}]}) self.REVOKED_v3_TOKEN_LIST_JSON = jsonutils.dumps( self.REVOKED_v3_TOKEN_LIST) revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ if isinstance(revoked_token_pkiz, str): revoked_token_pkiz = revoked_token_pkiz.encode('utf-8') self.REVOKED_TOKEN_PKIZ_HASH = utils.hash_signed_token( revoked_token_pkiz) revoked_v3_token_pkiz = self.REVOKED_v3_TOKEN_PKIZ if isinstance(revoked_v3_token_pkiz, str): revoked_v3_token_pkiz = revoked_v3_token_pkiz.encode('utf-8') self.REVOKED_v3_PKIZ_TOKEN_HASH = utils.hash_signed_token( revoked_v3_token_pkiz) self.REVOKED_TOKEN_PKIZ_LIST = ( {'revoked': [{'id': self.REVOKED_TOKEN_PKIZ_HASH, 'expires': timeutils.utcnow()}, {'id': self.REVOKED_v3_PKIZ_TOKEN_HASH, 'expires': timeutils.utcnow()}, ]}) self.REVOKED_TOKEN_PKIZ_LIST_JSON = jsonutils.dumps( self.REVOKED_TOKEN_PKIZ_LIST) self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED) self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED) self.SIGNED_v3_TOKEN_SCOPED_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED) self.SIGNED_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_SCOPED_PKIZ) self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_TOKEN_UNSCOPED_PKIZ) self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY = cms.cms_hash_token( self.SIGNED_v3_TOKEN_SCOPED_PKIZ) self.INVALID_SIGNED_TOKEN = ( "MIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") self.INVALID_SIGNED_PKIZ_TOKEN = ( "PKIZ_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111" "2222222222222222222222222222222222222222222222222222222222222222" "3333333333333333333333333333333333333333333333333333333333333333" "4444444444444444444444444444444444444444444444444444444444444444" "5555555555555555555555555555555555555555555555555555555555555555" "6666666666666666666666666666666666666666666666666666666666666666" "7777777777777777777777777777777777777777777777777777777777777777" "8888888888888888888888888888888888888888888888888888888888888888" "9999999999999999999999999999999999999999999999999999999999999999" "0000000000000000000000000000000000000000000000000000000000000000") # JSON responses keyed by token ID self.TOKEN_RESPONSES = { self.UUID_TOKEN_DEFAULT: { 'access': { 'token': { 'id': self.UUID_TOKEN_DEFAULT, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.VALID_DIABLO_TOKEN: { 'access': { 'token': { 'id': self.VALID_DIABLO_TOKEN, 'expires': '2999-01-01T00:00:10.000123Z', 'tenantId': 'tenant_id1', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.UUID_TOKEN_UNSCOPED: { 'access': { 'token': { 'id': self.UUID_TOKEN_UNSCOPED, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.UUID_TOKEN_NO_SERVICE_CATALOG: { 'access': { 'token': { 'id': 'valid-token', 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], } }, }, self.UUID_TOKEN_BIND: { 'access': { 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'id': self.UUID_TOKEN_BIND, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.UUID_TOKEN_UNKNOWN_BIND: { 'access': { 'token': { 'bind': {'FOO': 'BAR'}, 'id': self.UUID_TOKEN_UNKNOWN_BIND, 'expires': '2999-01-01T00:00:10.000123Z', 'tenant': { 'id': 'tenant_id1', 'name': 'tenant_name1', }, }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, 'serviceCatalog': {} }, }, self.v3_UUID_TOKEN_DEFAULT: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.v3_UUID_TOKEN_UNSCOPED: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } } } }, self.v3_UUID_TOKEN_DOMAIN_SCOPED: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'domain': { 'id': 'domain_id1', 'name': 'domain_name1', }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.SIGNED_TOKEN_SCOPED_KEY: { 'access': { 'token': { 'id': self.SIGNED_TOKEN_SCOPED_KEY, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'tenantId': 'tenant_id1', 'tenantName': 'tenant_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.SIGNED_TOKEN_UNSCOPED_KEY: { 'access': { 'token': { 'id': self.SIGNED_TOKEN_UNSCOPED_KEY, 'expires': '2999-01-01T00:00:10.000123Z', }, 'user': { 'id': 'user_id1', 'name': 'user_name1', 'roles': [ {'name': 'role1'}, {'name': 'role2'}, ], }, }, }, self.SIGNED_v3_TOKEN_SCOPED_KEY: { 'token': { 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1'}, {'name': 'role2'} ], 'catalog': {} } }, self.v3_UUID_TOKEN_BIND: { 'token': { 'bind': {'kerberos': self.KERBEROS_BIND}, 'methods': ['password'], 'expires_at': '2999-01-01T00:00:10.000123Z', 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, self.v3_UUID_TOKEN_UNKNOWN_BIND: { 'token': { 'bind': {'FOO': 'BAR'}, 'expires_at': '2999-01-01T00:00:10.000123Z', 'methods': ['password'], 'user': { 'id': 'user_id1', 'name': 'user_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'project': { 'id': 'tenant_id1', 'name': 'tenant_name1', 'domain': { 'id': 'domain_id1', 'name': 'domain_name1' } }, 'roles': [ {'name': 'role1', 'id': 'Role1'}, {'name': 'role2', 'id': 'Role2'}, ], 'catalog': {} } }, } self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_TOKEN_UNSCOPED_KEY]) self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_PKIZ_KEY] = ( self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY]) self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in self.TOKEN_RESPONSES.items()]) EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) class Deprecations(fixtures.Fixture): def setUp(self): super(Deprecations, self).setUp() # If keystoneclient calls any deprecated function this will raise an # exception. warnings.filterwarnings('error', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') self.addCleanup(warnings.resetwarnings) def expect_deprecations(self): """Call this if the test expects to call deprecated function.""" warnings.resetwarnings() warnings.filterwarnings('ignore', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') @contextlib.contextmanager def expect_deprecations_here(self): warnings.resetwarnings() warnings.filterwarnings('ignore', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') yield warnings.resetwarnings() warnings.filterwarnings('error', category=DeprecationWarning, module='^keystoneclient\\.') warnings.filterwarnings('ignore', category=DeprecationWarning, module='^debtcollector\\.') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2310035 python_keystoneclient-5.6.0/keystoneclient/tests/unit/generic/0000775000175000017500000000000000000000000025003 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/generic/__init__.py0000664000175000017500000000000000000000000027102 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/generic/test_client.py0000664000175000017500000000542300000000000027676 0ustar00zuulzuul00000000000000# Copyright 2014 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. from oslo_serialization import jsonutils from keystoneclient.generic import client from keystoneclient.tests.unit import utils BASE_HOST = 'http://keystone.example.com' BASE_URL = "%s:5000/" % BASE_HOST V2_URL = "%sv2.0" % BASE_URL EXTENSION_NAMESPACE = ("https://docs.openstack.org/identity/api/ext/OS-FAKE/" "v1.0") EXTENSION_DESCRIBED = {"href": "https://github.com/openstack/identity-api", "rel": "describedby", "type": "text/html"} EXTENSION_ALIAS_FOO = "OS-FAKE-FOO" EXTENSION_NAME_FOO = "OpenStack Keystone Fake Extension Foo" EXTENSION_FOO = {"alias": EXTENSION_ALIAS_FOO, "description": "Fake Foo extension to V2.0 API.", "links": [EXTENSION_DESCRIBED], "name": EXTENSION_NAME_FOO, "namespace": EXTENSION_NAMESPACE, "updated": '2014-01-08T00:00:00Z'} EXTENSION_ALIAS_BAR = "OS-FAKE-BAR" EXTENSION_NAME_BAR = "OpenStack Keystone Fake Extension Bar" EXTENSION_BAR = {"alias": EXTENSION_ALIAS_BAR, "description": "Fake Bar extension to V2.0 API.", "links": [EXTENSION_DESCRIBED], "name": EXTENSION_NAME_BAR, "namespace": EXTENSION_NAMESPACE, "updated": '2014-01-08T00:00:00Z'} def _create_extension_list(extensions): return jsonutils.dumps({'extensions': {'values': extensions}}) EXTENSION_LIST = _create_extension_list([EXTENSION_FOO, EXTENSION_BAR]) class ClientDiscoveryTests(utils.TestCase): def test_discover_extensions_v2(self): self.requests_mock.get("%s/extensions" % V2_URL, text=EXTENSION_LIST) # Creating a HTTPClient not using session is deprecated. # creating a generic client at all is deprecated. with self.deprecations.expect_deprecations_here(): extensions = client.Client().discover_extensions(url=V2_URL) self.assertIn(EXTENSION_ALIAS_FOO, extensions) self.assertEqual(extensions[EXTENSION_ALIAS_FOO], EXTENSION_NAME_FOO) self.assertIn(EXTENSION_ALIAS_BAR, extensions) self.assertEqual(extensions[EXTENSION_ALIAS_BAR], EXTENSION_NAME_BAR) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_base.py0000664000175000017500000003722700000000000025725 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid import fixtures from keystoneauth1.identity import v2 from keystoneauth1 import session import requests from keystoneclient import base from keystoneclient import exceptions from keystoneclient.tests.unit import utils from keystoneclient import utils as base_utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import roles from keystoneclient.v3 import users TEST_REQUEST_ID = uuid.uuid4().hex TEST_REQUEST_ID_1 = uuid.uuid4().hex def create_response_with_request_id_header(): resp = requests.Response() resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID return resp class HumanReadable(base.Resource): HUMAN_ID = True class BaseTest(utils.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual(repr(r), "") def test_getid(self): self.assertEqual(base.getid(4), 4) class TmpObject(object): id = 4 self.assertEqual(base.getid(TmpObject), 4) def test_resource_lazy_getattr(self): auth = v2.Token(token=self.TEST_TOKEN, auth_url='http://127.0.0.1:5000') session_ = session.Session(auth=auth) self.client = client.Client(session=session_) self.useFixture(fixtures.MockPatchObject( self.client._adapter, 'get', side_effect=AttributeError, autospec=True)) f = roles.Role(self.client.roles, {'id': 1, 'name': 'Member'}) self.assertEqual(f.name, 'Member') # Missing stuff still fails after a second get self.assertRaises(AttributeError, getattr, f, 'blahblah') def test_eq(self): # Two resources with same ID: never equal if their info is not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertNotEqual(r1, r2) self.assertTrue(r1 != r2) # Two resources with same ID: equal if their info is equal # The truth of r1==r2 does not imply that r1!=r2 is false in PY2. # Test that inequality operator is defined and that comparing equal # items returns False r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertTrue(r1 == r2) self.assertFalse(r1 != r2) # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = roles.Role(None, {'id': 1}) self.assertNotEqual(r1, r2) self.assertTrue(r1 != r2) # Two resources with no ID: equal if their info is equal # The truth of r1==r2 does not imply that r1!=r2 is false in PY2. # Test that inequality operator is defined and that comparing equal # items returns False. r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertTrue(r1 == r2) self.assertFalse(r1 != r2) r1 = base.Resource(None, {'id': 1}) self.assertNotEqual(r1, object()) self.assertTrue(r1 != object()) self.assertNotEqual(r1, {'id': 1}) self.assertTrue(r1 != {'id': 1}) def test_human_id(self): r = base.Resource(None, {"name": "1 of !"}) self.assertIsNone(r.human_id) r = HumanReadable(None, {"name": "1 of !"}) self.assertEqual(r.human_id, "1-of") def test_non_ascii_attr(self): r_dict = {"name": "foobar", u"тест": "1234", u"тест2": u"привет мир"} r = base.Resource(None, r_dict) self.assertEqual(r.name, "foobar") self.assertEqual(r.to_dict(), r_dict) class ManagerTest(utils.TestCase): body = {"hello": {"hi": 1}} url = "/test-url" def setUp(self): super(ManagerTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_)._adapter self.mgr = base.Manager(self.client) self.mgr.resource_class = base.Resource def test_api(self): with self.deprecations.expect_deprecations_here(): self.assertEqual(self.mgr.api, self.client) def test_get(self): get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._get(self.url, "hello") get_mock.assert_called_once_with(self.url) self.assertEqual(rsrc.hi, 1) def test_post(self): post_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'post', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._post(self.url, self.body, "hello") post_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) post_mock.reset_mock() rsrc = self.mgr._post(self.url, self.body, "hello", return_raw=True) post_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc["hi"], 1) def test_put(self): put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._put(self.url, self.body, "hello") put_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) put_mock.reset_mock() rsrc = self.mgr._put(self.url, self.body) put_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hello["hi"], 1) def test_patch(self): patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._patch(self.url, self.body, "hello") patch_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hi, 1) patch_mock.reset_mock() rsrc = self.mgr._patch(self.url, self.body) patch_mock.assert_called_once_with(self.url, body=self.body) self.assertEqual(rsrc.hello["hi"], 1) def test_update(self): patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._update( self.url, body=self.body, response_key="hello", method="PATCH", management=False) patch_mock.assert_called_once_with( self.url, management=False, body=self.body) self.assertEqual(rsrc.hi, 1) rsrc = self.mgr._update( self.url, body=None, response_key="hello", method="PUT", management=True) put_mock.assert_called_once_with(self.url, management=True, body=None) self.assertEqual(rsrc.hi, 1) class ManagerRequestIdTest(utils.TestCase): url = "/test-url" resp = create_response_with_request_id_header() def setUp(self): super(ManagerRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter self.mgr = base.Manager(self.client) self.mgr.resource_class = base.Resource def mock_request_method(self, request_method, body): return self.useFixture(fixtures.MockPatchObject( self.client, request_method, autospec=True, return_value=(self.resp, body)) ).mock def test_get(self): body = {"hello": {"hi": 1}} get_mock = self.mock_request_method('get', body) rsrc = self.mgr._get(self.url, "hello") get_mock.assert_called_once_with(self.url) self.assertEqual(rsrc.data.hi, 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_list(self): body = {"hello": [{"name": "admin"}, {"name": "admin"}]} get_mock = self.mock_request_method('get', body) returned_list = self.mgr._list(self.url, "hello") self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url) def test_list_with_multiple_response_objects(self): body = {"hello": [{"name": "admin"}, {"name": "admin"}]} resp_1 = requests.Response() resp_1.headers['x-openstack-request-id'] = TEST_REQUEST_ID resp_2 = requests.Response() resp_2.headers['x-openstack-request-id'] = TEST_REQUEST_ID_1 resp_result = [resp_1, resp_2] get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(resp_result, body)) ).mock returned_list = self.mgr._list(self.url, "hello") self.assertIn(returned_list.request_ids[0], [ TEST_REQUEST_ID, TEST_REQUEST_ID_1]) self.assertIn(returned_list.request_ids[1], [ TEST_REQUEST_ID, TEST_REQUEST_ID_1]) get_mock.assert_called_once_with(self.url) def test_post(self): body = {"hello": {"hi": 1}} post_mock = self.mock_request_method('post', body) rsrc = self.mgr._post(self.url, body, "hello") post_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) post_mock.reset_mock() rsrc = self.mgr._post(self.url, body, "hello", return_raw=True) post_mock.assert_called_once_with(self.url, body=body) self.assertNotIsInstance(rsrc, base.Response) self.assertEqual(rsrc["hi"], 1) def test_put(self): body = {"hello": {"hi": 1}} put_mock = self.mock_request_method('put', body) rsrc = self.mgr._put(self.url, body, "hello") put_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) put_mock.reset_mock() rsrc = self.mgr._put(self.url, body) put_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hello["hi"], 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_head(self): get_mock = self.mock_request_method('head', None) rsrc = self.mgr._head(self.url) get_mock.assert_called_once_with(self.url) self.assertFalse(rsrc.data) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_delete(self): delete_mock = self.mock_request_method('delete', None) resp, base_resp = self.mgr._delete(self.url, name="hello") delete_mock.assert_called_once_with('/test-url', name='hello') self.assertEqual(base_resp.request_ids[0], TEST_REQUEST_ID) self.assertEqual(base_resp.data, None) self.assertIsInstance(resp, requests.Response) def test_patch(self): body = {"hello": {"hi": 1}} patch_mock = self.mock_request_method('patch', body) rsrc = self.mgr._patch(self.url, body, "hello") patch_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hi, 1) patch_mock.reset_mock() rsrc = self.mgr._patch(self.url, body) patch_mock.assert_called_once_with(self.url, body=body) self.assertEqual(rsrc.data.hello["hi"], 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) def test_update(self): body = {"hello": {"hi": 1}} patch_mock = self.mock_request_method('patch', body) put_mock = self.mock_request_method('put', body) rsrc = self.mgr._update( self.url, body=body, response_key="hello", method="PATCH", management=False) patch_mock.assert_called_once_with( self.url, management=False, body=body) self.assertEqual(rsrc.data.hi, 1) rsrc = self.mgr._update( self.url, body=None, response_key="hello", method="PUT", management=True) put_mock.assert_called_once_with(self.url, management=True, body=None) self.assertEqual(rsrc.data.hi, 1) self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID) class ManagerWithFindRequestIdTest(utils.TestCase): url = "/fakes" resp = create_response_with_request_id_header() def setUp(self): super(ManagerWithFindRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter def test_find_resource(self): body = {"roles": [{"name": 'entity_one'}, {"name": 'entity_one_1'}]} request_resp = requests.Response() request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, side_effect=[exceptions.NotFound, (request_resp, body)]) ).mock mgr = roles.RoleManager(self.client) mgr.resource_class = roles.Role response = base_utils.find_resource(mgr, 'entity_one') get_mock.assert_called_with('/OS-KSADM/roles') self.assertEqual(response.request_ids[0], TEST_REQUEST_ID) class CrudManagerRequestIdTest(utils.TestCase): resp = create_response_with_request_id_header() request_resp = requests.Response() request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID def setUp(self): super(CrudManagerRequestIdTest, self).setUp() auth = v2.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter def test_find_resource(self): body = {"users": [{"name": 'entity_one'}]} get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, side_effect=[exceptions.NotFound, (self.request_resp, body)]) ).mock mgr = users.UserManager(self.client) mgr.resource_class = users.User response = base_utils.find_resource(mgr, 'entity_one') get_mock.assert_called_with('/users?name=entity_one') self.assertEqual(response.request_ids[0], TEST_REQUEST_ID) def test_list(self): body = {"users": [{"name": "admin"}, {"name": "admin"}]} get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(self.request_resp, body)) ).mock mgr = users.UserManager(self.client) mgr.resource_class = users.User returned_list = mgr.list() self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID) get_mock.assert_called_once_with('/users?') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_cms.py0000664000175000017500000002042500000000000025565 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 errno import os import subprocess from unittest import mock import testresources from testtools import matchers from keystoneclient.common import cms from keystoneclient import exceptions from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils class CMSTest(utils.TestCase, testresources.ResourcedTestCase): """Unit tests for the keystoneclient.common.cms module.""" resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def __init__(self, *args, **kwargs): super(CMSTest, self).__init__(*args, **kwargs) process = subprocess.Popen(['openssl', 'version'], stdout=subprocess.PIPE) out, err = process.communicate() # Example output: 'OpenSSL 0.9.8za 5 Jun 2014' openssl_version = out.split()[1] if err or openssl_version.startswith(b'0'): raise Exception('Your version of OpenSSL is not supported. ' 'You will need to update it to 1.0 or later.') def _raise_OSError(*args): e = OSError() e.errno = errno.EPIPE raise e def test_cms_verify(self): self.assertRaises(exceptions.CertificateConfigError, cms.cms_verify, 'data', 'no_exist_cert_file', 'no_exist_ca_file') def test_token_tocms_to_token(self): with open(os.path.join(client_fixtures.CMSDIR, 'auth_token_scoped.pem')) as f: AUTH_TOKEN_SCOPED_CMS = f.read() self.assertEqual(cms.token_to_cms(self.examples.SIGNED_TOKEN_SCOPED), AUTH_TOKEN_SCOPED_CMS) tok = cms.cms_to_token(cms.token_to_cms( self.examples.SIGNED_TOKEN_SCOPED)) self.assertEqual(tok, self.examples.SIGNED_TOKEN_SCOPED) def test_asn1_token(self): self.assertTrue(cms.is_asn1_token(self.examples.SIGNED_TOKEN_SCOPED)) self.assertFalse(cms.is_asn1_token('FOOBAR')) def test_cms_sign_token_no_files(self): self.assertRaises(subprocess.CalledProcessError, cms.cms_sign_token, self.examples.TOKEN_SCOPED_DATA, '/no/such/file', '/no/such/key') def test_cms_sign_token_no_files_pkiz(self): self.assertRaises(subprocess.CalledProcessError, cms.pkiz_sign, self.examples.TOKEN_SCOPED_DATA, '/no/such/file', '/no/such/key') def test_cms_sign_token_success(self): self.assertTrue( cms.pkiz_sign(self.examples.TOKEN_SCOPED_DATA, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_KEY_FILE)) def test_cms_verify_token_no_files(self): self.assertRaises(exceptions.CertificateConfigError, cms.cms_verify, self.examples.SIGNED_TOKEN_SCOPED, '/no/such/file', '/no/such/key') def test_cms_verify_token_no_oserror(self): with mock.patch('subprocess.Popen.communicate', new=self._raise_OSError): try: cms.cms_verify("x", '/no/such/file', '/no/such/key') except exceptions.CertificateConfigError as e: self.assertIn('/no/such/file', e.output) self.assertIn('Hit OSError ', e.output) else: self.fail('Expected exceptions.CertificateConfigError') def test_cms_verify_token_scoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_TOKEN_SCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_scoped_expired(self): cms_content = cms.token_to_cms( self.examples.SIGNED_TOKEN_SCOPED_EXPIRED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_unscoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_TOKEN_UNSCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_verify_token_v3_scoped(self): cms_content = cms.token_to_cms(self.examples.SIGNED_v3_TOKEN_SCOPED) self.assertTrue(cms.cms_verify(cms_content, self.examples.SIGNING_CERT_FILE, self.examples.SIGNING_CA_FILE)) def test_cms_hash_token_no_token_id(self): token_id = None self.assertThat(cms.cms_hash_token(token_id), matchers.Is(None)) def test_cms_hash_token_not_pki(self): """If the token_id is not a PKI token then it returns the token_id.""" token = 'something' self.assertFalse(cms.is_asn1_token(token)) self.assertThat(cms.cms_hash_token(token), matchers.Is(token)) def test_cms_hash_token_default_md5(self): """The default hash method is md5.""" token = self.examples.SIGNED_TOKEN_SCOPED token_id_default = cms.cms_hash_token(token) token_id_md5 = cms.cms_hash_token(token, mode='md5') self.assertThat(token_id_default, matchers.Equals(token_id_md5)) # md5 hash is 32 chars. self.assertThat(token_id_default, matchers.HasLength(32)) def test_cms_hash_token_sha256(self): """Can also hash with sha256.""" token = self.examples.SIGNED_TOKEN_SCOPED token_id = cms.cms_hash_token(token, mode='sha256') # sha256 hash is 64 chars. self.assertThat(token_id, matchers.HasLength(64)) @mock.patch('keystoneclient.common.cms._check_files_accessible') def test_process_communicate_handle_oserror_epipe(self, files_acc_mock): process_mock = mock.Mock() process_mock.communicate = self._raise_OSError process_mock.stderr = mock.Mock() process_mock.stderr.read = mock.Mock(return_value='proc stderr') files_acc_mock.return_value = 1, ('file_path', 'fileerror') output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual((output, retcode), ('', 1)) self.assertIn('file_path', err) self.assertIn('fileerror', err) self.assertIn('proc stderr', err) @mock.patch('keystoneclient.common.cms._check_files_accessible') def test_process_communicate_handle_oserror_epipe_files_ok( self, files_acc_mock): process_mock = mock.Mock() process_mock.communicate = self._raise_OSError process_mock.stderr = mock.Mock() process_mock.stderr.read = mock.Mock(return_value='proc stderr') files_acc_mock.return_value = -1, None output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual((output, retcode), ('', -1)) self.assertIn('proc stderr', err) def test_process_communicate_handle_oserror_no_exception(self): process_mock = mock.Mock() process_mock.communicate.return_value = 'out', 'err' process_mock.poll.return_value = 0 output, err, retcode = cms._process_communicate_handle_oserror( process_mock, '', []) self.assertEqual(output, 'out') self.assertEqual(err, 'err') self.assertEqual(retcode, 0) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_discovery.py0000664000175000017500000007334700000000000027025 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 re import uuid from keystoneauth1 import fixture from oslo_serialization import jsonutils from testtools import matchers from keystoneclient import _discover from keystoneclient.auth import token_endpoint from keystoneclient import client from keystoneclient import discover from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client BASE_HOST = 'http://keystone.example.com' BASE_URL = "%s:5000/" % BASE_HOST UPDATED = '2013-03-06T00:00:00Z' TEST_SERVICE_CATALOG = [{ "endpoints": [{ "adminURL": "%s:8774/v1.0" % BASE_HOST, "region": "RegionOne", "internalURL": "%s://127.0.0.1:8774/v1.0" % BASE_HOST, "publicURL": "%s:8774/v1.0/" % BASE_HOST }], "type": "nova_compat", "name": "nova_compat" }, { "endpoints": [{ "adminURL": "http://nova/novapi/admin", "region": "RegionOne", "internalURL": "http://nova/novapi/internal", "publicURL": "http://nova/novapi/public" }], "type": "compute", "name": "nova" }, { "endpoints": [{ "adminURL": "http://glance/glanceapi/admin", "region": "RegionOne", "internalURL": "http://glance/glanceapi/internal", "publicURL": "http://glance/glanceapi/public" }], "type": "image", "name": "glance" }, { "endpoints": [{ "adminURL": "%s:35357/v2.0" % BASE_HOST, "region": "RegionOne", "internalURL": "%s:5000/v2.0" % BASE_HOST, "publicURL": "%s:5000/v2.0" % BASE_HOST }], "type": "identity", "name": "keystone" }, { "endpoints": [{ "adminURL": "http://swift/swiftapi/admin", "region": "RegionOne", "internalURL": "http://swift/swiftapi/internal", "publicURL": "http://swift/swiftapi/public" }], "type": "object-store", "name": "swift" }] V2_URL = "%sv2.0" % BASE_URL V2_VERSION = fixture.V2Discovery(V2_URL) V2_VERSION.updated_str = UPDATED V2_AUTH_RESPONSE = jsonutils.dumps({ "access": { "token": { "expires": "2999-01-01T00:00:10.000123Z", "id": 'fakeToken', "tenant": { "id": '1' }, }, "user": { "id": 'test' }, "serviceCatalog": TEST_SERVICE_CATALOG, }, }) V3_URL = "%sv3" % BASE_URL V3_VERSION = fixture.V3Discovery(V3_URL) V3_MEDIA_TYPES = V3_VERSION.media_types V3_VERSION.updated_str = UPDATED V3_TOKEN = ('3e2813b7ba0b4006840c3825860b86ed',) V3_AUTH_RESPONSE = jsonutils.dumps({ "token": { "methods": [ "token", "password" ], "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": '1', "name": 'test-domain' }, "id": '1', "name": 'test-project' }, "user": { "domain": { "id": '1', "name": 'test-domain' }, "id": '1', "name": 'test-user' }, "issued_at": "2013-05-29T16:55:21.468960Z", }, }) CINDER_EXAMPLES = { "versions": [ { "status": "CURRENT", "updated": "2012-01-04T11:33:21Z", "id": "v1.0", "links": [ { "href": "%sv1/" % BASE_URL, "rel": "self" } ] }, { "status": "CURRENT", "updated": "2012-11-21T11:33:21Z", "id": "v2.0", "links": [ { "href": "%sv2/" % BASE_URL, "rel": "self" } ] } ] } GLANCE_EXAMPLES = { "versions": [ { "status": "CURRENT", "id": "v2.2", "links": [ { "href": "%sv2/" % BASE_URL, "rel": "self" } ] }, { "status": "SUPPORTED", "id": "v2.1", "links": [ { "href": "%sv2/" % BASE_URL, "rel": "self" } ] }, { "status": "SUPPORTED", "id": "v2.0", "links": [ { "href": "%sv2/" % BASE_URL, "rel": "self" } ] }, { "status": "CURRENT", "id": "v1.1", "links": [ { "href": "%sv1/" % BASE_URL, "rel": "self" } ] }, { "status": "SUPPORTED", "id": "v1.0", "links": [ { "href": "%sv1/" % BASE_URL, "rel": "self" } ] } ] } def _create_version_list(versions): return jsonutils.dumps({'versions': {'values': versions}}) def _create_single_version(version): return jsonutils.dumps({'version': version}) V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION]) V2_VERSION_LIST = _create_version_list([V2_VERSION]) V3_VERSION_ENTRY = _create_single_version(V3_VERSION) V2_VERSION_ENTRY = _create_single_version(V2_VERSION) class AvailableVersionsTests(utils.TestCase): def setUp(self): super(AvailableVersionsTests, self).setUp() self.deprecations.expect_deprecations() def test_available_versions_basics(self): examples = {'keystone': V3_VERSION_LIST, 'cinder': jsonutils.dumps(CINDER_EXAMPLES), 'glance': jsonutils.dumps(GLANCE_EXAMPLES)} for path, text in examples.items(): url = "%s%s" % (BASE_URL, path) self.requests_mock.get(url, status_code=300, text=text) versions = discover.available_versions(url) for v in versions: for n in ('id', 'status', 'links'): msg = '%s missing from %s version data' % (n, path) self.assertThat(v, matchers.Annotate(msg, matchers.Contains(n))) def test_available_versions_individual(self): self.requests_mock.get(V3_URL, status_code=200, text=V3_VERSION_ENTRY) versions = discover.available_versions(V3_URL) for v in versions: self.assertEqual(v['id'], 'v3.0') self.assertEqual(v['status'], 'stable') self.assertIn('media-types', v) self.assertIn('links', v) def test_available_keystone_data(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) versions = discover.available_versions(BASE_URL) self.assertEqual(2, len(versions)) for v in versions: self.assertIn(v['id'], ('v2.0', 'v3.0')) self.assertEqual(v['updated'], UPDATED) self.assertEqual(v['status'], 'stable') if v['id'] == 'v3.0': self.assertEqual(v['media-types'], V3_MEDIA_TYPES) def test_available_cinder_data(self): text = jsonutils.dumps(CINDER_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=300, text=text) versions = discover.available_versions(BASE_URL) self.assertEqual(2, len(versions)) for v in versions: self.assertEqual(v['status'], 'CURRENT') if v['id'] == 'v1.0': self.assertEqual(v['updated'], '2012-01-04T11:33:21Z') elif v['id'] == 'v2.0': self.assertEqual(v['updated'], '2012-11-21T11:33:21Z') else: self.fail("Invalid version found") def test_available_glance_data(self): text = jsonutils.dumps(GLANCE_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=200, text=text) versions = discover.available_versions(BASE_URL) self.assertEqual(5, len(versions)) for v in versions: if v['id'] in ('v2.2', 'v1.1'): self.assertEqual(v['status'], 'CURRENT') elif v['id'] in ('v2.1', 'v2.0', 'v1.0'): self.assertEqual(v['status'], 'SUPPORTED') else: self.fail("Invalid version found") class ClientDiscoveryTests(utils.TestCase): def setUp(self): super(ClientDiscoveryTests, self).setUp() self.deprecations.expect_deprecations() def assertCreatesV3(self, **kwargs): self.requests_mock.post('%s/auth/tokens' % V3_URL, text=V3_AUTH_RESPONSE, headers={'X-Subject-Token': V3_TOKEN}) kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') keystone = client.Client(**kwargs) self.assertIsInstance(keystone, v3_client.Client) return keystone def assertCreatesV2(self, **kwargs): self.requests_mock.post("%s/tokens" % V2_URL, text=V2_AUTH_RESPONSE) kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') keystone = client.Client(**kwargs) self.assertIsInstance(keystone, v2_client.Client) return keystone def assertVersionNotAvailable(self, **kwargs): kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') self.assertRaises(exceptions.VersionNotAvailable, client.Client, **kwargs) def assertDiscoveryFailure(self, **kwargs): kwargs.setdefault('username', 'foo') kwargs.setdefault('password', 'bar') self.assertRaises(exceptions.DiscoveryFailure, client.Client, **kwargs) def test_discover_v3(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertCreatesV3(auth_url=BASE_URL) def test_discover_v2(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) self.requests_mock.post("%s/tokens" % V2_URL, text=V2_AUTH_RESPONSE) self.assertCreatesV2(auth_url=BASE_URL) def test_discover_endpoint_v2(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) self.assertCreatesV2(endpoint=BASE_URL, token='fake-token') def test_discover_endpoint_v3(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertCreatesV3(endpoint=BASE_URL, token='fake-token') def test_discover_invalid_major_version(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertVersionNotAvailable(auth_url=BASE_URL, version=5) def test_discover_200_response_fails(self): self.requests_mock.get(BASE_URL, text='ok') self.assertDiscoveryFailure(auth_url=BASE_URL) def test_discover_minor_greater_than_available_fails(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.assertVersionNotAvailable(endpoint=BASE_URL, version=3.4) def test_discover_individual_version_v2(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.assertCreatesV2(auth_url=V2_URL) def test_discover_individual_version_v3(self): self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertCreatesV3(auth_url=V3_URL) def test_discover_individual_endpoint_v2(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.assertCreatesV2(endpoint=V2_URL, token='fake-token') def test_discover_individual_endpoint_v3(self): self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertCreatesV3(endpoint=V3_URL, token='fake-token') def test_discover_fail_to_create_bad_individual_version(self): self.requests_mock.get(V2_URL, text=V2_VERSION_ENTRY) self.requests_mock.get(V3_URL, text=V3_VERSION_ENTRY) self.assertVersionNotAvailable(auth_url=V2_URL, version=3) self.assertVersionNotAvailable(auth_url=V3_URL, version=2) def test_discover_unstable_versions(self): version_list = fixture.DiscoveryList(BASE_URL, v3_status='beta') self.requests_mock.get(BASE_URL, status_code=300, json=version_list) self.assertCreatesV2(auth_url=BASE_URL) self.assertVersionNotAvailable(auth_url=BASE_URL, version=3) self.assertCreatesV3(auth_url=BASE_URL, unstable=True) def test_discover_forwards_original_ip(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) ip = '192.168.1.1' self.assertCreatesV3(auth_url=BASE_URL, original_ip=ip) self.assertThat(self.requests_mock.last_request.headers['forwarded'], matchers.Contains(ip)) def test_discover_bad_args(self): self.assertRaises(exceptions.DiscoveryFailure, client.Client) def test_discover_bad_response(self): self.requests_mock.get(BASE_URL, status_code=300, json={'FOO': 'BAR'}) self.assertDiscoveryFailure(auth_url=BASE_URL) def test_discovery_ignore_invalid(self): resp = [{'id': 'v3.0', 'links': [1, 2, 3, 4], # invalid links 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED}] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list(resp)) self.assertDiscoveryFailure(auth_url=BASE_URL) def test_ignore_entry_without_links(self): v3 = V3_VERSION.copy() v3['links'] = [] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list([v3, V2_VERSION])) self.assertCreatesV2(auth_url=BASE_URL) def test_ignore_entry_without_status(self): v3 = V3_VERSION.copy() del v3['status'] self.requests_mock.get(BASE_URL, status_code=300, text=_create_version_list([v3, V2_VERSION])) self.assertCreatesV2(auth_url=BASE_URL) def test_greater_version_than_required(self): versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.6') self.requests_mock.get(BASE_URL, json=versions) self.assertCreatesV3(auth_url=BASE_URL, version=(3, 4)) def test_lesser_version_than_required(self): versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.4') self.requests_mock.get(BASE_URL, json=versions) self.assertVersionNotAvailable(auth_url=BASE_URL, version=(3, 6)) def test_bad_response(self): self.requests_mock.get(BASE_URL, status_code=300, text="Ugly Duckling") self.assertDiscoveryFailure(auth_url=BASE_URL) def test_pass_client_arguments(self): self.requests_mock.get(BASE_URL, status_code=300, text=V2_VERSION_LIST) kwargs = {'original_ip': '100', 'use_keyring': False, 'stale_duration': 15} cl = self.assertCreatesV2(auth_url=BASE_URL, **kwargs) with self.deprecations.expect_deprecations_here(): self.assertEqual(cl.original_ip, '100') self.assertEqual(cl.stale_duration, 15) self.assertFalse(cl.use_keyring) def test_overriding_stored_kwargs(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) self.requests_mock.post("%s/auth/tokens" % V3_URL, text=V3_AUTH_RESPONSE, headers={'X-Subject-Token': V3_TOKEN}) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL, debug=False, username='foo') client = disc.create_client(debug=True, password='bar') self.assertIsInstance(client, v3_client.Client) self.assertFalse(disc._client_kwargs['debug']) self.assertEqual(client.username, 'foo') self.assertEqual(client.password, 'bar') def test_available_versions(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_ENTRY) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) with self.deprecations.expect_deprecations_here(): versions = disc.available_versions() self.assertEqual(1, len(versions)) self.assertEqual(V3_VERSION, versions[0]) def test_unknown_client_version(self): V4_VERSION = {'id': 'v4.0', 'links': [{'href': 'http://url', 'rel': 'self'}], 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED} versions = fixture.DiscoveryList() versions.add_version(V4_VERSION) self.requests_mock.get(BASE_URL, status_code=300, json=versions) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=4) def test_discovery_fail_for_missing_v3(self): versions = fixture.DiscoveryList(v2=True, v3=False) self.requests_mock.get(BASE_URL, status_code=300, json=versions) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=(3, 0)) def _do_discovery_call(self, token=None, **kwargs): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) if not token: token = uuid.uuid4().hex url = 'http://testurl' with self.deprecations.expect_deprecations_here(): a = token_endpoint.Token(url, token) s = session.Session(auth=a) # will default to true as there is a plugin on the session discover.Discover(s, auth_url=BASE_URL, **kwargs) self.assertEqual(BASE_URL, self.requests_mock.last_request.url) def test_setting_authenticated_true(self): token = uuid.uuid4().hex self._do_discovery_call(token) self.assertRequestHeaderEqual('X-Auth-Token', token) def test_setting_authenticated_false(self): self._do_discovery_call(authenticated=False) self.assertNotIn('X-Auth-Token', self.requests_mock.last_request.headers) class DiscoverQueryTests(utils.TestCase): def test_available_keystone_data(self): self.requests_mock.get(BASE_URL, status_code=300, text=V3_VERSION_LIST) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((2, 0), versions[0]['version']) self.assertEqual('stable', versions[0]['raw_status']) self.assertEqual(V2_URL, versions[0]['url']) self.assertEqual((3, 0), versions[1]['version']) self.assertEqual('stable', versions[1]['raw_status']) self.assertEqual(V3_URL, versions[1]['url']) version = disc.data_for('v3.0') self.assertEqual((3, 0), version['version']) self.assertEqual('stable', version['raw_status']) self.assertEqual(V3_URL, version['url']) version = disc.data_for(2) self.assertEqual((2, 0), version['version']) self.assertEqual('stable', version['raw_status']) self.assertEqual(V2_URL, version['url']) self.assertIsNone(disc.url_for('v4')) self.assertEqual(V3_URL, disc.url_for('v3')) self.assertEqual(V2_URL, disc.url_for('v2')) def test_available_cinder_data(self): text = jsonutils.dumps(CINDER_EXAMPLES) self.requests_mock.get(BASE_URL, status_code=300, text=text) v1_url = "%sv1/" % BASE_URL v2_url = "%sv2/" % BASE_URL # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((1, 0), versions[0]['version']) self.assertEqual('CURRENT', versions[0]['raw_status']) self.assertEqual(v1_url, versions[0]['url']) self.assertEqual((2, 0), versions[1]['version']) self.assertEqual('CURRENT', versions[1]['raw_status']) self.assertEqual(v2_url, versions[1]['url']) version = disc.data_for('v2.0') self.assertEqual((2, 0), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v2_url, version['url']) version = disc.data_for(1) self.assertEqual((1, 0), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v1_url, version['url']) self.assertIsNone(disc.url_for('v3')) self.assertEqual(v2_url, disc.url_for('v2')) self.assertEqual(v1_url, disc.url_for('v1')) def test_available_glance_data(self): text = jsonutils.dumps(GLANCE_EXAMPLES) self.requests_mock.get(BASE_URL, text=text) v1_url = "%sv1/" % BASE_URL v2_url = "%sv2/" % BASE_URL # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual((1, 0), versions[0]['version']) self.assertEqual('SUPPORTED', versions[0]['raw_status']) self.assertEqual(v1_url, versions[0]['url']) self.assertEqual((1, 1), versions[1]['version']) self.assertEqual('CURRENT', versions[1]['raw_status']) self.assertEqual(v1_url, versions[1]['url']) self.assertEqual((2, 0), versions[2]['version']) self.assertEqual('SUPPORTED', versions[2]['raw_status']) self.assertEqual(v2_url, versions[2]['url']) self.assertEqual((2, 1), versions[3]['version']) self.assertEqual('SUPPORTED', versions[3]['raw_status']) self.assertEqual(v2_url, versions[3]['url']) self.assertEqual((2, 2), versions[4]['version']) self.assertEqual('CURRENT', versions[4]['raw_status']) self.assertEqual(v2_url, versions[4]['url']) for ver in (2, 2.1, 2.2): version = disc.data_for(ver) self.assertEqual((2, 2), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v2_url, version['url']) self.assertEqual(v2_url, disc.url_for(ver)) for ver in (1, 1.1): version = disc.data_for(ver) self.assertEqual((1, 1), version['version']) self.assertEqual('CURRENT', version['raw_status']) self.assertEqual(v1_url, version['url']) self.assertEqual(v1_url, disc.url_for(ver)) self.assertIsNone(disc.url_for('v3')) self.assertIsNone(disc.url_for('v2.3')) def test_allow_deprecated(self): status = 'deprecated' version_list = [{'id': 'v3.0', 'links': [{'href': V3_URL, 'rel': 'self'}], 'media-types': V3_MEDIA_TYPES, 'status': status, 'updated': UPDATED}] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) # deprecated is allowed by default versions = disc.version_data(allow_deprecated=False) self.assertEqual(0, len(versions)) versions = disc.version_data(allow_deprecated=True) self.assertEqual(1, len(versions)) self.assertEqual(status, versions[0]['raw_status']) self.assertEqual(V3_URL, versions[0]['url']) self.assertEqual((3, 0), versions[0]['version']) def test_allow_experimental(self): status = 'experimental' version_list = [{'id': 'v3.0', 'links': [{'href': V3_URL, 'rel': 'self'}], 'media-types': V3_MEDIA_TYPES, 'status': status, 'updated': UPDATED}] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual(0, len(versions)) versions = disc.version_data(allow_experimental=True) self.assertEqual(1, len(versions)) self.assertEqual(status, versions[0]['raw_status']) self.assertEqual(V3_URL, versions[0]['url']) self.assertEqual((3, 0), versions[0]['version']) def test_allow_unknown(self): status = 'abcdef' version_list = fixture.DiscoveryList(BASE_URL, v2=False, v3_status=status) self.requests_mock.get(BASE_URL, json=version_list) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) versions = disc.version_data() self.assertEqual(0, len(versions)) versions = disc.version_data(allow_unknown=True) self.assertEqual(1, len(versions)) self.assertEqual(status, versions[0]['raw_status']) self.assertEqual(V3_URL, versions[0]['url']) self.assertEqual((3, 0), versions[0]['version']) def test_ignoring_invalid_lnks(self): version_list = [{'id': 'v3.0', 'links': [{'href': V3_URL, 'rel': 'self'}], 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED}, {'id': 'v3.1', 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED}, {'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED, 'links': [{'href': V3_URL, 'rel': 'self'}], }] text = jsonutils.dumps({'versions': version_list}) self.requests_mock.get(BASE_URL, text=text) # Creating Discover not using session is deprecated. with self.deprecations.expect_deprecations_here(): disc = discover.Discover(auth_url=BASE_URL) # raw_version_data will return all choices, even invalid ones versions = disc.raw_version_data() self.assertEqual(3, len(versions)) # only the version with both id and links will be actually returned versions = disc.version_data() self.assertEqual(1, len(versions)) class CatalogHackTests(utils.TestCase): TEST_URL = 'http://keystone.server:5000/v2.0' OTHER_URL = 'http://other.server:5000/path' IDENTITY = 'identity' BASE_URL = 'http://keystone.server:5000/' V2_URL = BASE_URL + 'v2.0' V3_URL = BASE_URL + 'v3' def setUp(self): super(CatalogHackTests, self).setUp() self.hacks = _discover._VersionHacks() self.hacks.add_discover_hack(self.IDENTITY, re.compile('/v2.0/?$'), '/') def test_version_hacks(self): self.assertEqual(self.BASE_URL, self.hacks.get_discover_hack(self.IDENTITY, self.V2_URL)) self.assertEqual(self.BASE_URL, self.hacks.get_discover_hack(self.IDENTITY, self.V2_URL + '/')) self.assertEqual(self.OTHER_URL, self.hacks.get_discover_hack(self.IDENTITY, self.OTHER_URL)) def test_ignored_non_service_type(self): self.assertEqual(self.V2_URL, self.hacks.get_discover_hack('other', self.V2_URL)) class DiscoverUtils(utils.TestCase): def test_version_number(self): def assertVersion(inp, out): self.assertEqual(out, _discover.normalize_version_number(inp)) def versionRaises(inp): self.assertRaises(TypeError, _discover.normalize_version_number, inp) assertVersion('v1.2', (1, 2)) assertVersion('v11', (11, 0)) assertVersion('1.2', (1, 2)) assertVersion('1.5.1', (1, 5, 1)) assertVersion('1', (1, 0)) assertVersion(1, (1, 0)) assertVersion(5.2, (5, 2)) assertVersion((6, 1), (6, 1)) assertVersion([1, 4], (1, 4)) versionRaises('hello') versionRaises('1.a') versionRaises('vacuum') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_ec2utils.py0000664000175000017500000003261400000000000026540 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 testtools from keystoneclient.contrib.ec2 import utils from keystoneclient.tests.unit import client_fixtures class Ec2SignerTest(testtools.TestCase): def setUp(self): super(Ec2SignerTest, self).setUp() self.useFixture(client_fixtures.Deprecations()) self.access = '966afbde20b84200ae4e62e09acf46b2' self.secret = '89cdf9e94e2643cab35b8b8ac5a51f83' self.signer = utils.Ec2Signer(self.secret) def test_v4_creds_header(self): auth_str = 'AWS4-HMAC-SHA256 blah' credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {}, 'headers': {'Authorization': auth_str}} self.assertTrue(self.signer._v4_creds(credentials)) def test_v4_creds_param(self): credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'X-Amz-Algorithm': 'AWS4-HMAC-SHA256'}, 'headers': {}} self.assertTrue(self.signer._v4_creds(credentials)) def test_v4_creds_false(self): credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '0', 'AWSAccessKeyId': self.access, 'Timestamp': '2012-11-27T11:47:02Z', 'Action': 'Foo'}} self.assertFalse(self.signer._v4_creds(credentials)) def test_generate_0(self): """Test generate function for v0 signature.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '0', 'AWSAccessKeyId': self.access, 'Timestamp': '2012-11-27T11:47:02Z', 'Action': 'Foo'}} signature = self.signer.generate(credentials) expected = 'SmXQEZAUdQw5glv5mX8mmixBtas=' self.assertEqual(signature, expected) def test_generate_1(self): """Test generate function for v1 signature.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '1', 'AWSAccessKeyId': self.access}} signature = self.signer.generate(credentials) expected = 'VRnoQH/EhVTTLhwRLfuL7jmFW9c=' self.assertEqual(signature, expected) def test_generate_v2_SHA256(self): """Test generate function for v2 signature, SHA256.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '2', 'AWSAccessKeyId': self.access}} signature = self.signer.generate(credentials) expected = 'odsGmT811GffUO0Eu13Pq+xTzKNIjJ6NhgZU74tYX/w=' self.assertEqual(signature, expected) def test_generate_v2_SHA1(self): """Test generate function for v2 signature, SHA1.""" credentials = {'host': '127.0.0.1', 'verb': 'GET', 'path': '/v1/', 'params': {'SignatureVersion': '2', 'AWSAccessKeyId': self.access}} self.signer.hmac_256 = None signature = self.signer.generate(credentials) expected = 'ZqCxMI4ZtTXWI175743mJ0hy/Gc=' self.assertEqual(signature, expected) def test_generate_v4(self): """Test v4 generator with data from AWS docs example. see: http://docs.aws.amazon.com/general/latest/gr/ sigv4-create-canonical-request.html and http://docs.aws.amazon.com/general/latest/gr/ sigv4-signed-request-examples.html """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'iam.amazonaws.com', 'Authorization': auth_str} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {'Action': 'CreateUser', 'UserName': 'NewUser', 'Version': '2010-05-08', 'X-Amz-Algorithm': 'AWS4-HMAC-SHA256', 'X-Amz-Credential': 'AKIAEXAMPLE/20140611/' 'us-east-1/iam/aws4_request', 'X-Amz-Date': '20140611T231318Z', 'X-Amz-Expires': '30', 'X-Amz-SignedHeaders': 'host', 'X-Amz-Signature': 'ced6826de92d2bdeed8f846f0bf508e8' '559e98e4b0199114b84c54174deb456c'} credentials = {'host': 'iam.amazonaws.com', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('ced6826de92d2bdeed8f846f0bf508e8' '559e98e4b0199114b84c54174deb456c') self.assertEqual(signature, expected) def test_generate_v4_port(self): """Test v4 generator with host:port format.""" # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(signature, expected) def test_generate_v4_port_strip(self): """Test v4 generator with host:port format for old boto version. Validate for old (<2.9.3) version of boto, where the port should be stripped to match boto behavior. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.9.2 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('9a4b2276a5039ada3b90f72ea8ec1745' '14b92b909fb106b22ad910c5d75a54f4') self.assertEqual(expected, signature) def test_generate_v4_port_nostrip(self): """Test v4 generator with host:port format for new boto version. Validate for new (>=2.9.3) version of boto, where the port should not be stripped. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.9.3 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(expected, signature) def test_generate_v4_port_malformed_version(self): """Test v4 generator with host:port format for malformed boto version. Validate for malformed version of boto, where the port should not be stripped. """ # Create a new signer object with the AWS example key secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' signer = utils.Ec2Signer(secret) body_hash = ('b6359072c78d70ebee1e81adcbab4f0' '1bf2c23245fa365ef83fe8f1f955085e2') auth_str = ('AWS4-HMAC-SHA256 ' 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' 'us-east-1/iam/aws4_request,' 'SignedHeaders=content-type;host;x-amz-date,') headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-Amz-Date': '20110909T233600Z', 'Host': 'foo:8000', 'Authorization': auth_str, 'User-Agent': 'Boto/2.922 (linux2)'} # Note the example in the AWS docs is inconsistent, previous # examples specify no query string, but the final POST example # does, apparently incorrectly since an empty parameter list # aligns all steps and the final signature with the examples params = {} credentials = {'host': 'foo:8000', 'verb': 'POST', 'path': '/', 'params': params, 'headers': headers, 'body_hash': body_hash} signature = signer.generate(credentials) expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(expected, signature) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_fixtures.py0000664000175000017500000002321300000000000026652 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 keystoneclient import fixture from keystoneclient.tests.unit import utils class V2TokenTests(utils.TestCase): def test_unscoped(self): token_id = uuid.uuid4().hex user_id = uuid.uuid4().hex user_name = uuid.uuid4().hex token = fixture.V2Token(token_id=token_id, user_id=user_id, user_name=user_name) self.assertEqual(token_id, token.token_id) self.assertEqual(token_id, token['access']['token']['id']) self.assertEqual(user_id, token.user_id) self.assertEqual(user_id, token['access']['user']['id']) self.assertEqual(user_name, token.user_name) self.assertEqual(user_name, token['access']['user']['name']) self.assertIsNone(token.trust_id) def test_tenant_scoped(self): tenant_id = uuid.uuid4().hex tenant_name = uuid.uuid4().hex token = fixture.V2Token(tenant_id=tenant_id, tenant_name=tenant_name) self.assertEqual(tenant_id, token.tenant_id) self.assertEqual(tenant_id, token['access']['token']['tenant']['id']) self.assertEqual(tenant_name, token.tenant_name) tn = token['access']['token']['tenant']['name'] self.assertEqual(tenant_name, tn) self.assertIsNone(token.trust_id) def test_trust_scoped(self): trust_id = uuid.uuid4().hex trustee_user_id = uuid.uuid4().hex token = fixture.V2Token(trust_id=trust_id, trustee_user_id=trustee_user_id) trust = token['access']['trust'] self.assertEqual(trust_id, token.trust_id) self.assertEqual(trust_id, trust['id']) self.assertEqual(trustee_user_id, token.trustee_user_id) self.assertEqual(trustee_user_id, trust['trustee_user_id']) def test_roles(self): role_id1 = uuid.uuid4().hex role_name1 = uuid.uuid4().hex role_id2 = uuid.uuid4().hex role_name2 = uuid.uuid4().hex token = fixture.V2Token() token.add_role(id=role_id1, name=role_name1) token.add_role(id=role_id2, name=role_name2) role_names = token['access']['user']['roles'] role_ids = token['access']['metadata']['roles'] self.assertEqual(set([role_id1, role_id2]), set(role_ids)) for r in (role_name1, role_name2): self.assertIn({'name': r}, role_names) def test_services(self): service_type = uuid.uuid4().hex service_name = uuid.uuid4().hex endpoint_id = uuid.uuid4().hex region = uuid.uuid4().hex public = uuid.uuid4().hex admin = uuid.uuid4().hex internal = uuid.uuid4().hex token = fixture.V2Token() svc = token.add_service(type=service_type, name=service_name) svc.add_endpoint(public=public, admin=admin, internal=internal, region=region, id=endpoint_id) self.assertEqual(1, len(token['access']['serviceCatalog'])) service = token['access']['serviceCatalog'][0]['endpoints'][0] self.assertEqual(public, service['publicURL']) self.assertEqual(internal, service['internalURL']) self.assertEqual(admin, service['adminURL']) self.assertEqual(region, service['region']) self.assertEqual(endpoint_id, service['id']) class V3TokenTests(utils.TestCase): def test_unscoped(self): user_id = uuid.uuid4().hex user_name = uuid.uuid4().hex user_domain_id = uuid.uuid4().hex user_domain_name = uuid.uuid4().hex token = fixture.V3Token(user_id=user_id, user_name=user_name, user_domain_id=user_domain_id, user_domain_name=user_domain_name) self.assertEqual(user_id, token.user_id) self.assertEqual(user_id, token['token']['user']['id']) self.assertEqual(user_name, token.user_name) self.assertEqual(user_name, token['token']['user']['name']) user_domain = token['token']['user']['domain'] self.assertEqual(user_domain_id, token.user_domain_id) self.assertEqual(user_domain_id, user_domain['id']) self.assertEqual(user_domain_name, token.user_domain_name) self.assertEqual(user_domain_name, user_domain['name']) def test_project_scoped(self): project_id = uuid.uuid4().hex project_name = uuid.uuid4().hex project_domain_id = uuid.uuid4().hex project_domain_name = uuid.uuid4().hex token = fixture.V3Token(project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name) self.assertEqual(project_id, token.project_id) self.assertEqual(project_id, token['token']['project']['id']) self.assertEqual(project_name, token.project_name) self.assertEqual(project_name, token['token']['project']['name']) project_domain = token['token']['project']['domain'] self.assertEqual(project_domain_id, token.project_domain_id) self.assertEqual(project_domain_id, project_domain['id']) self.assertEqual(project_domain_name, token.project_domain_name) self.assertEqual(project_domain_name, project_domain['name']) def test_domain_scoped(self): domain_id = uuid.uuid4().hex domain_name = uuid.uuid4().hex token = fixture.V3Token(domain_id=domain_id, domain_name=domain_name) self.assertEqual(domain_id, token.domain_id) self.assertEqual(domain_id, token['token']['domain']['id']) self.assertEqual(domain_name, token.domain_name) self.assertEqual(domain_name, token['token']['domain']['name']) def test_roles(self): role1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} role2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} token = fixture.V3Token() token.add_role(**role1) token.add_role(**role2) self.assertEqual(2, len(token['token']['roles'])) self.assertIn(role1, token['token']['roles']) self.assertIn(role2, token['token']['roles']) def test_trust_scoped(self): trust_id = uuid.uuid4().hex trustee_user_id = uuid.uuid4().hex trustor_user_id = uuid.uuid4().hex impersonation = True token = fixture.V3Token(trust_id=trust_id, trustee_user_id=trustee_user_id, trustor_user_id=trustor_user_id, trust_impersonation=impersonation) trust = token['token']['OS-TRUST:trust'] self.assertEqual(trust_id, token.trust_id) self.assertEqual(trust_id, trust['id']) self.assertEqual(trustee_user_id, token.trustee_user_id) self.assertEqual(trustee_user_id, trust['trustee_user']['id']) self.assertEqual(trustor_user_id, token.trustor_user_id) self.assertEqual(trustor_user_id, trust['trustor_user']['id']) self.assertEqual(impersonation, token.trust_impersonation) self.assertEqual(impersonation, trust['impersonation']) def test_oauth_scoped(self): access_id = uuid.uuid4().hex consumer_id = uuid.uuid4().hex token = fixture.V3Token(oauth_access_token_id=access_id, oauth_consumer_id=consumer_id) oauth = token['token']['OS-OAUTH1'] self.assertEqual(access_id, token.oauth_access_token_id) self.assertEqual(access_id, oauth['access_token_id']) self.assertEqual(consumer_id, token.oauth_consumer_id) self.assertEqual(consumer_id, oauth['consumer_id']) def test_catalog(self): service_type = uuid.uuid4().hex service_name = uuid.uuid4().hex service_id = uuid.uuid4().hex region = uuid.uuid4().hex endpoints = {'public': uuid.uuid4().hex, 'internal': uuid.uuid4().hex, 'admin': uuid.uuid4().hex} token = fixture.V3Token() svc = token.add_service(type=service_type, name=service_name, id=service_id) svc.add_standard_endpoints(region=region, **endpoints) self.assertEqual(1, len(token['token']['catalog'])) service = token['token']['catalog'][0] self.assertEqual(3, len(service['endpoints'])) self.assertEqual(service_name, service['name']) self.assertEqual(service_type, service['type']) self.assertEqual(service_id, service['id']) for endpoint in service['endpoints']: # assert an id exists for each endpoint, remove it to make testing # the endpoint content below easier. self.assertTrue(endpoint.pop('id')) for interface, url in endpoints.items(): endpoint = {'interface': interface, 'url': url, 'region': region, 'region_id': region} self.assertIn(endpoint, service['endpoints']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_http.py0000664000175000017500000001775000000000000025771 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 io import logging from testtools import matchers from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient import session from keystoneclient.tests.unit import utils RESPONSE_BODY = '{"hi": "there"}' def get_client(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test") return cl def get_authed_client(): cl = get_client() cl.management_url = "http://127.0.0.1:5000" cl.auth_token = "token" return cl class ClientTest(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/hi' def test_unauthorized_client_requests(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_client() self.assertRaises(exceptions.AuthorizationFailure, cl.get, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.post, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.put, '/hi') self.assertRaises(exceptions.AuthorizationFailure, cl.delete, '/hi') def test_get(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('GET', text=RESPONSE_BODY) with self.deprecations.expect_deprecations_here(): resp, body = cl.get("/hi") self.assertEqual(self.requests_mock.last_request.method, 'GET') self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL) self.assertRequestHeaderEqual('X-Auth-Token', 'token') self.assertRequestHeaderEqual('User-Agent', httpclient.USER_AGENT) # Automatic JSON parsing self.assertEqual(body, {"hi": "there"}) def test_get_error_with_plaintext_resp(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('GET', status_code=400, text='Some evil plaintext string') self.assertRaises(exceptions.BadRequest, cl.get, '/hi') def test_get_error_with_json_resp(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() err_response = { "error": { "code": 400, "title": "Error title", "message": "Error message string" } } self.stub_url('GET', status_code=400, json=err_response) exc_raised = False try: with self.deprecations.expect_deprecations_here(): cl.get('/hi') except exceptions.BadRequest as exc: exc_raised = True self.assertEqual(exc.message, "Error message string (HTTP 400)") self.assertTrue(exc_raised, 'Exception not raised.') def test_post(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() self.stub_url('POST') with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) self.assertEqual(self.requests_mock.last_request.method, 'POST') self.assertEqual(self.requests_mock.last_request.body, '[1, 2, 3]') self.assertRequestHeaderEqual('X-Auth-Token', 'token') self.assertRequestHeaderEqual('Content-Type', 'application/json') self.assertRequestHeaderEqual('User-Agent', httpclient.USER_AGENT) def test_forwarded_for(self): ORIGINAL_IP = "10.100.100.1" # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test", original_ip=ORIGINAL_IP) self.stub_url('GET') with self.deprecations.expect_deprecations_here(): cl.request(self.TEST_URL, 'GET') forwarded = "for=%s;by=%s" % (ORIGINAL_IP, httpclient.USER_AGENT) self.assertRequestHeaderEqual('Forwarded', forwarded) def test_client_deprecated(self): # Can resolve symbols from the keystoneclient.client module. # keystoneclient.client was deprecated and renamed to # keystoneclient.httpclient. This tests that keystoneclient.client # can still be used. from keystoneclient import client # These statements will raise an AttributeError if the symbol isn't # defined in the module. client.HTTPClient class BasicRequestTests(utils.TestCase): url = 'http://keystone.test.com/' def setUp(self): super(BasicRequestTests, self).setUp() self.logger_message = io.StringIO() handler = logging.StreamHandler(self.logger_message) handler.setLevel(logging.DEBUG) self.logger = logging.getLogger(session.__name__) level = self.logger.getEffectiveLevel() self.logger.setLevel(logging.DEBUG) self.logger.addHandler(handler) self.addCleanup(self.logger.removeHandler, handler) self.addCleanup(self.logger.setLevel, level) def request(self, method='GET', response='Test Response', status_code=200, url=None, headers={}, **kwargs): if not url: url = self.url self.requests_mock.register_uri(method, url, text=response, status_code=status_code, headers=headers) with self.deprecations.expect_deprecations_here(): return httpclient.request(url, method, headers=headers, **kwargs) def test_basic_params(self): method = 'GET' response = 'Test Response' status = 200 self.request(method=method, status_code=status, response=response, headers={'Content-Type': 'application/json'}) self.assertEqual(self.requests_mock.last_request.method, method) logger_message = self.logger_message.getvalue() self.assertThat(logger_message, matchers.Contains('curl')) self.assertThat(logger_message, matchers.Contains('-X %s' % method)) self.assertThat(logger_message, matchers.Contains(self.url)) self.assertThat(logger_message, matchers.Contains(str(status))) self.assertThat(logger_message, matchers.Contains(response)) def test_headers(self): headers = {'key': 'val', 'test': 'other'} self.request(headers=headers) for k, v in headers.items(): self.assertRequestHeaderEqual(k, v) for header in headers.items(): self.assertThat(self.logger_message.getvalue(), matchers.Contains('-H "%s: %s"' % header)) def test_body(self): data = "BODY DATA" self.request(response=data, headers={'Content-Type': 'application/json'}) logger_message = self.logger_message.getvalue() self.assertThat(logger_message, matchers.Contains('BODY:')) self.assertThat(logger_message, matchers.Contains(data)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_https.py0000664000175000017500000001010300000000000026135 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 requests from unittest import mock from keystoneclient import httpclient from keystoneclient.tests.unit import utils FAKE_RESPONSE = utils.test_response(json={'hi': 'there'}) REQUEST_URL = 'https://127.0.0.1:5000/hi' RESPONSE_BODY = '{"hi": "there"}' def get_client(): cl = httpclient.HTTPClient(username="username", password="password", project_id="tenant", auth_url="auth_test", cacert="ca.pem", cert=('cert.pem', "key.pem")) return cl def get_authed_client(): cl = get_client() cl.management_url = "https://127.0.0.1:5000" cl.auth_token = "token" return cl class ClientTest(utils.TestCase): @mock.patch.object(requests, 'request') def test_get(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() with self.deprecations.expect_deprecations_here(): resp, body = cl.get("/hi") # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'GET') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') # Automatic JSON parsing self.assertEqual(body, {"hi": "there"}) @mock.patch.object(requests, 'request') def test_post(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = get_authed_client() with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'POST') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['data'], '[1, 2, 3]') self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') @mock.patch.object(requests, 'request') def test_post_auth(self, MOCK_REQUEST): MOCK_REQUEST.return_value = FAKE_RESPONSE # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient( username="username", password="password", project_id="tenant", auth_url="auth_test", cacert="ca.pem", cert=('cert.pem', 'key.pem')) cl.management_url = "https://127.0.0.1:5000" cl.auth_token = "token" with self.deprecations.expect_deprecations_here(): cl.post("/hi", body=[1, 2, 3]) # this may become too tightly couple later mock_args, mock_kwargs = MOCK_REQUEST.call_args self.assertEqual(mock_args[0], 'POST') self.assertEqual(mock_args[1], REQUEST_URL) self.assertEqual(mock_kwargs['data'], '[1, 2, 3]') self.assertEqual(mock_kwargs['headers']['X-Auth-Token'], 'token') self.assertEqual(mock_kwargs['cert'], ('cert.pem', 'key.pem')) self.assertEqual(mock_kwargs['verify'], 'ca.pem') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_keyring.py0000664000175000017500000002001500000000000026446 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 from oslo_utils import timeutils from keystoneclient import access from keystoneclient import httpclient from keystoneclient.tests.unit import utils from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient import utils as client_utils try: import keyring # noqa import pickle # noqa except ImportError: keyring = None PROJECT_SCOPED_TOKEN = client_fixtures.project_scoped_token() # These mirror values from PROJECT_SCOPED_TOKEN USERNAME = 'exampleuser' AUTH_URL = 'http://public.com:5000/v2.0' TOKEN = '04c7d5ffaeef485f9dc69c06db285bdb' PASSWORD = 'password' TENANT = 'tenant' TENANT_ID = 'tenant_id' class KeyringTest(utils.TestCase): def setUp(self): if keyring is None: self.skipTest( 'optional package keyring or pickle is not installed') class MemoryKeyring(keyring.backend.KeyringBackend): """A Simple testing keyring. This class supports stubbing an initial password to be returned by setting password, and allows easy password and key retrieval. Also records if a password was retrieved. """ def __init__(self): self.key = None self.password = None self.fetched = False self.get_password_called = False self.set_password_called = False def supported(self): return 1 def get_password(self, service, username): self.get_password_called = True key = username + '@' + service # make sure we don't get passwords crossed if one is enforced. if self.key and self.key != key: return None if self.password: self.fetched = True return self.password def set_password(self, service, username, password): self.set_password_called = True self.key = username + '@' + service self.password = password super(KeyringTest, self).setUp() self.memory_keyring = MemoryKeyring() keyring.set_keyring(self.memory_keyring) def test_no_keyring_key(self): """Test case when no keyring set. Ensure that if we don't have use_keyring set in the client that the keyring is never accessed. """ # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL) # stub and check that a new token is received method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # make sure that we never touched the keyring self.assertFalse(self.memory_keyring.get_password_called) self.assertFalse(self.memory_keyring.set_password_called) def test_build_keyring_key(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL) keyring_key = cl._build_keyring_key(auth_url=AUTH_URL, username=USERNAME, tenant_name=TENANT, tenant_id=TENANT_ID, token=TOKEN) self.assertEqual(keyring_key, '%s/%s/%s/%s/%s' % (AUTH_URL, TENANT_ID, TENANT, TOKEN, USERNAME)) def test_set_and_get_keyring_expired(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # set an expired token into the keyring auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN) expired = timeutils.utcnow() - datetime.timedelta(minutes=30) auth_ref['token']['expires'] = client_utils.isotime(expired) self.memory_keyring.password = pickle.dumps(auth_ref) # stub and check that a new token is received, so not using expired method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # check that a value was returned from the keyring self.assertTrue(self.memory_keyring.fetched) # check that the new token has been loaded into the keyring new_auth_ref = pickle.loads(self.memory_keyring.password) self.assertEqual(new_auth_ref['token']['expires'], PROJECT_SCOPED_TOKEN['access']['token']['expires']) def test_get_keyring(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # set an token into the keyring auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN) future = timeutils.utcnow() + datetime.timedelta(minutes=30) auth_ref['token']['expires'] = client_utils.isotime(future) self.memory_keyring.password = pickle.dumps(auth_ref) # don't stub get_raw_token so will fail if authenticate happens self.assertTrue(cl.authenticate()) self.assertTrue(self.memory_keyring.fetched) def test_set_keyring(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, project_id=TENANT_ID, auth_url=AUTH_URL, use_keyring=True) # stub and check that a new token is received method = 'get_raw_token_from_identity_service' with mock.patch.object(cl, method) as meth: meth.return_value = (True, PROJECT_SCOPED_TOKEN) self.assertTrue(cl.authenticate()) self.assertEqual(1, meth.call_count) # we checked the keyring, but we didn't find anything self.assertTrue(self.memory_keyring.get_password_called) self.assertFalse(self.memory_keyring.fetched) # check that the new token has been loaded into the keyring self.assertTrue(self.memory_keyring.set_password_called) new_auth_ref = pickle.loads(self.memory_keyring.password) self.assertEqual(new_auth_ref.auth_token, TOKEN) self.assertEqual(new_auth_ref['token'], PROJECT_SCOPED_TOKEN['access']['token']) self.assertEqual(new_auth_ref.username, USERNAME) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_session.py0000664000175000017500000012046200000000000026470 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse from io import StringIO import itertools import logging from unittest import mock import uuid from oslo_config import cfg from oslo_config import fixture as config from oslo_serialization import jsonutils import requests from testtools import matchers from keystoneclient import adapter from keystoneclient.auth import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import session as client_session from keystoneclient.tests.unit import utils class SessionTests(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/' def setUp(self): super(SessionTests, self).setUp() self.deprecations.expect_deprecations() def test_get(self): session = client_session.Session() self.stub_url('GET', text='response') resp = session.get(self.TEST_URL) self.assertEqual('GET', self.requests_mock.last_request.method) self.assertEqual(resp.text, 'response') self.assertTrue(resp.ok) def test_post(self): session = client_session.Session() self.stub_url('POST', text='response') resp = session.post(self.TEST_URL, json={'hello': 'world'}) self.assertEqual('POST', self.requests_mock.last_request.method) self.assertEqual(resp.text, 'response') self.assertTrue(resp.ok) self.assertRequestBodyIs(json={'hello': 'world'}) def test_head(self): session = client_session.Session() self.stub_url('HEAD') resp = session.head(self.TEST_URL) self.assertEqual('HEAD', self.requests_mock.last_request.method) self.assertTrue(resp.ok) self.assertRequestBodyIs('') def test_put(self): session = client_session.Session() self.stub_url('PUT', text='response') resp = session.put(self.TEST_URL, json={'hello': 'world'}) self.assertEqual('PUT', self.requests_mock.last_request.method) self.assertEqual(resp.text, 'response') self.assertTrue(resp.ok) self.assertRequestBodyIs(json={'hello': 'world'}) def test_delete(self): session = client_session.Session() self.stub_url('DELETE', text='response') resp = session.delete(self.TEST_URL) self.assertEqual('DELETE', self.requests_mock.last_request.method) self.assertTrue(resp.ok) self.assertEqual(resp.text, 'response') def test_patch(self): session = client_session.Session() self.stub_url('PATCH', text='response') resp = session.patch(self.TEST_URL, json={'hello': 'world'}) self.assertEqual('PATCH', self.requests_mock.last_request.method) self.assertTrue(resp.ok) self.assertEqual(resp.text, 'response') self.assertRequestBodyIs(json={'hello': 'world'}) def test_user_agent(self): session = client_session.Session(user_agent='test-agent') self.stub_url('GET', text='response') resp = session.get(self.TEST_URL) self.assertTrue(resp.ok) self.assertRequestHeaderEqual('User-Agent', 'test-agent') resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'}) self.assertTrue(resp.ok) self.assertRequestHeaderEqual('User-Agent', 'new-agent') resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'}, user_agent='overrides-agent') self.assertTrue(resp.ok) self.assertRequestHeaderEqual('User-Agent', 'overrides-agent') def test_http_session_opts(self): session = client_session.Session(cert='cert.pem', timeout=5, verify='certs') FAKE_RESP = utils.test_response(text='resp') RESP = mock.Mock(return_value=FAKE_RESP) with mock.patch.object(session.session, 'request', RESP) as mocked: session.post(self.TEST_URL, data='value') mock_args, mock_kwargs = mocked.call_args self.assertEqual(mock_args[0], 'POST') self.assertEqual(mock_args[1], self.TEST_URL) self.assertEqual(mock_kwargs['data'], 'value') self.assertEqual(mock_kwargs['cert'], 'cert.pem') self.assertEqual(mock_kwargs['verify'], 'certs') self.assertEqual(mock_kwargs['timeout'], 5) def test_not_found(self): session = client_session.Session() self.stub_url('GET', status_code=404) self.assertRaises(exceptions.NotFound, session.get, self.TEST_URL) def test_server_error(self): session = client_session.Session() self.stub_url('GET', status_code=500) self.assertRaises(exceptions.InternalServerError, session.get, self.TEST_URL) def test_session_debug_output(self): """Test request and response headers in debug logs. in order to redact secure headers while debug is true. """ session = client_session.Session(verify=False) headers = {'HEADERA': 'HEADERVALB', 'Content-Type': 'application/json'} security_headers = {'Authorization': uuid.uuid4().hex, 'X-Auth-Token': uuid.uuid4().hex, 'X-Subject-Token': uuid.uuid4().hex, 'X-Service-Token': uuid.uuid4().hex} body = '{"a": "b"}' data = '{"c": "d"}' all_headers = dict( itertools.chain(headers.items(), security_headers.items())) self.stub_url('POST', text=body, headers=all_headers) resp = session.post(self.TEST_URL, headers=all_headers, data=data) self.assertEqual(resp.status_code, 200) self.assertIn('curl', self.logger.output) self.assertIn('POST', self.logger.output) self.assertIn('--insecure', self.logger.output) self.assertIn(body, self.logger.output) self.assertIn("'%s'" % data, self.logger.output) for k, v in headers.items(): self.assertIn(k, self.logger.output) self.assertIn(v, self.logger.output) # Assert that response headers contains actual values and # only debug logs has been masked for k, v in security_headers.items(): self.assertIn('%s: {SHA1}' % k, self.logger.output) self.assertEqual(v, resp.headers[k]) self.assertNotIn(v, self.logger.output) def test_logs_failed_output(self): """Test that output is logged even for failed requests.""" session = client_session.Session() body = {uuid.uuid4().hex: uuid.uuid4().hex} self.stub_url('GET', json=body, status_code=400, headers={'Content-Type': 'application/json'}) resp = session.get(self.TEST_URL, raise_exc=False) self.assertEqual(resp.status_code, 400) self.assertIn(list(body.keys())[0], self.logger.output) self.assertIn(list(body.values())[0], self.logger.output) def test_logging_body_only_for_specified_content_types(self): """Verify response body is only logged in specific content types. Response bodies are logged only when the response's Content-Type header is set to application/json. This prevents us to get an unexpected MemoryError when reading arbitrary responses, such as streams. """ OMITTED_BODY = ('Omitted, Content-Type is set to %s. Only ' 'application/json responses have their bodies logged.') session = client_session.Session(verify=False) # Content-Type is not set body = jsonutils.dumps({'token': {'id': '...'}}) self.stub_url('POST', text=body) session.post(self.TEST_URL) self.assertNotIn(body, self.logger.output) self.assertIn(OMITTED_BODY % None, self.logger.output) # Content-Type is set to text/xml body = '...' self.stub_url('POST', text=body, headers={'Content-Type': 'text/xml'}) session.post(self.TEST_URL) self.assertNotIn(body, self.logger.output) self.assertIn(OMITTED_BODY % 'text/xml', self.logger.output) # Content-Type is set to application/json body = jsonutils.dumps({'token': {'id': '...'}}) self.stub_url('POST', text=body, headers={'Content-Type': 'application/json'}) session.post(self.TEST_URL) self.assertIn(body, self.logger.output) self.assertNotIn(OMITTED_BODY % 'application/json', self.logger.output) # Content-Type is set to application/json; charset=UTF-8 body = jsonutils.dumps({'token': {'id': '...'}}) self.stub_url( 'POST', text=body, headers={'Content-Type': 'application/json; charset=UTF-8'}) session.post(self.TEST_URL) self.assertIn(body, self.logger.output) self.assertNotIn(OMITTED_BODY % 'application/json; charset=UTF-8', self.logger.output) def test_unicode_data_in_debug_output(self): """Verify that ascii-encodable data is logged without modification.""" session = client_session.Session(verify=False) body = 'RESP' data = 'αβγδ' self.stub_url('POST', text=body) session.post(self.TEST_URL, data=data) self.assertIn("'%s'" % data, self.logger.output) def test_logging_cacerts(self): path_to_certs = '/path/to/certs' session = client_session.Session(verify=path_to_certs) self.stub_url('GET', text='text') session.get(self.TEST_URL) self.assertIn('--cacert', self.logger.output) self.assertIn(path_to_certs, self.logger.output) def test_connect_retries(self): def _timeout_error(request, context): raise requests.exceptions.Timeout() self.stub_url('GET', text=_timeout_error) session = client_session.Session() retries = 3 with mock.patch('time.sleep') as m: self.assertRaises(exceptions.RequestTimeout, session.get, self.TEST_URL, connect_retries=retries) self.assertEqual(retries, m.call_count) # 3 retries finishing with 2.0 means 0.5, 1.0 and 2.0 m.assert_called_with(2.0) # we count retries so there will be one initial request + 3 retries self.assertThat(self.requests_mock.request_history, matchers.HasLength(retries + 1)) def test_uses_tcp_keepalive_by_default(self): session = client_session.Session() requests_session = session.session self.assertIsInstance(requests_session.adapters['http://'], client_session.TCPKeepAliveAdapter) self.assertIsInstance(requests_session.adapters['https://'], client_session.TCPKeepAliveAdapter) def test_does_not_set_tcp_keepalive_on_custom_sessions(self): mock_session = mock.Mock() client_session.Session(session=mock_session) self.assertFalse(mock_session.mount.called) def test_ssl_error_message(self): error = uuid.uuid4().hex def _ssl_error(request, context): raise requests.exceptions.SSLError(error) self.stub_url('GET', text=_ssl_error) session = client_session.Session() # The exception should contain the URL and details about the SSL error msg = _('SSL exception connecting to %(url)s: %(error)s') % { 'url': self.TEST_URL, 'error': error} self.assertRaisesRegex( exceptions.SSLError, msg, session.get, self.TEST_URL, ) def test_mask_password_in_http_log_response(self): session = client_session.Session() def fake_debug(msg): self.assertNotIn('verybadpass', msg) logger = mock.Mock(isEnabledFor=mock.Mock(return_value=True)) logger.debug = mock.Mock(side_effect=fake_debug) body = { "connection_info": { "driver_volume_type": "iscsi", "data": { "auth_password": "verybadpass", "target_discovered": False, "encrypted": False, "qos_specs": None, "target_iqn": ("iqn.2010-10.org.openstack:volume-" "744d2085-8e78-40a5-8659-ef3cffb2480e"), "target_portal": "172.99.69.228:3260", "volume_id": "744d2085-8e78-40a5-8659-ef3cffb2480e", "target_lun": 1, "access_mode": "rw", "auth_username": "verybadusername", "auth_method": "CHAP"}}} body_json = jsonutils.dumps(body) response = mock.Mock(text=body_json, status_code=200, headers={'content-type': 'application/json'}) session._http_log_response(response, logger) self.assertEqual(1, logger.debug.call_count) class TCPKeepAliveAdapter(utils.TestCase): @mock.patch.object(client_session, 'socket') @mock.patch('requests.adapters.HTTPAdapter.init_poolmanager') def test_init_poolmanager_all_options(self, mock_parent_init_poolmanager, mock_socket): # properties expected to be in socket. mock_socket.TCP_KEEPIDLE = mock.sentinel.TCP_KEEPIDLE mock_socket.TCP_KEEPCNT = mock.sentinel.TCP_KEEPCNT mock_socket.TCP_KEEPINTVL = mock.sentinel.TCP_KEEPINTVL desired_opts = [mock_socket.TCP_KEEPIDLE, mock_socket.TCP_KEEPCNT, mock_socket.TCP_KEEPINTVL] adapter = client_session.TCPKeepAliveAdapter() adapter.init_poolmanager() call_args, call_kwargs = mock_parent_init_poolmanager.call_args called_socket_opts = call_kwargs['socket_options'] call_options = [opt for (protocol, opt, value) in called_socket_opts] for opt in desired_opts: self.assertIn(opt, call_options) @mock.patch.object(client_session, 'socket') @mock.patch('requests.adapters.HTTPAdapter.init_poolmanager') def test_init_poolmanager(self, mock_parent_init_poolmanager, mock_socket): spec = ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE'] mock_socket.mock_add_spec(spec) adapter = client_session.TCPKeepAliveAdapter() adapter.init_poolmanager() call_args, call_kwargs = mock_parent_init_poolmanager.call_args called_socket_opts = call_kwargs['socket_options'] call_options = [opt for (protocol, opt, value) in called_socket_opts] self.assertEqual([mock_socket.TCP_NODELAY, mock_socket.SO_KEEPALIVE], call_options) class RedirectTests(utils.TestCase): REDIRECT_CHAIN = ['http://myhost:3445/', 'http://anotherhost:6555/', 'http://thirdhost/', 'http://finaldestination:55/'] DEFAULT_REDIRECT_BODY = 'Redirect' DEFAULT_RESP_BODY = 'Found' def setUp(self): super(RedirectTests, self).setUp() self.deprecations.expect_deprecations() def setup_redirects(self, method='GET', status_code=305, redirect_kwargs=None, final_kwargs=None): redirect_kwargs = redirect_kwargs or {} final_kwargs = final_kwargs or {} redirect_kwargs.setdefault('text', self.DEFAULT_REDIRECT_BODY) for s, d in zip(self.REDIRECT_CHAIN, self.REDIRECT_CHAIN[1:]): self.requests_mock.register_uri(method, s, status_code=status_code, headers={'Location': d}, **redirect_kwargs) final_kwargs.setdefault('status_code', 200) final_kwargs.setdefault('text', self.DEFAULT_RESP_BODY) self.requests_mock.register_uri(method, self.REDIRECT_CHAIN[-1], **final_kwargs) def assertResponse(self, resp): self.assertEqual(resp.status_code, 200) self.assertEqual(resp.text, self.DEFAULT_RESP_BODY) def test_basic_get(self): session = client_session.Session() self.setup_redirects() resp = session.get(self.REDIRECT_CHAIN[-2]) self.assertResponse(resp) def test_basic_post_keeps_correct_method(self): session = client_session.Session() self.setup_redirects(method='POST', status_code=301) resp = session.post(self.REDIRECT_CHAIN[-2]) self.assertResponse(resp) def test_redirect_forever(self): session = client_session.Session(redirect=True) self.setup_redirects() resp = session.get(self.REDIRECT_CHAIN[0]) self.assertResponse(resp) self.assertTrue(len(resp.history), len(self.REDIRECT_CHAIN)) def test_no_redirect(self): session = client_session.Session(redirect=False) self.setup_redirects() resp = session.get(self.REDIRECT_CHAIN[0]) self.assertEqual(resp.status_code, 305) self.assertEqual(resp.url, self.REDIRECT_CHAIN[0]) def test_redirect_limit(self): self.setup_redirects() for i in (1, 2): session = client_session.Session(redirect=i) resp = session.get(self.REDIRECT_CHAIN[0]) self.assertEqual(resp.status_code, 305) self.assertEqual(resp.url, self.REDIRECT_CHAIN[i]) self.assertEqual(resp.text, self.DEFAULT_REDIRECT_BODY) def test_history_matches_requests(self): self.setup_redirects(status_code=301) session = client_session.Session(redirect=True) req_resp = requests.get(self.REDIRECT_CHAIN[0], allow_redirects=True) ses_resp = session.get(self.REDIRECT_CHAIN[0]) self.assertEqual(len(req_resp.history), len(ses_resp.history)) for r, s in zip(req_resp.history, ses_resp.history): self.assertEqual(r.url, s.url) self.assertEqual(r.status_code, s.status_code) class ConstructSessionFromArgsTests(utils.TestCase): KEY = 'keyfile' CERT = 'certfile' CACERT = 'cacert-path' def _s(self, k=None, **kwargs): k = k or kwargs with self.deprecations.expect_deprecations_here(): return client_session.Session.construct(k) def test_verify(self): self.assertFalse(self._s(insecure=True).verify) self.assertTrue(self._s(verify=True, insecure=True).verify) self.assertFalse(self._s(verify=False, insecure=True).verify) self.assertEqual(self._s(cacert=self.CACERT).verify, self.CACERT) def test_cert(self): tup = (self.CERT, self.KEY) self.assertEqual(self._s(cert=tup).cert, tup) self.assertEqual(self._s(cert=self.CERT, key=self.KEY).cert, tup) self.assertIsNone(self._s(key=self.KEY).cert) def test_pass_through(self): value = 42 # only a number because timeout needs to be for key in ['timeout', 'session', 'original_ip', 'user_agent']: args = {key: value} self.assertEqual(getattr(self._s(args), key), value) self.assertNotIn(key, args) class AuthPlugin(base.BaseAuthPlugin): """Very simple debug authentication plugin. Takes Parameters such that it can throw exceptions at the right times. """ TEST_TOKEN = utils.TestCase.TEST_TOKEN TEST_USER_ID = 'aUser' TEST_PROJECT_ID = 'aProject' SERVICE_URLS = { 'identity': {'public': 'http://identity-public:1111/v2.0', 'admin': 'http://identity-admin:1111/v2.0'}, 'compute': {'public': 'http://compute-public:2222/v1.0', 'admin': 'http://compute-admin:2222/v1.0'}, 'image': {'public': 'http://image-public:3333/v2.0', 'admin': 'http://image-admin:3333/v2.0'} } def __init__(self, token=TEST_TOKEN, invalidate=True): self.token = token self._invalidate = invalidate def get_token(self, session): return self.token def get_endpoint(self, session, service_type=None, interface=None, **kwargs): try: return self.SERVICE_URLS[service_type][interface] except (KeyError, AttributeError): return None def invalidate(self): return self._invalidate def get_user_id(self, session): return self.TEST_USER_ID def get_project_id(self, session): return self.TEST_PROJECT_ID class CalledAuthPlugin(base.BaseAuthPlugin): ENDPOINT = 'http://fakeendpoint/' def __init__(self, invalidate=True): self.get_token_called = False self.get_endpoint_called = False self.endpoint_arguments = {} self.invalidate_called = False self._invalidate = invalidate def get_token(self, session): self.get_token_called = True return utils.TestCase.TEST_TOKEN def get_endpoint(self, session, **kwargs): self.get_endpoint_called = True self.endpoint_arguments = kwargs return self.ENDPOINT def invalidate(self): self.invalidate_called = True return self._invalidate class SessionAuthTests(utils.TestCase): TEST_URL = 'http://127.0.0.1:5000/' TEST_JSON = {'hello': 'world'} def setUp(self): super(SessionAuthTests, self).setUp() self.deprecations.expect_deprecations() def stub_service_url(self, service_type, interface, path, method='GET', **kwargs): base_url = AuthPlugin.SERVICE_URLS[service_type][interface] uri = "%s/%s" % (base_url.rstrip('/'), path.lstrip('/')) self.requests_mock.register_uri(method, uri, **kwargs) def test_auth_plugin_default_with_plugin(self): self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON) # if there is an auth_plugin then it should default to authenticated auth = AuthPlugin() sess = client_session.Session(auth=auth) resp = sess.get(self.TEST_URL) self.assertEqual(resp.json(), self.TEST_JSON) self.assertRequestHeaderEqual('X-Auth-Token', AuthPlugin.TEST_TOKEN) def test_auth_plugin_disable(self): self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON) auth = AuthPlugin() sess = client_session.Session(auth=auth) resp = sess.get(self.TEST_URL, authenticated=False) self.assertEqual(resp.json(), self.TEST_JSON) self.assertRequestHeaderEqual('X-Auth-Token', None) def test_service_type_urls(self): service_type = 'compute' interface = 'public' path = '/instances' status = 200 body = 'SUCCESS' self.stub_service_url(service_type=service_type, interface=interface, path=path, status_code=status, text=body) sess = client_session.Session(auth=AuthPlugin()) resp = sess.get(path, endpoint_filter={'service_type': service_type, 'interface': interface}) self.assertEqual(self.requests_mock.last_request.url, AuthPlugin.SERVICE_URLS['compute']['public'] + path) self.assertEqual(resp.text, body) self.assertEqual(resp.status_code, status) def test_service_url_raises_if_no_auth_plugin(self): sess = client_session.Session() self.assertRaises(exceptions.MissingAuthPlugin, sess.get, '/path', endpoint_filter={'service_type': 'compute', 'interface': 'public'}) def test_service_url_raises_if_no_url_returned(self): sess = client_session.Session(auth=AuthPlugin()) self.assertRaises(exceptions.EndpointNotFound, sess.get, '/path', endpoint_filter={'service_type': 'unknown', 'interface': 'public'}) def test_raises_exc_only_when_asked(self): # A request that returns a HTTP error should by default raise an # exception by default, if you specify raise_exc=False then it will not self.requests_mock.get(self.TEST_URL, status_code=401) sess = client_session.Session() self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL) resp = sess.get(self.TEST_URL, raise_exc=False) self.assertEqual(401, resp.status_code) def test_passed_auth_plugin(self): passed = CalledAuthPlugin() sess = client_session.Session() self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path', status_code=200) endpoint_filter = {'service_type': 'identity'} # no plugin with authenticated won't work self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path', authenticated=True) # no plugin with an endpoint filter won't work self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path', authenticated=False, endpoint_filter=endpoint_filter) resp = sess.get('path', auth=passed, endpoint_filter=endpoint_filter) self.assertEqual(200, resp.status_code) self.assertTrue(passed.get_endpoint_called) self.assertTrue(passed.get_token_called) def test_passed_auth_plugin_overrides(self): fixed = CalledAuthPlugin() passed = CalledAuthPlugin() sess = client_session.Session(fixed) self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path', status_code=200) resp = sess.get('path', auth=passed, endpoint_filter={'service_type': 'identity'}) self.assertEqual(200, resp.status_code) self.assertTrue(passed.get_endpoint_called) self.assertTrue(passed.get_token_called) self.assertFalse(fixed.get_endpoint_called) self.assertFalse(fixed.get_token_called) def test_requests_auth_plugin(self): sess = client_session.Session() requests_auth = object() FAKE_RESP = utils.test_response(text='resp') RESP = mock.Mock(return_value=FAKE_RESP) with mock.patch.object(sess.session, 'request', RESP) as mocked: sess.get(self.TEST_URL, requests_auth=requests_auth) mocked.assert_called_once_with('GET', self.TEST_URL, headers=mock.ANY, allow_redirects=mock.ANY, auth=requests_auth, verify=mock.ANY) def test_reauth_called(self): auth = CalledAuthPlugin(invalidate=True) sess = client_session.Session(auth=auth) self.requests_mock.get(self.TEST_URL, [{'text': 'Failed', 'status_code': 401}, {'text': 'Hello', 'status_code': 200}]) # allow_reauth=True is the default resp = sess.get(self.TEST_URL, authenticated=True) self.assertEqual(200, resp.status_code) self.assertEqual('Hello', resp.text) self.assertTrue(auth.invalidate_called) def test_reauth_not_called(self): auth = CalledAuthPlugin(invalidate=True) sess = client_session.Session(auth=auth) self.requests_mock.get(self.TEST_URL, [{'text': 'Failed', 'status_code': 401}, {'text': 'Hello', 'status_code': 200}]) self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL, authenticated=True, allow_reauth=False) self.assertFalse(auth.invalidate_called) def test_endpoint_override_overrides_filter(self): auth = CalledAuthPlugin() sess = client_session.Session(auth=auth) override_base = 'http://mytest/' path = 'path' override_url = override_base + path resp_text = uuid.uuid4().hex self.requests_mock.get(override_url, text=resp_text) resp = sess.get(path, endpoint_override=override_base, endpoint_filter={'service_type': 'identity'}) self.assertEqual(resp_text, resp.text) self.assertEqual(override_url, self.requests_mock.last_request.url) self.assertTrue(auth.get_token_called) self.assertFalse(auth.get_endpoint_called) def test_endpoint_override_ignore_full_url(self): auth = CalledAuthPlugin() sess = client_session.Session(auth=auth) path = 'path' url = self.TEST_URL + path resp_text = uuid.uuid4().hex self.requests_mock.get(url, text=resp_text) resp = sess.get(url, endpoint_override='http://someother.url', endpoint_filter={'service_type': 'identity'}) self.assertEqual(resp_text, resp.text) self.assertEqual(url, self.requests_mock.last_request.url) self.assertTrue(auth.get_token_called) self.assertFalse(auth.get_endpoint_called) def test_user_and_project_id(self): auth = AuthPlugin() sess = client_session.Session(auth=auth) self.assertEqual(auth.TEST_USER_ID, sess.get_user_id()) self.assertEqual(auth.TEST_PROJECT_ID, sess.get_project_id()) def test_logger_object_passed(self): logger = logging.getLogger(uuid.uuid4().hex) logger.setLevel(logging.DEBUG) logger.propagate = False io = StringIO() handler = logging.StreamHandler(io) logger.addHandler(handler) auth = AuthPlugin() sess = client_session.Session(auth=auth) response = {uuid.uuid4().hex: uuid.uuid4().hex} self.stub_url('GET', json=response, headers={'Content-Type': 'application/json'}) resp = sess.get(self.TEST_URL, logger=logger) self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) self.assertIn(list(response.keys())[0], output) self.assertIn(list(response.values())[0], output) self.assertNotIn(list(response.keys())[0], self.logger.output) self.assertNotIn(list(response.values())[0], self.logger.output) class AdapterTest(utils.TestCase): SERVICE_TYPE = uuid.uuid4().hex SERVICE_NAME = uuid.uuid4().hex INTERFACE = uuid.uuid4().hex REGION_NAME = uuid.uuid4().hex USER_AGENT = uuid.uuid4().hex VERSION = uuid.uuid4().hex TEST_URL = CalledAuthPlugin.ENDPOINT def setUp(self): super(AdapterTest, self).setUp() self.deprecations.expect_deprecations() def _create_loaded_adapter(self): auth = CalledAuthPlugin() sess = client_session.Session() return adapter.Adapter(sess, auth=auth, service_type=self.SERVICE_TYPE, service_name=self.SERVICE_NAME, interface=self.INTERFACE, region_name=self.REGION_NAME, user_agent=self.USER_AGENT, version=self.VERSION) def _verify_endpoint_called(self, adpt): self.assertEqual(self.SERVICE_TYPE, adpt.auth.endpoint_arguments['service_type']) self.assertEqual(self.SERVICE_NAME, adpt.auth.endpoint_arguments['service_name']) self.assertEqual(self.INTERFACE, adpt.auth.endpoint_arguments['interface']) self.assertEqual(self.REGION_NAME, adpt.auth.endpoint_arguments['region_name']) self.assertEqual(self.VERSION, adpt.auth.endpoint_arguments['version']) def test_setting_variables_on_request(self): response = uuid.uuid4().hex self.stub_url('GET', text=response) adpt = self._create_loaded_adapter() resp = adpt.get('/') self.assertEqual(resp.text, response) self._verify_endpoint_called(adpt) self.assertTrue(adpt.auth.get_token_called) self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) def test_setting_variables_on_get_endpoint(self): adpt = self._create_loaded_adapter() url = adpt.get_endpoint() self.assertEqual(self.TEST_URL, url) self._verify_endpoint_called(adpt) def test_legacy_binding(self): key = uuid.uuid4().hex val = uuid.uuid4().hex response = jsonutils.dumps({key: val}) self.stub_url('GET', text=response) auth = CalledAuthPlugin() sess = client_session.Session(auth=auth) adpt = adapter.LegacyJsonAdapter(sess, service_type=self.SERVICE_TYPE, user_agent=self.USER_AGENT) resp, body = adpt.get('/') self.assertEqual(self.SERVICE_TYPE, auth.endpoint_arguments['service_type']) self.assertEqual(resp.text, response) self.assertEqual(val, body[key]) def test_legacy_binding_non_json_resp(self): response = uuid.uuid4().hex self.stub_url('GET', text=response, headers={'Content-Type': 'text/html'}) auth = CalledAuthPlugin() sess = client_session.Session(auth=auth) adpt = adapter.LegacyJsonAdapter(sess, service_type=self.SERVICE_TYPE, user_agent=self.USER_AGENT) resp, body = adpt.get('/') self.assertEqual(self.SERVICE_TYPE, auth.endpoint_arguments['service_type']) self.assertEqual(resp.text, response) self.assertIsNone(body) def test_methods(self): sess = client_session.Session() adpt = adapter.Adapter(sess) url = 'http://url' for method in ['get', 'head', 'post', 'put', 'patch', 'delete']: with mock.patch.object(adpt, 'request') as m: getattr(adpt, method)(url) m.assert_called_once_with(url, method.upper()) def test_setting_endpoint_override(self): endpoint_override = 'http://overrideurl' path = '/path' endpoint_url = endpoint_override + path auth = CalledAuthPlugin() sess = client_session.Session(auth=auth) adpt = adapter.Adapter(sess, endpoint_override=endpoint_override) response = uuid.uuid4().hex self.requests_mock.get(endpoint_url, text=response) resp = adpt.get(path) self.assertEqual(response, resp.text) self.assertEqual(endpoint_url, self.requests_mock.last_request.url) self.assertEqual(endpoint_override, adpt.get_endpoint()) def test_adapter_invalidate(self): auth = CalledAuthPlugin() sess = client_session.Session() adpt = adapter.Adapter(sess, auth=auth) adpt.invalidate() self.assertTrue(auth.invalidate_called) def test_adapter_get_token(self): auth = CalledAuthPlugin() sess = client_session.Session() adpt = adapter.Adapter(sess, auth=auth) self.assertEqual(self.TEST_TOKEN, adpt.get_token()) self.assertTrue(auth.get_token_called) def test_adapter_connect_retries(self): retries = 2 sess = client_session.Session() adpt = adapter.Adapter(sess, connect_retries=retries) def _refused_error(request, context): raise requests.exceptions.ConnectionError() self.stub_url('GET', text=_refused_error) with mock.patch('time.sleep') as m: self.assertRaises(exceptions.ConnectionRefused, adpt.get, self.TEST_URL) self.assertEqual(retries, m.call_count) # we count retries so there will be one initial request + 2 retries self.assertThat(self.requests_mock.request_history, matchers.HasLength(retries + 1)) def test_user_and_project_id(self): auth = AuthPlugin() sess = client_session.Session() adpt = adapter.Adapter(sess, auth=auth) self.assertEqual(auth.TEST_USER_ID, adpt.get_user_id()) self.assertEqual(auth.TEST_PROJECT_ID, adpt.get_project_id()) def test_logger_object_passed(self): logger = logging.getLogger(uuid.uuid4().hex) logger.setLevel(logging.DEBUG) logger.propagate = False io = StringIO() handler = logging.StreamHandler(io) logger.addHandler(handler) auth = AuthPlugin() sess = client_session.Session(auth=auth) adpt = adapter.Adapter(sess, auth=auth, logger=logger) response = {uuid.uuid4().hex: uuid.uuid4().hex} self.stub_url('GET', json=response, headers={'Content-Type': 'application/json'}) resp = adpt.get(self.TEST_URL, logger=logger) self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) self.assertIn(list(response.keys())[0], output) self.assertIn(list(response.values())[0], output) self.assertNotIn(list(response.keys())[0], self.logger.output) self.assertNotIn(list(response.values())[0], self.logger.output) class ConfLoadingTests(utils.TestCase): GROUP = 'sessiongroup' def setUp(self): super(ConfLoadingTests, self).setUp() self.conf_fixture = self.useFixture(config.Config()) client_session.Session.register_conf_options(self.conf_fixture.conf, self.GROUP) def config(self, **kwargs): kwargs['group'] = self.GROUP self.conf_fixture.config(**kwargs) def get_session(self, **kwargs): with self.deprecations.expect_deprecations_here(): return client_session.Session.load_from_conf_options( self.conf_fixture.conf, self.GROUP, **kwargs) def test_insecure_timeout(self): self.config(insecure=True, timeout=5) s = self.get_session() self.assertFalse(s.verify) self.assertEqual(5, s.timeout) def test_client_certs(self): cert = '/path/to/certfile' key = '/path/to/keyfile' self.config(certfile=cert, keyfile=key) s = self.get_session() self.assertTrue(s.verify) self.assertEqual((cert, key), s.cert) def test_cacert(self): cafile = '/path/to/cacert' self.config(cafile=cafile) s = self.get_session() self.assertEqual(cafile, s.verify) def test_deprecated(self): def new_deprecated(): return cfg.DeprecatedOpt(uuid.uuid4().hex, group=uuid.uuid4().hex) opt_names = ['cafile', 'certfile', 'keyfile', 'insecure', 'timeout'] depr = dict([(n, [new_deprecated()]) for n in opt_names]) opts = client_session.Session.get_conf_options(deprecated_opts=depr) self.assertThat(opt_names, matchers.HasLength(len(opts))) for opt in opts: self.assertIn(depr[opt.name][0], opt.deprecated_opts) class CliLoadingTests(utils.TestCase): def setUp(self): super(CliLoadingTests, self).setUp() self.parser = argparse.ArgumentParser() client_session.Session.register_cli_options(self.parser) def get_session(self, val, **kwargs): args = self.parser.parse_args(val.split()) with self.deprecations.expect_deprecations_here(): return client_session.Session.load_from_cli_options(args, **kwargs) def test_insecure_timeout(self): s = self.get_session('--insecure --timeout 5.5') self.assertFalse(s.verify) self.assertEqual(5.5, s.timeout) def test_client_certs(self): cert = '/path/to/certfile' key = '/path/to/keyfile' s = self.get_session('--os-cert %s --os-key %s' % (cert, key)) self.assertTrue(s.verify) self.assertEqual((cert, key), s.cert) def test_cacert(self): cacert = '/path/to/cacert' s = self.get_session('--os-cacert %s' % cacert) self.assertEqual(cacert, s.verify) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/test_utils.py0000664000175000017500000001073700000000000026150 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 exceptions as ksa_exceptions import testresources from testtools import matchers from keystoneclient import exceptions as ksc_exceptions from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils as test_utils from keystoneclient import utils class FakeResource(object): pass class FakeManager(object): resource_class = FakeResource resources = { '1234': {'name': 'entity_one'}, '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0': {'name': 'entity_two'}, '\xe3\x82\xbdtest': {'name': '\u30bdtest'}, '5678': {'name': '9876'} } def get(self, resource_id): try: return self.resources[str(resource_id)] except KeyError: raise ksa_exceptions.NotFound(resource_id) def find(self, name=None): if name == '9999': # NOTE(morganfainberg): special case that raises NoUniqueMatch. raise ksc_exceptions.NoUniqueMatch() for resource_id, resource in self.resources.items(): if resource['name'] == str(name): return resource raise ksa_exceptions.NotFound(name) class FindResourceTestCase(test_utils.TestCase): def setUp(self): super(FindResourceTestCase, self).setUp() self.manager = FakeManager() def test_find_none(self): self.assertRaises(ksc_exceptions.CommandError, utils.find_resource, self.manager, 'asdf') def test_find_by_integer_id(self): output = utils.find_resource(self.manager, 1234) self.assertEqual(output, self.manager.resources['1234']) def test_find_by_str_id(self): output = utils.find_resource(self.manager, '1234') self.assertEqual(output, self.manager.resources['1234']) def test_find_by_uuid(self): uuid = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' output = utils.find_resource(self.manager, uuid) self.assertEqual(output, self.manager.resources[uuid]) def test_find_by_unicode(self): name = '\xe3\x82\xbdtest' output = utils.find_resource(self.manager, name) self.assertEqual(output, self.manager.resources[name]) def test_find_by_str_name(self): output = utils.find_resource(self.manager, 'entity_one') self.assertEqual(output, self.manager.resources['1234']) def test_find_by_int_name(self): output = utils.find_resource(self.manager, 9876) self.assertEqual(output, self.manager.resources['5678']) def test_find_no_unique_match(self): self.assertRaises(ksc_exceptions.CommandError, utils.find_resource, self.manager, 9999) class FakeObject(object): def __init__(self, name): self.name = name class HashSignedTokenTestCase(test_utils.TestCase, testresources.ResourcedTestCase): """Unit tests for utils.hash_signed_token().""" resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_default_md5(self): """The default hash method is md5.""" token = self.examples.SIGNED_TOKEN_SCOPED token = token.encode('utf-8') token_id_default = utils.hash_signed_token(token) token_id_md5 = utils.hash_signed_token(token, mode='md5') self.assertThat(token_id_default, matchers.Equals(token_id_md5)) # md5 hash is 32 chars. self.assertThat(token_id_default, matchers.HasLength(32)) def test_sha256(self): """Can also hash with sha256.""" token = self.examples.SIGNED_TOKEN_SCOPED token = token.encode('utf-8') token_id = utils.hash_signed_token(token, mode='sha256') # sha256 hash is 64 chars. self.assertThat(token_id, matchers.HasLength(64)) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/utils.py0000664000175000017500000001435700000000000025113 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 logging import sys import urllib.parse as urlparse import uuid import fixtures from oslo_serialization import jsonutils import requests import requests_mock from requests_mock.contrib import fixture import testscenarios import testtools from keystoneclient.tests.unit import client_fixtures class TestCase(testtools.TestCase): TEST_DOMAIN_ID = uuid.uuid4().hex TEST_DOMAIN_NAME = uuid.uuid4().hex TEST_GROUP_ID = uuid.uuid4().hex TEST_ROLE_ID = uuid.uuid4().hex TEST_TENANT_ID = uuid.uuid4().hex TEST_TENANT_NAME = uuid.uuid4().hex TEST_TOKEN = uuid.uuid4().hex TEST_TRUST_ID = uuid.uuid4().hex TEST_USER = uuid.uuid4().hex TEST_USER_ID = uuid.uuid4().hex TEST_ROOT_URL = 'http://127.0.0.1:5000/' def setUp(self): super(TestCase, self).setUp() self.deprecations = self.useFixture(client_fixtures.Deprecations()) self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) self.requests_mock = self.useFixture(fixture.Fixture()) def stub_url(self, method, parts=None, base_url=None, json=None, **kwargs): if not base_url: base_url = self.TEST_URL if json: kwargs['text'] = jsonutils.dumps(json) headers = kwargs.setdefault('headers', {}) headers['Content-Type'] = 'application/json' if parts: url = '/'.join([p.strip('/') for p in [base_url] + parts]) else: url = base_url url = url.replace("/?", "?") self.requests_mock.register_uri(method, url, **kwargs) def assertRequestBodyIs(self, body=None, json=None): last_request_body = self.requests_mock.last_request.body if json: val = jsonutils.loads(last_request_body) self.assertEqual(json, val) elif body: self.assertEqual(body, last_request_body) def assertQueryStringIs(self, qs=''): r"""Verify the QueryString matches what is expected. The qs parameter should be of the format \'foo=bar&abc=xyz\' """ expected = urlparse.parse_qs(qs, keep_blank_values=True) parts = urlparse.urlparse(self.requests_mock.last_request.url) querystring = urlparse.parse_qs(parts.query, keep_blank_values=True) self.assertEqual(expected, querystring) def assertQueryStringContains(self, **kwargs): """Verify the query string contains the expected parameters. This method is used to verify that the query string for the most recent request made contains all the parameters provided as ``kwargs``, and that the value of each parameter contains the value for the kwarg. If the value for the kwarg is an empty string (''), then all that's verified is that the parameter is present. """ parts = urlparse.urlparse(self.requests_mock.last_request.url) qs = urlparse.parse_qs(parts.query, keep_blank_values=True) for k, v in kwargs.items(): self.assertIn(k, qs) self.assertIn(v, qs[k]) def assertRequestHeaderEqual(self, name, val): """Verify that the last request made contains a header and its value. The request must have already been made. """ headers = self.requests_mock.last_request.headers self.assertEqual(headers.get(name), val) def test_response(**kwargs): r = requests.Request(method='GET', url='http://localhost:5000').prepare() return requests_mock.create_response(r, **kwargs) 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): 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 ClientTestCaseMixin(testscenarios.WithScenarios): client_fixture_class = None data_fixture_class = None def setUp(self): super(ClientTestCaseMixin, self).setUp() self.data_fixture = None self.client_fixture = None self.client = None if self.client_fixture_class: fix = self.client_fixture_class(self.requests_mock, self.deprecations) self.client_fixture = self.useFixture(fix) self.client = self.client_fixture.client self.TEST_USER_ID = self.client_fixture.user_id if self.data_fixture_class: fix = self.data_fixture_class(self.requests_mock) self.data_fixture = self.useFixture(fix) 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2350037 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/0000775000175000017500000000000000000000000024135 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/__init__.py0000664000175000017500000000000000000000000026234 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/client_fixtures.py0000664000175000017500000001234700000000000027725 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 def unscoped_token(): return fixture.V2Token(token_id='3e2813b7ba0b4006840c3825860b86ed', expires='2012-10-03T16:58:01Z', user_id='c4da488862bd435c9e6c0275a0d0e49a', user_name='exampleuser') def project_scoped_token(): _TENANT_ID = '225da22d3ce34b15877ea70b2a575f58' f = fixture.V2Token(token_id='04c7d5ffaeef485f9dc69c06db285bdb', expires='2012-10-03T16:53:36Z', tenant_id='225da22d3ce34b15877ea70b2a575f58', tenant_name='exampleproject', user_id='c4da488862bd435c9e6c0275a0d0e49a', user_name='exampleuser', audit_chain_id=uuid.uuid4().hex) f.add_role(id='member_id', name='Member') s = f.add_service('volume', 'Volume Service') s.add_endpoint(public='http://public.com:8776/v1/%s' % _TENANT_ID, admin='http://admin:8776/v1/%s' % _TENANT_ID, internal='http://internal:8776/v1/%s' % _TENANT_ID, region='RegionOne') s = f.add_service('image', 'Image Service') s.add_endpoint(public='http://public.com:9292/v1', admin='http://admin:9292/v1', internal='http://internal:9292/v1', region='RegionOne') s = f.add_service('compute', 'Compute Service') s.add_endpoint(public='http://public.com:8774/v2/%s' % _TENANT_ID, admin='http://admin:8774/v2/%s' % _TENANT_ID, internal='http://internal:8774/v2/%s' % _TENANT_ID, region='RegionOne') s = f.add_service('ec2', 'EC2 Service') s.add_endpoint(public='http://public.com:8773/services/Cloud', admin='http://admin:8773/services/Admin', internal='http://internal:8773/services/Cloud', region='RegionOne') s = f.add_service('identity', 'Identity Service') s.add_endpoint(public='http://public.com:5000/v2.0', admin='http://admin:35357/v2.0', internal='http://internal:5000/v2.0', region='RegionOne') return f def auth_response_body(): f = fixture.V2Token(token_id='ab48a9efdfedb23ty3494', expires='2010-11-01T03:32:15-05:00', tenant_id='345', tenant_name='My Project', user_id='123', user_name='jqsmith', audit_chain_id=uuid.uuid4().hex) f.add_role(id='234', name='compute:admin') role = f.add_role(id='235', name='object-store:admin') role['tenantId'] = '1' s = f.add_service('compute', 'Cloud Servers') endpoint = s.add_endpoint(public='https://compute.north.host/v1/1234', internal='https://compute.north.host/v1/1234', region='North') endpoint['tenantId'] = '1' endpoint['versionId'] = '1.0' endpoint['versionInfo'] = 'https://compute.north.host/v1.0/' endpoint['versionList'] = 'https://compute.north.host/' endpoint = s.add_endpoint(public='https://compute.north.host/v1.1/3456', internal='https://compute.north.host/v1.1/3456', region='North') endpoint['tenantId'] = '2' endpoint['versionId'] = '1.1' endpoint['versionInfo'] = 'https://compute.north.host/v1.1/' endpoint['versionList'] = 'https://compute.north.host/' s = f.add_service('object-store', 'Cloud Files') endpoint = s.add_endpoint(public='https://swift.north.host/v1/blah', internal='https://swift.north.host/v1/blah', region='South') endpoint['tenantId'] = '11' endpoint['versionId'] = '1.0' endpoint['versionInfo'] = 'uri' endpoint['versionList'] = 'uri' endpoint = s.add_endpoint(public='https://swift.north.host/v1.1/blah', internal='https://compute.north.host/v1.1/blah', region='South') endpoint['tenantId'] = '2' endpoint['versionId'] = '1.1' endpoint['versionInfo'] = 'https://swift.north.host/v1.1/' endpoint['versionList'] = 'https://swift.north.host/' s = f.add_service('image', 'Image Servers') s.add_endpoint(public='https://image.north.host/v1/', internal='https://image-internal.north.host/v1/', region='North') s.add_endpoint(public='https://image.south.host/v1/', internal='https://image-internal.south.host/v1/', region='South') return f ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_access.py0000664000175000017500000002061300000000000027011 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 keystoneauth1 import fixture from oslo_utils import timeutils import testresources from keystoneclient import access from keystoneclient.tests.unit import client_fixtures as token_data from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class AccessInfoTest(utils.TestCase, testresources.ResourcedTestCase): resources = [('examples', token_data.EXAMPLES_RESOURCE)] def test_building_unscoped_accessinfo(self): token = client_fixtures.unscoped_token() auth_ref = access.AccessInfo.factory(body=token) self.assertTrue(auth_ref) self.assertIn('token', auth_ref) self.assertEqual(auth_ref.auth_token, '3e2813b7ba0b4006840c3825860b86ed') self.assertEqual(auth_ref.username, 'exampleuser') self.assertEqual(auth_ref.user_id, 'c4da488862bd435c9e6c0275a0d0e49a') self.assertEqual(auth_ref.role_ids, []) self.assertEqual(auth_ref.role_names, []) self.assertIsNone(auth_ref.tenant_name) self.assertIsNone(auth_ref.tenant_id) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.auth_url) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.management_url) with self.deprecations.expect_deprecations_here(): self.assertFalse(auth_ref.scoped) self.assertFalse(auth_ref.domain_scoped) self.assertFalse(auth_ref.project_scoped) self.assertFalse(auth_ref.trust_scoped) self.assertIsNone(auth_ref.project_domain_id) self.assertIsNone(auth_ref.project_domain_name) self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') self.assertEqual(auth_ref.expires, token.expires) self.assertEqual(auth_ref.issued, token.issued) self.assertEqual(token.audit_id, auth_ref.audit_id) self.assertIsNone(auth_ref.audit_chain_id) self.assertIsNone(token.audit_chain_id) def test_will_expire_soon(self): token = client_fixtures.unscoped_token() expires = timeutils.utcnow() + datetime.timedelta(minutes=5) token.expires = expires auth_ref = access.AccessInfo.factory(body=token) self.assertFalse(auth_ref.will_expire_soon(stale_duration=120)) self.assertTrue(auth_ref.will_expire_soon(stale_duration=300)) self.assertFalse(auth_ref.will_expire_soon()) def test_building_scoped_accessinfo(self): token = client_fixtures.project_scoped_token() auth_ref = access.AccessInfo.factory(body=token) self.assertTrue(auth_ref) self.assertIn('token', auth_ref) self.assertIn('serviceCatalog', auth_ref) self.assertTrue(auth_ref['serviceCatalog']) self.assertEqual(auth_ref.auth_token, '04c7d5ffaeef485f9dc69c06db285bdb') self.assertEqual(auth_ref.username, 'exampleuser') self.assertEqual(auth_ref.user_id, 'c4da488862bd435c9e6c0275a0d0e49a') self.assertEqual(auth_ref.role_ids, ['member_id']) self.assertEqual(auth_ref.role_names, ['Member']) self.assertEqual(auth_ref.tenant_name, 'exampleproject') self.assertEqual(auth_ref.tenant_id, '225da22d3ce34b15877ea70b2a575f58') self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.auth_url, ('http://public.com:5000/v2.0',)) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.management_url, ('http://admin:35357/v2.0',)) self.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') with self.deprecations.expect_deprecations_here(): self.assertTrue(auth_ref.scoped) self.assertTrue(auth_ref.project_scoped) self.assertFalse(auth_ref.domain_scoped) self.assertEqual(token.audit_id, auth_ref.audit_id) self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) def test_diablo_token(self): diablo_token = self.examples.TOKEN_RESPONSES[ self.examples.VALID_DIABLO_TOKEN] auth_ref = access.AccessInfo.factory(body=diablo_token) self.assertTrue(auth_ref) self.assertEqual(auth_ref.username, 'user_name1') self.assertEqual(auth_ref.project_id, 'tenant_id1') self.assertEqual(auth_ref.project_name, 'tenant_id1') self.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') self.assertEqual(auth_ref.role_names, ['role1', 'role2']) with self.deprecations.expect_deprecations_here(): self.assertFalse(auth_ref.scoped) def test_grizzly_token(self): grizzly_token = self.examples.TOKEN_RESPONSES[ self.examples.SIGNED_TOKEN_SCOPED_KEY] auth_ref = access.AccessInfo.factory(body=grizzly_token) self.assertEqual(auth_ref.project_id, 'tenant_id1') self.assertEqual(auth_ref.project_name, 'tenant_name1') self.assertEqual(auth_ref.project_domain_id, 'default') self.assertEqual(auth_ref.project_domain_name, 'Default') self.assertEqual(auth_ref.user_domain_id, 'default') self.assertEqual(auth_ref.user_domain_name, 'Default') self.assertEqual(auth_ref.role_names, ['role1', 'role2']) def test_v2_roles(self): role_id = 'a' role_name = 'b' token = fixture.V2Token() token.set_scope() token.add_role(id=role_id, name=role_name) auth_ref = access.AccessInfo.factory(body=token) self.assertEqual([role_id], auth_ref.role_ids) self.assertEqual([role_id], auth_ref['metadata']['roles']) self.assertEqual([role_name], auth_ref.role_names) self.assertEqual([{'name': role_name}], auth_ref['user']['roles']) def test_trusts(self): user_id = uuid.uuid4().hex trust_id = uuid.uuid4().hex token = fixture.V2Token(user_id=user_id, trust_id=trust_id) token.set_scope() token.add_role() auth_ref = access.AccessInfo.factory(body=token) self.assertEqual(trust_id, auth_ref.trust_id) self.assertEqual(user_id, auth_ref.trustee_user_id) self.assertEqual(trust_id, token['access']['trust']['id']) def test_override_auth_token(self): token = fixture.V2Token() token.set_scope() token.add_role() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token) self.assertEqual(token.token_id, auth_ref.auth_token) auth_ref.auth_token = new_auth_token self.assertEqual(new_auth_token, auth_ref.auth_token) del auth_ref.auth_token self.assertEqual(token.token_id, auth_ref.auth_token) def test_override_auth_token_in_factory(self): token = fixture.V2Token() token.set_scope() token.add_role() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token, auth_token=new_auth_token) self.assertEqual(new_auth_token, auth_ref.auth_token) del auth_ref.auth_token self.assertEqual(token.token_id, auth_ref.auth_token) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_auth.py0000664000175000017500000002645500000000000026523 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 import datetime from oslo_serialization import jsonutils from oslo_utils import timeutils from testtools import testcase from keystoneclient import exceptions from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): def setUp(self): super(AuthenticateAgainstKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "access": { "token": { "expires": "2999-01-01T00:00:10.000123Z", "id": self.TEST_TOKEN, "tenant": { "id": self.TEST_TENANT_ID }, }, "user": { "id": self.TEST_USER }, "serviceCatalog": self.TEST_SERVICE_CATALOG, }, } self.TEST_REQUEST_BODY = { "auth": { "passwordCredentials": { "username": self.TEST_USER, "password": self.TEST_TOKEN, }, "tenantId": self.TEST_TENANT_ID, }, } def test_authenticate_success_expired(self): resp_a = copy.deepcopy(self.TEST_RESPONSE_DICT) resp_b = copy.deepcopy(self.TEST_RESPONSE_DICT) headers = {'Content-Type': 'application/json'} # Build an expired token resp_a['access']['token']['expires'] = ( (timeutils.utcnow() - datetime.timedelta(1)).isoformat()) # Build a new response TEST_TOKEN = "abcdef" resp_b['access']['token']['expires'] = '2999-01-01T00:00:10.000123Z' resp_b['access']['token']['id'] = TEST_TOKEN # return expired first, and then the new response self.stub_auth(response_list=[{'json': resp_a, 'headers': headers}, {'json': resp_b, 'headers': headers}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL, username=self.TEST_USER, password=self.TEST_TOKEN) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) with self.deprecations.expect_deprecations_here(): self.assertEqual(cs.auth_token, TEST_TOKEN) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_failure(self): _auth = 'auth' _cred = 'passwordCredentials' _pass = 'password' self.TEST_REQUEST_BODY[_auth][_cred][_pass] = 'bad_key' error = {"unauthorized": {"message": "Unauthorized", "code": "401"}} self.stub_auth(status_code=401, json=error) with testcase.ExpectedException(exceptions.Unauthorized): with self.deprecations.expect_deprecations_here(): client.Client(username=self.TEST_USER, password="bad_key", project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_redirect(self): self.stub_auth(status_code=305, text='Use Proxy', headers={'Location': self.TEST_ADMIN_URL + "/tokens"}) self.stub_auth(base_url=self.TEST_ADMIN_URL, json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_unscoped(self): del self.TEST_RESPONSE_DICT['access']['serviceCatalog'] del self.TEST_REQUEST_BODY['auth']['tenantId'] self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(username=self.TEST_USER, password=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): fake_token = 'fake_token' fake_url = '/fake-url' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) with self.deprecations.expect_deprecations_here(): cl = client.Client(auth_url=self.TEST_URL, token=fake_token) json_body = jsonutils.loads(self.requests_mock.last_request.body) self.assertEqual(json_body['auth']['token']['id'], fake_token) with self.deprecations.expect_deprecations_here(): resp, body = cl.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) def test_authenticate_success_token_scoped(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] ['endpoints'][0]["adminURL"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_scoped_trust(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.TEST_REQUEST_BODY['auth']['trust_id'] = self.TEST_TRUST_ID response = self.TEST_RESPONSE_DICT.copy() response['access']['trust'] = {"trustee_user_id": self.TEST_USER, "id": self.TEST_TRUST_ID} self.stub_auth(json=response) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, trust_id=self.TEST_TRUST_ID, auth_url=self.TEST_URL) self.assertTrue(cs.auth_ref.trust_scoped) self.assertEqual(cs.auth_ref.trust_id, self.TEST_TRUST_ID) self.assertEqual(cs.auth_ref.trustee_user_id, self.TEST_USER) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_unscoped(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] del self.TEST_REQUEST_BODY['auth']['tenantId'] del self.TEST_RESPONSE_DICT['access']['serviceCatalog'] self.TEST_REQUEST_BODY['auth']['token'] = {'id': self.TEST_TOKEN} self.stub_auth(json=self.TEST_RESPONSE_DICT) with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertNotIn('serviceCatalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): fake_url = '/fake-url' fake_token = 'fake_token' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.auth_token, self.TEST_TOKEN) # the token returned from the authentication will be used resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) # then override that token and the new token shall be used cl.auth_token = fake_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(fake_token, token) # if we clear that overridden token then we fall back to the original del cl.auth_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_certificates.py0000664000175000017500000000313600000000000030216 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. import testresources from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class CertificateTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_get_ca_certificate(self): self.stub_url('GET', ['certificates', 'ca'], headers={'Content-Type': 'text/html; charset=UTF-8'}, text=self.examples.SIGNING_CA) res = self.client.certificates.get_ca_certificate() self.assertEqual(self.examples.SIGNING_CA, res) def test_get_signing_certificate(self): self.stub_url('GET', ['certificates', 'signing'], headers={'Content-Type': 'text/html; charset=UTF-8'}, text=self.examples.SIGNING_CERT) res = self.client.certificates.get_signing_certificate() self.assertEqual(self.examples.SIGNING_CERT, res) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_client.py0000664000175000017500000002343000000000000027026 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 oslo_serialization import jsonutils from keystoneauth1 import fixture from keystoneauth1 import session as auth_session from keystoneclient.auth import token_endpoint from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client class KeystoneClientTest(utils.TestCase): def test_unscoped_init(self): token = client_fixtures.unscoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username='exampleuser', password='password', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertFalse(c.auth_ref.scoped) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertIsNone(c.auth_ref.trust_id) self.assertFalse(c.auth_ref.trust_scoped) self.assertIsNone(c.get_project_id(session=None)) self.assertEqual(token.user_id, c.get_user_id(session=None)) def test_scoped_init(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(c.auth_ref.scoped) self.assertTrue(c.auth_ref.project_scoped) self.assertFalse(c.auth_ref.domain_scoped) self.assertIsNone(c.auth_ref.trust_id) self.assertFalse(c.auth_ref.trust_scoped) self.assertEqual(token.tenant_id, c.get_project_id(session=None)) self.assertEqual(token.user_id, c.get_user_id(session=None)) def test_auth_ref_load(self): self.stub_auth(json=client_fixtures.project_scoped_token()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) cache = jsonutils.dumps(cl.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache)) self.assertIsNotNone(new_client.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(new_client.auth_ref.scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertIsNone(new_client.auth_ref.trust_id) self.assertFalse(new_client.auth_ref.trust_scoped) self.assertEqual(new_client.username, 'exampleuser') self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v2.0') def test_auth_ref_load_with_overridden_arguments(self): self.stub_auth(json=client_fixtures.project_scoped_token()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) cache = jsonutils.dumps(cl.auth_ref) new_auth_url = "http://new-public:5000/v2.0" # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache), auth_url=new_auth_url) self.assertIsNotNone(new_client.auth_ref) with self.deprecations.expect_deprecations_here(): self.assertTrue(new_client.auth_ref.scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertIsNone(new_client.auth_ref.trust_id) self.assertFalse(new_client.auth_ref.trust_scoped) self.assertEqual(new_client.auth_url, new_auth_url) self.assertEqual(new_client.username, 'exampleuser') self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v2.0') def test_init_err_no_auth_url(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, username='exampleuser', password='password') def test_management_url_is_updated(self): first = fixture.V2Token() first.set_scope() admin_url = 'http://admin:35357/v2.0' second_url = 'http://secondurl:35357/v2.0' s = first.add_service('identity') s.add_endpoint(public='http://public.com:5000/v2.0', admin=admin_url) second = fixture.V2Token() second.set_scope() s = second.add_service('identity') s.add_endpoint(public='http://secondurl:5000/v2.0', admin=second_url) self.stub_auth(response_list=[{'json': first}, {'json': second}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.management_url, admin_url) with self.deprecations.expect_deprecations_here(): cl.authenticate() self.assertEqual(cl.management_url, second_url) def test_client_with_region_name_passes_to_service_catalog(self): # NOTE(jamielennox): this is deprecated behaviour that should be # removed ASAP, however must remain compatible. self.stub_auth(json=client_fixtures.auth_response_body()) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='North') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'https://image.north.host/v1/') # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='South') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'https://image.south.host/v1/') def test_client_without_auth_params(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, project_name='exampleproject', auth_url=self.TEST_URL) def test_client_params(self): with self.deprecations.expect_deprecations_here(): sess = session.Session() auth = token_endpoint.Token('a', 'b') opts = {'auth': auth, 'connect_retries': 50, 'endpoint_override': uuid.uuid4().hex, 'interface': uuid.uuid4().hex, 'region_name': uuid.uuid4().hex, 'service_name': uuid.uuid4().hex, 'user_agent': uuid.uuid4().hex, } cl = client.Client(session=sess, **opts) for k, v in opts.items(): self.assertEqual(v, getattr(cl._adapter, k)) self.assertEqual('identity', cl._adapter.service_type) self.assertEqual((2, 0), cl._adapter.version) def test_empty_service_catalog_param(self): # Client().service_catalog should return None if the client is not # authenticated sess = auth_session.Session() cl = client.Client(session=sess) self.assertIsNone(cl.service_catalog) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_discovery.py0000664000175000017500000000675700000000000027574 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 keystoneclient.generic import client from keystoneclient.tests.unit.v2_0 import utils class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): def setUp(self): super(DiscoverKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "versions": { "values": [{ "id": "v2.0", "status": "beta", "updated": "2011-11-19T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", }, {"rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://127.0.0.1:5000/v2.0/identity.wadl", } ], "media-types": [{ "base": "application/xml", "type": "application/vnd.openstack.identity-v2.0+xml", }, { "base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json", }], }], }, } def test_get_versions(self): self.stub_url('GET', base_url=self.TEST_ROOT_URL, json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover(self.TEST_ROOT_URL) self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v2.0', versions) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) def test_get_version_local(self): self.stub_url('GET', base_url="http://localhost:35357/", json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover() self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v2.0', versions) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_ec2.py0000664000175000017500000000730100000000000026220 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import ec2 class EC2Tests(utils.ClientTestCase): def test_create(self): user_id = 'usr' tenant_id = 'tnt' req_body = { "tenant_id": tenant_id, } resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('POST', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) cred = self.client.ec2.create(user_id, tenant_id) self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') self.assertRequestBodyIs(json=req_body) def test_get(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2', 'access'], json=resp_body) cred = self.client.ec2.get(user_id, 'access') self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_list(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credentials": { "values": [ { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, }, { "access": "another", "secret": "key", "tenant_id": tenant_id, "created": "12/12/31", "enabled": True, } ] } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) creds = self.client.ec2.list(user_id) self.assertEqual(len(creds), 2) cred = creds[0] self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_delete(self): user_id = 'usr' access = 'access' self.stub_url('DELETE', ['users', user_id, 'credentials', 'OS-EC2', access], status_code=204) self.client.ec2.delete(user_id, access) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_endpoints.py0000664000175000017500000001302700000000000027554 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import endpoints class EndpointTests(utils.ClientTestCase): def setUp(self): super(EndpointTests, self).setUp() self.TEST_ENDPOINTS = { 'endpoints': [ { 'adminurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'id': '8f9531231e044e218824b0e58688d262', 'internalurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'publicurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'region': 'RegionOne', }, { 'adminurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'id': '8f9531231e044e218824b0e58688d263', 'internalurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'publicurl': 'http://host-1:8774/v1.1/$(tenant_id)s', 'region': 'RegionOne', } ] } def test_create_with_optional_params(self): req_body = { "endpoint": { "region": "RegionOne", "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "internalurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": "http://host-3:8774/v1.1/$(tenant_id)s", "service_id": uuid.uuid4().hex, } } resp_body = { "endpoint": { "adminurl": "http://host-3:8774/v1.1/$(tenant_id)s", "region": "RegionOne", "id": uuid.uuid4().hex, "internalurl": "http://host-3:8774/v1.1/$(tenant_id)s", "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint = self.client.endpoints.create( region=req_body['endpoint']['region'], publicurl=req_body['endpoint']['publicurl'], adminurl=req_body['endpoint']['adminurl'], internalurl=req_body['endpoint']['internalurl'], service_id=req_body['endpoint']['service_id'] ) self.assertIsInstance(endpoint, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body) def test_create_with_optional_params_as_none(self): req_body_without_defaults = { "endpoint": { "region": "RegionOne", "service_id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } resp_body = { "endpoint": { "region": "RegionOne", "id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint_without_defaults = self.client.endpoints.create( region=req_body_without_defaults['endpoint']['region'], publicurl=req_body_without_defaults['endpoint']['publicurl'], service_id=req_body_without_defaults['endpoint']['service_id'], adminurl=None, internalurl=None ) self.assertIsInstance(endpoint_without_defaults, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body_without_defaults) def test_create_without_optional_params(self): req_body_without_defaults = { "endpoint": { "region": "RegionOne", "service_id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } resp_body = { "endpoint": { "region": "RegionOne", "id": uuid.uuid4().hex, "publicurl": "http://host-3:8774/v1.1/$(tenant_id)s", "adminurl": None, "internalurl": None, } } self.stub_url('POST', ['endpoints'], json=resp_body) endpoint_without_defaults = self.client.endpoints.create( region=req_body_without_defaults['endpoint']['region'], publicurl=req_body_without_defaults['endpoint']['publicurl'], service_id=req_body_without_defaults['endpoint']['service_id'] ) self.assertIsInstance(endpoint_without_defaults, endpoints.Endpoint) self.assertRequestBodyIs(json=req_body_without_defaults) def test_delete(self): self.stub_url('DELETE', ['endpoints', '8f953'], status_code=204) self.client.endpoints.delete('8f953') def test_list(self): self.stub_url('GET', ['endpoints'], json=self.TEST_ENDPOINTS) endpoint_list = self.client.endpoints.list() [self.assertIsInstance(r, endpoints.Endpoint) for r in endpoint_list] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_extensions.py0000664000175000017500000000542700000000000027755 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import extensions class ExtensionTests(utils.ClientTestCase): def setUp(self): super(ExtensionTests, self).setUp() self.TEST_EXTENSIONS = { 'extensions': { "values": [ { 'name': 'OpenStack Keystone User CRUD', 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-KSCRUD/v1.0', 'updated': '2013-07-07T12:00:0-00:00', 'alias': 'OS-KSCRUD', 'description': 'OpenStack extensions to Keystone v2.0 API' ' enabling User Operations.', 'links': '[{"href":' '"https://github.com/openstack/identity-api", "type":' ' "text/html", "rel": "describedby"}]', }, { 'name': 'OpenStack EC2 API', 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-EC2/v1.0', 'updated': '2013-09-07T12:00:0-00:00', 'alias': 'OS-EC2', 'description': 'OpenStack EC2 Credentials backend.', 'links': '[{"href":' '"https://github.com/openstack/identity-api", "type":' ' "text/html", "rel": "describedby"}]', } ] } } def test_list(self): self.stub_url('GET', ['extensions'], json=self.TEST_EXTENSIONS) extensions_list = self.client.extensions.list() self.assertEqual(2, len(extensions_list)) for extension in extensions_list: self.assertIsInstance(extension, extensions.Extension) self.assertIsNotNone(extension.alias) self.assertIsNotNone(extension.description) self.assertIsNotNone(extension.links) self.assertIsNotNone(extension.name) self.assertIsNotNone(extension.namespace) self.assertIsNotNone(extension.updated) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_roles.py0000664000175000017500000001035000000000000026671 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import roles class RoleTests(utils.ClientTestCase): def setUp(self): super(RoleTests, self).setUp() self.ADMIN_ROLE_ID = uuid.uuid4().hex self.MEMBER_ROLE_ID = uuid.uuid4().hex self.TEST_ROLES = { "roles": { "values": [ { "name": "admin", "id": self.ADMIN_ROLE_ID, }, { "name": "member", "id": self.MEMBER_ROLE_ID, } ], }, } def test_create(self): req_body = { "role": { "name": "sysadmin", } } role_id = uuid.uuid4().hex resp_body = { "role": { "name": "sysadmin", "id": role_id, } } self.stub_url('POST', ['OS-KSADM', 'roles'], json=resp_body) role = self.client.roles.create(req_body['role']['name']) self.assertRequestBodyIs(json=req_body) self.assertIsInstance(role, roles.Role) self.assertEqual(role.id, role_id) self.assertEqual(role.name, req_body['role']['name']) def test_delete(self): self.stub_url('DELETE', ['OS-KSADM', 'roles', self.ADMIN_ROLE_ID], status_code=204) self.client.roles.delete(self.ADMIN_ROLE_ID) def test_get(self): self.stub_url('GET', ['OS-KSADM', 'roles', self.ADMIN_ROLE_ID], json={'role': self.TEST_ROLES['roles']['values'][0]}) role = self.client.roles.get(self.ADMIN_ROLE_ID) self.assertIsInstance(role, roles.Role) self.assertEqual(role.id, self.ADMIN_ROLE_ID) self.assertEqual(role.name, 'admin') def test_list(self): self.stub_url('GET', ['OS-KSADM', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.list() [self.assertIsInstance(r, roles.Role) for r in role_list] def test_roles_for_user(self): self.stub_url('GET', ['users', 'foo', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.roles_for_user('foo') [self.assertIsInstance(r, roles.Role) for r in role_list] def test_roles_for_user_tenant(self): self.stub_url('GET', ['tenants', 'barrr', 'users', 'foo', 'roles'], json=self.TEST_ROLES) role_list = self.client.roles.roles_for_user('foo', 'barrr') [self.assertIsInstance(r, roles.Role) for r in role_list] def test_add_user_role(self): self.stub_url('PUT', ['users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.add_user_role('foo', 'barrr') def test_add_user_role_tenant(self): id_ = uuid.uuid4().hex self.stub_url('PUT', ['tenants', id_, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.add_user_role('foo', 'barrr', id_) def test_remove_user_role(self): self.stub_url('DELETE', ['users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.remove_user_role('foo', 'barrr') def test_remove_user_role_tenant(self): id_ = uuid.uuid4().hex self.stub_url('DELETE', ['tenants', id_, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.roles.remove_user_role('foo', 'barrr', id_) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_service_catalog.py0000664000175000017500000002211000000000000030674 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 fixture from keystoneclient import access from keystoneclient import exceptions from keystoneclient.tests.unit.v2_0 import client_fixtures from keystoneclient.tests.unit.v2_0 import utils class ServiceCatalogTest(utils.TestCase): def setUp(self): super(ServiceCatalogTest, self).setUp() self.AUTH_RESPONSE_BODY = client_fixtures.auth_response_body() def test_building_a_service_catalog(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog self.assertEqual(sc.url_for(service_type='compute'), "https://compute.north.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '1', service_type='compute'), "https://compute.north.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '2', service_type='compute'), "https://compute.north.host/v1.1/3456") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='publicURL') self.assertEqual(public_ep['compute'][1]['tenantId'], '2') self.assertEqual(public_ep['compute'][1]['versionId'], '1.1') self.assertEqual(public_ep['compute'][1]['internalURL'], "https://compute.north.host/v1.1/3456") def test_service_catalog_regions(self): self.AUTH_RESPONSE_BODY['access']['region_name'] = "North" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='publicURL') self.assertEqual(url, "https://image.north.host/v1/") self.AUTH_RESPONSE_BODY['access']['region_name'] = "South" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='internalURL') self.assertEqual(url, "https://image-internal.south.host/v1/") def test_service_catalog_empty(self): self.AUTH_RESPONSE_BODY['access']['serviceCatalog'] = [] auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) self.assertRaises(exceptions.EmptyCatalog, auth_ref.service_catalog.url_for, service_type='image', endpoint_type='internalURL') def test_service_catalog_get_endpoints_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog endpoints = sc.get_endpoints(service_type='image', region_name='North') self.assertEqual(len(endpoints), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.north.host/v1/') endpoints = sc.get_endpoints(service_type='image', region_name='South') self.assertEqual(len(endpoints), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.south.host/v1/') endpoints = sc.get_endpoints(service_type='compute') self.assertEqual(len(endpoints['compute']), 2) endpoints = sc.get_endpoints(service_type='compute', region_name='North') self.assertEqual(len(endpoints['compute']), 2) endpoints = sc.get_endpoints(service_type='compute', region_name='West') self.assertEqual(len(endpoints['compute']), 0) def test_service_catalog_url_for_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', region_name='North') self.assertEqual(url, 'https://image.north.host/v1/') url = sc.url_for(service_type='image', region_name='South') self.assertEqual(url, 'https://image.south.host/v1/') url = sc.url_for(service_type='compute', region_name='North', attr='versionId', filter_value='1.1') self.assertEqual(url, 'https://compute.north.host/v1.1/3456') self.assertRaises(exceptions.EndpointNotFound, sc.url_for, service_type='image', region_name='West') def test_servcie_catalog_get_url_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog urls = sc.get_urls(service_type='image') self.assertEqual(len(urls), 2) urls = sc.get_urls(service_type='image', region_name='North') self.assertEqual(len(urls), 1) self.assertEqual(urls[0], 'https://image.north.host/v1/') urls = sc.get_urls(service_type='image', region_name='South') self.assertEqual(len(urls), 1) self.assertEqual(urls[0], 'https://image.south.host/v1/') urls = sc.get_urls(service_type='image', region_name='West') self.assertIsNone(urls) def test_service_catalog_param_overrides_body_region(self): self.AUTH_RESPONSE_BODY['access']['region_name'] = "North" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image') self.assertEqual(url, 'https://image.north.host/v1/') url = sc.url_for(service_type='image', region_name='South') self.assertEqual(url, 'https://image.south.host/v1/') endpoints = sc.get_endpoints(service_type='image') self.assertEqual(len(endpoints['image']), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.north.host/v1/') endpoints = sc.get_endpoints(service_type='image', region_name='South') self.assertEqual(len(endpoints['image']), 1) self.assertEqual(endpoints['image'][0]['publicURL'], 'https://image.south.host/v1/') def test_service_catalog_service_name(self): auth_ref = access.AccessInfo.factory(resp=None, body=self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_name='Image Servers', endpoint_type='public', service_type='image', region_name='North') self.assertEqual('https://image.north.host/v1/', url) self.assertRaises(exceptions.EndpointNotFound, sc.url_for, service_name='Image Servers', service_type='compute') urls = sc.get_urls(service_type='image', service_name='Image Servers', endpoint_type='public') self.assertIn('https://image.north.host/v1/', urls) self.assertIn('https://image.south.host/v1/', urls) urls = sc.get_urls(service_type='image', service_name='Servers', endpoint_type='public') self.assertIsNone(urls) def test_service_catalog_multiple_service_types(self): token = fixture.V2Token() token.set_scope() for i in range(3): s = token.add_service('compute') s.add_endpoint(public='public-%d' % i, admin='admin-%d' % i, internal='internal-%d' % i, region='region-%d' % i) auth_ref = access.AccessInfo.factory(resp=None, body=token) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='publicURL') self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='publicURL', region_name='region-1') self.assertEqual(('public-1', ), urls) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_services.py0000664000175000017500000001123300000000000027371 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import services class ServiceTests(utils.ClientTestCase): def setUp(self): super(ServiceTests, self).setUp() self.NOVA_SERVICE_ID = uuid.uuid4().hex self.KEYSTONE_SERVICE_ID = uuid.uuid4().hex self.TEST_SERVICES = { "OS-KSADM:services": { "values": [ { "name": "nova", "type": "compute", "description": "Nova-compatible service.", "id": self.NOVA_SERVICE_ID }, { "name": "keystone", "type": "identity", "description": "Keystone-compatible service.", "id": self.KEYSTONE_SERVICE_ID }, ], }, } def test_create_with_description(self): req_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": "Swift-compatible service.", } } service_id = uuid.uuid4().hex resp_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": "Swift-compatible service.", "id": service_id, } } self.stub_url('POST', ['OS-KSADM', 'services'], json=resp_body) service = self.client.services.create( req_body['OS-KSADM:service']['name'], req_body['OS-KSADM:service']['type'], req_body['OS-KSADM:service']['description']) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, service_id) self.assertEqual(service.name, req_body['OS-KSADM:service']['name']) self.assertEqual(service.description, req_body['OS-KSADM:service']['description']) self.assertRequestBodyIs(json=req_body) def test_create_without_description(self): req_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "description": None, } } service_id = uuid.uuid4().hex resp_body = { "OS-KSADM:service": { "name": "swift", "type": "object-store", "id": service_id, "description": None, } } self.stub_url('POST', ['OS-KSADM', 'services'], json=resp_body) service = self.client.services.create( req_body['OS-KSADM:service']['name'], req_body['OS-KSADM:service']['type'], req_body['OS-KSADM:service']['description']) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, service_id) self.assertEqual(service.name, req_body['OS-KSADM:service']['name']) self.assertIsNone(service.description) self.assertRequestBodyIs(json=req_body) def test_delete(self): self.stub_url('DELETE', ['OS-KSADM', 'services', self.NOVA_SERVICE_ID], status_code=204) self.client.services.delete(self.NOVA_SERVICE_ID) def test_get(self): test_services = self.TEST_SERVICES['OS-KSADM:services']['values'][0] self.stub_url('GET', ['OS-KSADM', 'services', self.NOVA_SERVICE_ID], json={'OS-KSADM:service': test_services}) service = self.client.services.get(self.NOVA_SERVICE_ID) self.assertIsInstance(service, services.Service) self.assertEqual(service.id, self.NOVA_SERVICE_ID) self.assertEqual(service.name, 'nova') self.assertEqual(service.type, 'compute') def test_list(self): self.stub_url('GET', ['OS-KSADM', 'services'], json=self.TEST_SERVICES) service_list = self.client.services.list() [self.assertIsInstance(r, services.Service) for r in service_list] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_tenants.py0000664000175000017500000003203000000000000027220 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 exceptions from keystoneauth1 import fixture from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import users class TenantTests(utils.ClientTestCase): def setUp(self): super(TenantTests, self).setUp() self.INVIS_ID = uuid.uuid4().hex self.DEMO_ID = uuid.uuid4().hex self.ADMIN_ID = uuid.uuid4().hex self.EXTRAS_ID = uuid.uuid4().hex self.TEST_TENANTS = { "tenants": { "values": [ { "enabled": True, "description": "A description change!", "name": "invisible_to_admin", "id": self.INVIS_ID, }, { "enabled": True, "description": "None", "name": "demo", "id": self.DEMO_ID, }, { "enabled": True, "description": "None", "name": "admin", "id": self.ADMIN_ID, }, { "extravalue01": "metadata01", "enabled": True, "description": "For testing extras", "name": "test_extras", "id": self.EXTRAS_ID, } ], "links": [], }, } def test_create(self): req_body = { "tenant": { "name": "tenantX", "description": "Like tenant 9, but better.", "enabled": True, "extravalue01": "metadata01", }, } id_ = uuid.uuid4().hex resp_body = { "tenant": { "name": "tenantX", "enabled": True, "id": id_, "description": "Like tenant 9, but better.", "extravalue01": "metadata01", } } self.stub_url('POST', ['tenants'], json=resp_body) tenant = self.client.tenants.create( req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled'], extravalue01=req_body['tenant']['extravalue01'], name="don't overwrite priors") self.assertIsInstance(tenant, tenants.Tenant) self.assertEqual(tenant.id, id_) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "Like tenant 9, but better.") self.assertEqual(tenant.extravalue01, "metadata01") self.assertRequestBodyIs(json=req_body) def test_duplicate_create(self): req_body = { "tenant": { "name": "tenantX", "description": "The duplicate tenant.", "enabled": True }, } resp_body = { "error": { "message": "Conflict occurred attempting to store project.", "code": 409, "title": "Conflict", } } self.stub_url('POST', ['tenants'], status_code=409, json=resp_body) def create_duplicate_tenant(): self.client.tenants.create(req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled']) self.assertRaises(exceptions.Conflict, create_duplicate_tenant) def test_delete(self): self.stub_url('DELETE', ['tenants', self.ADMIN_ID], status_code=204) self.client.tenants.delete(self.ADMIN_ID) def test_get(self): resp = {'tenant': self.TEST_TENANTS['tenants']['values'][2]} self.stub_url('GET', ['tenants', self.ADMIN_ID], json=resp) t = self.client.tenants.get(self.ADMIN_ID) self.assertIsInstance(t, tenants.Tenant) self.assertEqual(t.id, self.ADMIN_ID) self.assertEqual(t.name, 'admin') def test_list(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list() [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_limit(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(limit=1) self.assertQueryStringIs('limit=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_marker(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(marker=1) self.assertQueryStringIs('marker=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_list_limit_marker(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list(limit=1, marker=1) self.assertQueryStringIs('marker=1&limit=1') [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] def test_update(self): req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, "extravalue01": "metadataChanged", # "extraname": "dontoverwrite!", }, } resp_body = { "tenant": { "name": "tenantX", "enabled": False, "id": self.EXTRAS_ID, "description": "I changed you!", "extravalue01": "metadataChanged", }, } self.stub_url('POST', ['tenants', self.EXTRAS_ID], json=resp_body) tenant = self.client.tenants.update( req_body['tenant']['id'], req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled'], extravalue01=req_body['tenant']['extravalue01'], name="don't overwrite priors") self.assertIsInstance(tenant, tenants.Tenant) self.assertRequestBodyIs(json=req_body) self.assertEqual(tenant.id, self.EXTRAS_ID) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "I changed you!") self.assertFalse(tenant.enabled) self.assertEqual(tenant.extravalue01, "metadataChanged") def test_update_empty_description(self): req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "", "enabled": False, }, } resp_body = { "tenant": { "name": "tenantX", "enabled": False, "id": self.EXTRAS_ID, "description": "", }, } self.stub_url('POST', ['tenants', self.EXTRAS_ID], json=resp_body) tenant = self.client.tenants.update(req_body['tenant']['id'], req_body['tenant']['name'], req_body['tenant']['description'], req_body['tenant']['enabled']) self.assertIsInstance(tenant, tenants.Tenant) self.assertRequestBodyIs(json=req_body) self.assertEqual(tenant.id, self.EXTRAS_ID) self.assertEqual(tenant.name, "tenantX") self.assertEqual(tenant.description, "") self.assertFalse(tenant.enabled) def test_add_user(self): self.stub_url('PUT', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.tenants.add_user(self.EXTRAS_ID, 'foo', 'barrr') def test_remove_user(self): self.stub_url('DELETE', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) self.client.tenants.remove_user(self.EXTRAS_ID, 'foo', 'barrr') def test_tenant_add_user(self): self.stub_url('PUT', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, }, } # make tenant object with manager tenant = self.client.tenants.resource_class(self.client.tenants, req_body['tenant']) tenant.add_user('foo', 'barrr') self.assertIsInstance(tenant, tenants.Tenant) def test_tenant_remove_user(self): self.stub_url('DELETE', ['tenants', self.EXTRAS_ID, 'users', 'foo', 'roles', 'OS-KSADM', 'barrr'], status_code=204) req_body = { "tenant": { "id": self.EXTRAS_ID, "name": "tenantX", "description": "I changed you!", "enabled": False, }, } # make tenant object with manager tenant = self.client.tenants.resource_class(self.client.tenants, req_body['tenant']) tenant.remove_user('foo', 'barrr') self.assertIsInstance(tenant, tenants.Tenant) def test_tenant_list_users(self): tenant_id = uuid.uuid4().hex user_id1 = uuid.uuid4().hex user_id2 = uuid.uuid4().hex tenant_resp = { 'tenant': { 'name': uuid.uuid4().hex, 'enabled': True, 'id': tenant_id, 'description': 'test tenant', } } users_resp = { 'users': { 'values': [ { 'email': uuid.uuid4().hex, 'enabled': True, 'id': user_id1, 'name': uuid.uuid4().hex, }, { 'email': uuid.uuid4().hex, 'enabled': True, 'id': user_id2, 'name': uuid.uuid4().hex, }, ] } } self.stub_url('GET', ['tenants', tenant_id], json=tenant_resp) self.stub_url('GET', ['tenants', tenant_id, 'users'], json=users_resp) tenant = self.client.tenants.get(tenant_id) user_objs = tenant.list_users() for u in user_objs: self.assertIsInstance(u, users.User) self.assertEqual(set([user_id1, user_id2]), set([u.id for u in user_objs])) def test_list_tenants_use_admin_url(self): self.stub_url('GET', ['tenants'], json=self.TEST_TENANTS) tenant_list = self.client.tenants.list() self.assertEqual(self.TEST_URL + '/tenants', self.requests_mock.last_request.url) [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] self.assertEqual(len(self.TEST_TENANTS['tenants']['values']), len(tenant_list)) def test_list_tenants_fallback_to_auth_url(self): new_auth_url = 'http://keystone.test:5000/v2.0' token = fixture.V2Token(token_id=self.TEST_TOKEN, user_name=self.TEST_USER, user_id=self.TEST_USER_ID) self.stub_auth(base_url=new_auth_url, json=token) self.stub_url('GET', ['tenants'], base_url=new_auth_url, json=self.TEST_TENANTS) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username=self.TEST_USER, auth_url=new_auth_url, password=uuid.uuid4().hex) self.assertIsNone(c.management_url) tenant_list = c.tenants.list() [self.assertIsInstance(t, tenants.Tenant) for t in tenant_list] self.assertEqual(len(self.TEST_TENANTS['tenants']['values']), len(tenant_list)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_tokens.py0000664000175000017500000002010300000000000027045 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 exceptions from keystoneauth1 import fixture from keystoneclient import access from keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import client from keystoneclient.v2_0 import tokens class TokenTests(utils.ClientTestCase): def test_delete(self): id_ = uuid.uuid4().hex self.stub_url('DELETE', ['tokens', id_], status_code=204) self.client.tokens.delete(id_) def test_user_password(self): token_fixture = fixture.V2Token(user_name=self.TEST_USER) self.stub_auth(json=token_fixture) password = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(username=self.TEST_USER, password=password) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) req_body = { 'auth': { 'passwordCredentials': { 'username': self.TEST_USER, 'password': password, } } } self.assertRequestBodyIs(json=req_body) def test_with_token_id(self): token_fixture = fixture.V2Token() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) req_body = { 'auth': { 'token': { 'id': token_id, } } } self.assertRequestBodyIs(json=req_body) def test_without_auth_params(self): self.assertRaises(ValueError, self.client.tokens.authenticate) self.assertRaises(ValueError, self.client.tokens.authenticate, tenant_id=uuid.uuid4().hex) def test_with_tenant_id(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex tenant_id = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id, tenant_id=tenant_id) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) tenant_data = {'id': token_fixture.tenant_id, 'name': token_fixture.tenant_name} self.assertEqual(tenant_data, token_ref.tenant) req_body = { 'auth': { 'token': { 'id': token_id, }, 'tenantId': tenant_id } } self.assertRequestBodyIs(json=req_body) def test_with_tenant_name(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_id = uuid.uuid4().hex tenant_name = uuid.uuid4().hex token_ref = self.client.tokens.authenticate(token=token_id, tenant_name=tenant_name) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) tenant_data = {'id': token_fixture.tenant_id, 'name': token_fixture.tenant_name} self.assertEqual(tenant_data, token_ref.tenant) req_body = { 'auth': { 'token': { 'id': token_id, }, 'tenantName': tenant_name } } self.assertRequestBodyIs(json=req_body) def test_authenticate_use_admin_url(self): token_fixture = fixture.V2Token() token_fixture.set_scope() self.stub_auth(json=token_fixture) token_ref = self.client.tokens.authenticate(token=uuid.uuid4().hex) self.assertEqual(self.TEST_URL + '/tokens', self.requests_mock.last_request.url) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) def test_authenticate_fallback_to_auth_url(self): new_auth_url = 'http://keystone.test:5000/v2.0' token_fixture = fixture.V2Token() self.stub_auth(base_url=new_auth_url, json=token_fixture) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(username=self.TEST_USER, auth_url=new_auth_url, password=uuid.uuid4().hex) self.assertIsNone(c.management_url) token_ref = c.tokens.authenticate(token=uuid.uuid4().hex) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(token_fixture.token_id, token_ref.id) self.assertEqual(token_fixture.expires_str, token_ref.expires) def test_validate_token(self): id_ = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=id_) self.stub_url('GET', ['tokens', id_], json=token_fixture) token_data = self.client.tokens.get_token_data(id_) self.assertEqual(token_fixture, token_data) token_ref = self.client.tokens.validate(id_) self.assertIsInstance(token_ref, tokens.Token) self.assertEqual(id_, token_ref.id) def test_validate_token_invalid_token(self): # If the token is invalid, typically a NotFound is raised. id_ = uuid.uuid4().hex # The server is expected to return 404 if the token is invalid. self.stub_url('GET', ['tokens', id_], status_code=404) self.assertRaises(exceptions.NotFound, self.client.tokens.get_token_data, id_) self.assertRaises(exceptions.NotFound, self.client.tokens.validate, id_) def test_validate_token_access_info_with_token_id(self): # Can validate a token passing a string token ID. token_id = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=token_id) self.stub_url('GET', ['tokens', token_id], json=token_fixture) access_info = self.client.tokens.validate_access_info(token_id) self.assertIsInstance(access_info, access.AccessInfoV2) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_access_info_with_access_info(self): # Can validate a token passing an access info. token_id = uuid.uuid4().hex token_fixture = fixture.V2Token(token_id=token_id) self.stub_url('GET', ['tokens', token_id], json=token_fixture) token = access.AccessInfo.factory(body=token_fixture) access_info = self.client.tokens.validate_access_info(token) self.assertIsInstance(access_info, access.AccessInfoV2) self.assertEqual(token_id, access_info.auth_token) def test_get_revoked(self): sample_revoked_response = {'signed': '-----BEGIN CMS-----\nMIIB...'} self.stub_url('GET', ['tokens', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked() self.assertEqual(sample_revoked_response, resp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/test_users.py0000664000175000017500000002353000000000000026712 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 keystoneclient.tests.unit.v2_0 import utils from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import users class UserTests(utils.ClientTestCase): def setUp(self): super(UserTests, self).setUp() self.ADMIN_USER_ID = uuid.uuid4().hex self.DEMO_USER_ID = uuid.uuid4().hex self.TEST_USERS = { "users": { "values": [ { "email": "None", "enabled": True, "id": self.ADMIN_USER_ID, "name": "admin", }, { "email": "None", "enabled": True, "id": self.DEMO_USER_ID, "name": "demo", }, ] } } def test_create(self): tenant_id = uuid.uuid4().hex user_id = uuid.uuid4().hex password = uuid.uuid4().hex req_body = { "user": { "name": "gabriel", "password": password, "tenantId": tenant_id, "email": "test@example.com", "enabled": True, } } resp_body = { "user": { "name": "gabriel", "enabled": True, "tenantId": tenant_id, "id": user_id, "password": password, "email": "test@example.com", } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create(req_body['user']['name'], req_body['user']['password'], req_body['user']['email'], tenant_id=req_body['user']['tenantId'], enabled=req_body['user']['enabled']) self.assertIsInstance(user, users.User) self.assertEqual(user.id, user_id) self.assertEqual(user.name, "gabriel") self.assertEqual(user.email, "test@example.com") self.assertRequestBodyIs(json=req_body) self.assertNotIn(password, self.logger.output) def test_create_user_without_email(self): tenant_id = uuid.uuid4().hex req_body = { "user": { "name": "gabriel", "password": "test", "tenantId": tenant_id, "enabled": True, "email": None, } } user_id = uuid.uuid4().hex resp_body = { "user": { "name": "gabriel", "enabled": True, "tenantId": tenant_id, "id": user_id, "password": "test", } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create( req_body['user']['name'], req_body['user']['password'], tenant_id=req_body['user']['tenantId'], enabled=req_body['user']['enabled']) self.assertIsInstance(user, users.User) self.assertEqual(user.id, user_id) self.assertEqual(user.name, "gabriel") self.assertRequestBodyIs(json=req_body) def test_create_user_without_password(self): user_name = 'test' user_id = uuid.uuid4().hex tenant_id = uuid.uuid4().hex user_enabled = True req_body = { 'user': { 'name': user_name, 'password': None, 'tenantId': tenant_id, 'enabled': user_enabled, 'email': None, } } resp_body = { 'user': { 'name': user_name, 'enabled': user_enabled, 'tenantId': tenant_id, 'id': user_id, } } self.stub_url('POST', ['users'], json=resp_body) user = self.client.users.create(user_name, tenant_id=tenant_id, enabled=user_enabled) self.assertIsInstance(user, users.User) self.assertEqual(user_id, user.id) self.assertEqual(user_name, user.name) self.assertRequestBodyIs(json=req_body) def test_delete(self): self.stub_url('DELETE', ['users', self.ADMIN_USER_ID], status_code=204) self.client.users.delete(self.ADMIN_USER_ID) def test_get(self): self.stub_url('GET', ['users', self.ADMIN_USER_ID], json={'user': self.TEST_USERS['users']['values'][0]}) u = self.client.users.get(self.ADMIN_USER_ID) self.assertIsInstance(u, users.User) self.assertEqual(u.id, self.ADMIN_USER_ID) self.assertEqual(u.name, 'admin') def test_list(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list() [self.assertIsInstance(u, users.User) for u in user_list] def test_list_limit(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(limit=1) self.assertQueryStringIs('limit=1') [self.assertIsInstance(u, users.User) for u in user_list] def test_list_marker(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(marker='foo') self.assertQueryStringIs('marker=foo') [self.assertIsInstance(u, users.User) for u in user_list] def test_list_limit_marker(self): self.stub_url('GET', ['users'], json=self.TEST_USERS) user_list = self.client.users.list(limit=1, marker='foo') self.assertQueryStringIs('marker=foo&limit=1') [self.assertIsInstance(u, users.User) for u in user_list] def test_update(self): req_1 = { "user": { "email": "gabriel@example.com", "name": "gabriel", } } password = uuid.uuid4().hex req_2 = { "user": { "password": password, } } tenant_id = uuid.uuid4().hex req_3 = { "user": { "tenantId": tenant_id, } } req_4 = { "user": { "enabled": False, } } self.stub_url('PUT', ['users', self.DEMO_USER_ID], json=req_1) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'password'], json=req_2) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'tenant'], json=req_3) self.stub_url('PUT', ['users', self.DEMO_USER_ID, 'OS-KSADM', 'enabled'], json=req_4) self.client.users.update(self.DEMO_USER_ID, name='gabriel', email='gabriel@example.com') self.assertRequestBodyIs(json=req_1) self.client.users.update_password(self.DEMO_USER_ID, password) self.assertRequestBodyIs(json=req_2) self.client.users.update_tenant(self.DEMO_USER_ID, tenant_id) self.assertRequestBodyIs(json=req_3) self.client.users.update_enabled(self.DEMO_USER_ID, False) self.assertRequestBodyIs(json=req_4) self.assertNotIn(password, self.logger.output) def test_update_own_password(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex req_body = { 'user': { 'password': new_password, 'original_password': old_password } } resp_body = { 'access': {} } self.stub_url('PATCH', ['OS-KSCRUD', 'users', self.TEST_USER_ID], json=resp_body) self.client.users.update_own_password(old_password, new_password) self.assertRequestBodyIs(json=req_body) self.assertNotIn(old_password, self.logger.output) self.assertNotIn(new_password, self.logger.output) def test_user_role_listing(self): user_id = uuid.uuid4().hex role_id1 = uuid.uuid4().hex role_id2 = uuid.uuid4().hex tenant_id = uuid.uuid4().hex user_resp = { 'user': { 'id': user_id, 'email': uuid.uuid4().hex, 'name': uuid.uuid4().hex, } } roles_resp = { 'roles': { 'values': [ { 'name': uuid.uuid4().hex, 'id': role_id1, }, { 'name': uuid.uuid4().hex, 'id': role_id2, } ] } } self.stub_url('GET', ['users', user_id], json=user_resp) self.stub_url('GET', ['tenants', tenant_id, 'users', user_id, 'roles'], json=roles_resp) user = self.client.users.get(user_id) role_objs = user.list_roles(tenant_id) for r in role_objs: self.assertIsInstance(r, roles.Role) self.assertEqual(set([role_id1, role_id2]), set([r.id for r in role_objs])) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v2_0/utils.py0000664000175000017500000000577600000000000025666 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 keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils class UnauthenticatedTestCase(utils.TestCase): """Class used as base for unauthenticated calls.""" TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v2.0') TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v2.0') class TestCase(UnauthenticatedTestCase): TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v2.0" TEST_SERVICE_CATALOG = [{ "endpoints": [{ "adminURL": "http://cdn.admin-nets.local:8774/v1.0", "region": "RegionOne", "internalURL": "http://127.0.0.1:8774/v1.0", "publicURL": "http://cdn.admin-nets.local:8774/v1.0/" }], "type": "nova_compat", "name": "nova_compat" }, { "endpoints": [{ "adminURL": "http://nova/novapi/admin", "region": "RegionOne", "internalURL": "http://nova/novapi/internal", "publicURL": "http://nova/novapi/public" }], "type": "compute", "name": "nova" }, { "endpoints": [{ "adminURL": "http://glance/glanceapi/admin", "region": "RegionOne", "internalURL": "http://glance/glanceapi/internal", "publicURL": "http://glance/glanceapi/public" }], "type": "image", "name": "glance" }, { "endpoints": [{ "adminURL": TEST_ADMIN_IDENTITY_ENDPOINT, "region": "RegionOne", "internalURL": "http://127.0.0.1:5000/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" }], "type": "identity", "name": "keystone" }, { "endpoints": [{ "adminURL": "http://swift/swiftapi/admin", "region": "RegionOne", "internalURL": "http://swift/swiftapi/internal", "publicURL": "http://swift/swiftapi/public" }], "type": "object-store", "name": "swift" }] def stub_auth(self, **kwargs): self.stub_url('POST', ['tokens'], **kwargs) class ClientTestCase(utils.ClientTestCaseMixin, TestCase): scenarios = [ ('original', {'client_fixture_class': client_fixtures.OriginalV2}), ('ksc-session', {'client_fixture_class': client_fixtures.KscSessionV2}), ('ksa-session', {'client_fixture_class': client_fixtures.KsaSessionV2}), ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2430036 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/0000775000175000017500000000000000000000000023717 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/__init__.py0000664000175000017500000000000000000000000026016 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/client_fixtures.py0000664000175000017500000001301600000000000027501 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 def unscoped_token(**kwargs): return fixture.V3Token(**kwargs) def domain_scoped_token(**kwargs): kwargs.setdefault('audit_chain_id', uuid.uuid4().hex) f = fixture.V3Token(**kwargs) if not f.domain_id: f.set_domain_scope() f.add_role(name='admin') f.add_role(name='member') region = 'RegionOne' s = f.add_service('volume') s.add_standard_endpoints(public='http://public.com:8776/v1/None', internal='http://internal.com:8776/v1/None', admin='http://admin.com:8776/v1/None', region=region) s = f.add_service('image') s.add_standard_endpoints(public='http://public.com:9292/v1', internal='http://internal:9292/v1', admin='http://admin:9292/v1', region=region) s = f.add_service('compute') s.add_standard_endpoints(public='http://public.com:8774/v1.1/None', internal='http://internal:8774/v1.1/None', admin='http://admin:8774/v1.1/None', region=region) s = f.add_service('ec2') s.add_standard_endpoints(public='http://public.com:8773/services/Cloud', internal='http://internal:8773/services/Cloud', admin='http://admin:8773/services/Admin', region=region) s = f.add_service('identity') s.add_standard_endpoints(public='http://public.com:5000/v3', internal='http://internal:5000/v3', admin='http://admin:35357/v3', region=region) return f def project_scoped_token(**kwargs): kwargs.setdefault('audit_chain_id', uuid.uuid4().hex) f = fixture.V3Token(**kwargs) if not f.project_id: f.set_project_scope() f.add_role(name='admin') f.add_role(name='member') region = 'RegionOne' tenant = '225da22d3ce34b15877ea70b2a575f58' s = f.add_service('volume') s.add_standard_endpoints(public='http://public.com:8776/v1/%s' % tenant, internal='http://internal:8776/v1/%s' % tenant, admin='http://admin:8776/v1/%s' % tenant, region=region) s = f.add_service('image') s.add_standard_endpoints(public='http://public.com:9292/v1', internal='http://internal:9292/v1', admin='http://admin:9292/v1', region=region) s = f.add_service('compute') s.add_standard_endpoints(public='http://public.com:8774/v2/%s' % tenant, internal='http://internal:8774/v2/%s' % tenant, admin='http://admin:8774/v2/%s' % tenant, region=region) s = f.add_service('ec2') s.add_standard_endpoints(public='http://public.com:8773/services/Cloud', internal='http://internal:8773/services/Cloud', admin='http://admin:8773/services/Admin', region=region) s = f.add_service('identity') s.add_standard_endpoints(public='http://public.com:5000/v3', internal='http://internal:5000/v3', admin='http://admin:35357/v3', region=region) return f AUTH_SUBJECT_TOKEN = uuid.uuid4().hex AUTH_RESPONSE_HEADERS = { 'X-Subject-Token': AUTH_SUBJECT_TOKEN, } def auth_response_body(): f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) f.set_project_scope() f.add_role(name='admin') f.add_role(name='member') s = f.add_service('compute', name='nova') s.add_standard_endpoints( public='https://compute.north.host/novapi/public', internal='https://compute.north.host/novapi/internal', admin='https://compute.north.host/novapi/admin', region='North') s = f.add_service('object-store', name='swift') s.add_standard_endpoints( public='http://swift.north.host/swiftapi/public', internal='http://swift.north.host/swiftapi/internal', admin='http://swift.north.host/swiftapi/admin', region='South') s = f.add_service('image', name='glance') s.add_standard_endpoints( public='http://glance.north.host/glanceapi/public', internal='http://glance.north.host/glanceapi/internal', admin='http://glance.north.host/glanceapi/admin', region='North') s.add_standard_endpoints( public='http://glance.south.host/glanceapi/public', internal='http://glance.south.host/glanceapi/internal', admin='http://glance.south.host/glanceapi/admin', region='South') return f def trust_token(): f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) f.set_trust_scope() return f ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1870036 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/examples/0000775000175000017500000000000000000000000025535 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2430036 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/examples/xml/0000775000175000017500000000000000000000000026335 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000021300000000000011451 xustar0000000000000000117 path=python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenResponse.xml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenRespo0000664000175000017500000003347200000000000034260 0ustar00zuulzuul00000000000000 http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal urn:uuid:487c064b-b7c6-4654-b4d4-715f9961170e 2014-08-05T18:36:14.235Z 2014-08-05T18:41:14.235Z 2014-08-05T18:36:14.063Z 2014-08-05T19:36:14.063Z https://ltartari2.cern.ch:5000/Shibboleth.sso/ADFS https://ltartari2.cern.ch:5000/Shibboleth.sso/ADFS marek.denis@cern.ch urn:oasis:names:tc:SAML:1.0:cm:bearer marek.denis@cern.ch marek.denis@cern.ch madenis CERN Users Domain Users occupants-bldg-31 CERN-Direct-Employees ca-dev-allowed cernts-cerntstest-users staf-fell-pjas-at-cern ELG-CERN student-club-new-members pawel-dynamic-test-82 Marek Kamil Denis +5555555 31S-013 Marek Kamil Denis CERN Registered CERN Normal marek.denis@cern.ch urn:oasis:names:tc:SAML:1.0:cm:bearer EaZ/2d0KAY5un9akV3++Npyk6hBc8JuTYs2S3lSxUeQ= CxYiYvNsbedhHdmDbb9YQCBy6Ppus3bNJdw2g2HLq0VU2yRhv23mUW05I89Hs4yG4OcCo0uOZ3zaeNFbSNXMW+Mr996tAXtujKjgyrCXNJAToE+gwltvGxwY1EluSbe3IzoSM3Ao87mKhxGOSzlDhuN7dQ9Rv6l/J4gUjbOO5SIX4pdZ6mVF7cHEfe9x+H8Lg15YjnElQUEaPi+NSW5jYTdtIpsB4ORxJvALuSt6+4doDYc9wuwBiWkEdnBHAQBINoKpAV2oy0/C85SBX3IdRhxUznmL5yEUmf8JvPccXecMPqJow0L43mnCdu74xPwU0as3MNfYQ10kLvHXHfIExg== MIIIEjCCBfqgAwIBAgIKLYgjvQAAAAAAMDANBgkqhkiG9w0BAQsFADBRMRIwEAYKCZImiZPyLGQBGRYCY2gxFDASBgoJkiaJk/IsZAEZFgRjZXJuMSUwIwYDVQQDExxDRVJOIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMTEwODA4Mzg1NVoXDTIzMDcyOTA5MTkzOFowVjESMBAGCgmSJomT8ixkARkWAmNoMRQwEgYKCZImiZPyLGQBGRYEY2VybjESMBAGA1UECxMJY29tcHV0ZXJzMRYwFAYDVQQDEw1sb2dpbi5jZXJuLmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6t1C0SGlLddL2M+ltffGioTnDT3eztOxlA9bAGuvB8/Rjym8en6+ET9boM02CyoR5Vpn8iElXVWccAExPIQEq70D6LPe86vb+tYhuKPeLfuICN9Z0SMQ4f+57vk61Co1/uw/8kPvXlyd+Ai8Dsn/G0hpH67bBI9VOQKfpJqclcSJuSlUB5PJffvMUpr29B0eRx8LKFnIHbDILSu6nVbFLcadtWIjbYvoKorXg3J6urtkz+zEDeYMTvA6ZGOFf/Xy5eGtroSq9csSC976tx+umKEPhXBA9AcpiCV9Cj5axN03Aaa+iTE36jpnjcd9d02dy5Q9jE2nUN6KXnB6qF6eQIDAQABo4ID5TCCA+EwPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIg73QCYLtjQ2G7Ysrgd71N4WA0GIehd2yb4Wu9TkCAWQCARkwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIFoDBoBgNVHSAEYTBfMF0GCisGAQQBYAoEAQEwTzBNBggrBgEFBQcCARZBaHR0cDovL2NhLWRvY3MuY2Vybi5jaC9jYS1kb2NzL2NwLWNwcy9jZXJuLXRydXN0ZWQtY2EyLWNwLWNwcy5wZGYwJwYJKwYBBAGCNxUKBBowGDAKBggrBgEFBQcDAjAKBggrBgEFBQcDATAdBgNVHQ4EFgQUqtJcwUXasyM6sRaO5nCMFoFDenMwGAYDVR0RBBEwD4INbG9naW4uY2Vybi5jaDAfBgNVHSMEGDAWgBQdkBnqyM7MPI0UsUzZ7BTiYUADYTCCASoGA1UdHwSCASEwggEdMIIBGaCCARWgggERhkdodHRwOi8vY2FmaWxlcy5jZXJuLmNoL2NhZmlsZXMvY3JsL0NFUk4lMjBDZXJ0aWZpY2F0aW9uJTIwQXV0aG9yaXR5LmNybIaBxWxkYXA6Ly8vQ049Q0VSTiUyMENlcnRpZmljYXRpb24lMjBBdXRob3JpdHksQ049Q0VSTlBLSTA3LENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWNlcm4sREM9Y2g/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIIBVAYIKwYBBQUHAQEEggFGMIIBQjBcBggrBgEFBQcwAoZQaHR0cDovL2NhZmlsZXMuY2Vybi5jaC9jYWZpbGVzL2NlcnRpZmljYXRlcy9DRVJOJTIwQ2VydGlmaWNhdGlvbiUyMEF1dGhvcml0eS5jcnQwgbsGCCsGAQUFBzAChoGubGRhcDovLy9DTj1DRVJOJTIwQ2VydGlmaWNhdGlvbiUyMEF1dGhvcml0eSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1jZXJuLERDPWNoP2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0aG9yaXR5MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jZXJuLmNoL29jc3AwDQYJKoZIhvcNAQELBQADggIBAGKZ3bknTCfNuh4TMaL3PuvBFjU8LQ5NKY9GLZvY2ibYMRk5Is6eWRgyUsy1UJRQdaQQPnnysqrGq8VRw/NIFotBBsA978/+jj7v4e5Kr4o8HvwAQNLBxNmF6XkDytpLL701FcNEGRqIsoIhNzihi2VBADLC9HxljEyPT52IR767TMk/+xTOqClceq3sq6WRD4m+xaWRUJyOhn+Pqr+wbhXIw4wzHC6X0hcLj8P9Povtm6VmKkN9JPuymMo/0+zSrUt2+TYfmbbEKYJSP0+sceQ76IKxxmSdKAr1qDNE8v+c3DvPM2PKmfivwaV2l44FdP8ulzqTgphkYcN1daa9Oc+qJeyu/eL7xWzk6Zq5R+jVrMlM0p1y2XczI7Hoc96TMOcbVnwgMcVqRM9p57VItn6XubYPR0C33i1yUZjkWbIfqEjq6Vev6lVgngOyzu+hqC/8SDyORA3dlF9aZOD13kPZdF/JRphHREQtaRydAiYRlE/WHTvOcY52jujDftUR6oY0eWaWkwSHbX+kDFx8IlR8UtQCUgkGHBGwnOYLIGu7SRDGSfOBOiVhxKoHWVk/pL6eKY2SkmyOmmgO4JnQGg95qeAOMG/EQZt/2x8GAavUqGvYy9dPFwFf08678hQqkjNSuex7UD0ku8OP1QKvpP44l6vZhFc6A5XqjdU9lus1 _c9e77bc4-a81b-4da7-88c2-72a6ba376d3f _c9e77bc4-a81b-4da7-88c2-72a6ba376d3f urn:oasis:names:tc:SAML:1.0:assertion http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/examples/xml/ADFS_fault.xml0000664000175000017500000000153300000000000030771 0ustar00zuulzuul00000000000000 http://www.w3.org/2005/08/addressing/soap/fault urn:uuid:89c47849-2622-4cdc-bb06-1d46c89ed12d s:Sender a:FailedAuthentication At least one security token in the message could not be validated. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/saml2_fixtures.py0000664000175000017500000002465000000000000027247 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. SP_SOAP_RESPONSE = b""" https://openstack4.local/shibboleth ss:mem:6f1f20fafbb38433467e9d477df67615 https://openstack4.local/shibboleth """ SAML2_ASSERTION = b""" x= https://idp.testshib.org/idp/shibboleth VALUE== VALUE= """ UNSCOPED_TOKEN_HEADER = 'UNSCOPED_TOKEN' UNSCOPED_TOKEN = { "token": { "issued_at": "2014-06-09T09:48:59.643406Z", "extras": {}, "methods": ["saml2"], "expires_at": "2014-06-09T10:48:59.643375Z", "user": { "OS-FEDERATION": { "identity_provider": { "id": "testshib" }, "protocol": { "id": "saml2" }, "groups": [ {"id": "1764fa5cf69a49a4918131de5ce4af9a"} ] }, "id": "testhib%20user", "name": "testhib user" } } } PROJECTS = { "projects": [ { "domain_id": "37ef61", "enabled": 'true', "id": "12d706", "links": { "self": "http://identity:35357/v3/projects/12d706" }, "name": "a project name" }, { "domain_id": "37ef61", "enabled": 'true', "id": "9ca0eb", "links": { "self": "http://identity:35357/v3/projects/9ca0eb" }, "name": "another project" } ], "links": { "self": "http://identity:35357/v3/auth/projects", "previous": 'null', "next": 'null' } } DOMAINS = { "domains": [ { "description": "desc of domain", "enabled": 'true', "id": "37ef61", "links": { "self": "http://identity:35357/v3/domains/37ef61" }, "name": "my domain" } ], "links": { "self": "http://identity:35357/v3/auth/domains", "previous": 'null', "next": 'null' } } SAML_ENCODING = "" TOKEN_SAML_RESPONSE = """ http://keystone.idp/v3/OS-FEDERATION/saml2/idp http://keystone.idp/v3/OS-FEDERATION/saml2/idp 0KH2CxdkfzU+6eiRhTC+mbObUKI= m2jh5gDvX/1k+4uKtbb08CHp2b9UWsLw ... admin urn:oasis:names:tc:SAML:2.0:ac:classes:Password http://keystone.idp/v3/OS-FEDERATION/saml2/idp admin admin admin """ TOKEN_BASED_SAML = ''.join([SAML_ENCODING, TOKEN_SAML_RESPONSE]) ECP_ENVELOPE = """ ss:mem:1ddfe8b0f58341a5a840d2e8717b0737 {0} """.format(TOKEN_SAML_RESPONSE) TOKEN_BASED_ECP = ''.join([SAML_ENCODING, ECP_ENVELOPE]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_access.py0000664000175000017500000002130300000000000026570 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 keystoneauth1 import fixture from oslo_utils import timeutils from keystoneclient import access from keystoneclient.tests.unit import utils as test_utils from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils TOKEN_RESPONSE = test_utils.test_response( headers=client_fixtures.AUTH_RESPONSE_HEADERS ) UNSCOPED_TOKEN = client_fixtures.unscoped_token() DOMAIN_SCOPED_TOKEN = client_fixtures.domain_scoped_token() PROJECT_SCOPED_TOKEN = client_fixtures.project_scoped_token() class AccessInfoTest(utils.TestCase): def test_building_unscoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=UNSCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertNotIn('catalog', auth_ref) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(UNSCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(UNSCOPED_TOKEN.user_id, auth_ref.user_id) self.assertEqual(auth_ref.role_ids, []) self.assertEqual(auth_ref.role_names, []) self.assertIsNone(auth_ref.project_name) self.assertIsNone(auth_ref.project_id) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.auth_url) with self.deprecations.expect_deprecations_here(): self.assertIsNone(auth_ref.management_url) self.assertFalse(auth_ref.domain_scoped) self.assertFalse(auth_ref.project_scoped) self.assertEqual(UNSCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(UNSCOPED_TOKEN.user_domain_name, auth_ref.user_domain_name) self.assertIsNone(auth_ref.project_domain_id) self.assertIsNone(auth_ref.project_domain_name) self.assertEqual(auth_ref.expires, timeutils.parse_isotime( UNSCOPED_TOKEN['token']['expires_at'])) self.assertEqual(auth_ref.issued, timeutils.parse_isotime( UNSCOPED_TOKEN['token']['issued_at'])) self.assertEqual(auth_ref.expires, UNSCOPED_TOKEN.expires) self.assertEqual(auth_ref.issued, UNSCOPED_TOKEN.issued) self.assertEqual(auth_ref.audit_id, UNSCOPED_TOKEN.audit_id) self.assertIsNone(auth_ref.audit_chain_id) self.assertIsNone(UNSCOPED_TOKEN.audit_chain_id) def test_will_expire_soon(self): expires = timeutils.utcnow() + datetime.timedelta(minutes=5) UNSCOPED_TOKEN['token']['expires_at'] = expires.isoformat() auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=UNSCOPED_TOKEN) self.assertFalse(auth_ref.will_expire_soon(stale_duration=120)) self.assertTrue(auth_ref.will_expire_soon(stale_duration=301)) self.assertFalse(auth_ref.will_expire_soon()) def test_building_domain_scoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=DOMAIN_SCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertIn('catalog', auth_ref) self.assertTrue(auth_ref['catalog']) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_id, auth_ref.user_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.role_ids, auth_ref.role_ids) self.assertEqual(DOMAIN_SCOPED_TOKEN.role_names, auth_ref.role_names) self.assertEqual(DOMAIN_SCOPED_TOKEN.domain_name, auth_ref.domain_name) self.assertEqual(DOMAIN_SCOPED_TOKEN.domain_id, auth_ref.domain_id) self.assertIsNone(auth_ref.project_name) self.assertIsNone(auth_ref.project_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.user_domain_name, auth_ref.user_domain_name) self.assertIsNone(auth_ref.project_domain_id) self.assertIsNone(auth_ref.project_domain_name) self.assertTrue(auth_ref.domain_scoped) self.assertFalse(auth_ref.project_scoped) self.assertEqual(DOMAIN_SCOPED_TOKEN.audit_id, auth_ref.audit_id) self.assertEqual(DOMAIN_SCOPED_TOKEN.audit_chain_id, auth_ref.audit_chain_id) def test_building_project_scoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=PROJECT_SCOPED_TOKEN) self.assertTrue(auth_ref) self.assertIn('methods', auth_ref) self.assertIn('catalog', auth_ref) self.assertTrue(auth_ref['catalog']) self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, auth_ref.auth_token) self.assertEqual(PROJECT_SCOPED_TOKEN.user_name, auth_ref.username) self.assertEqual(PROJECT_SCOPED_TOKEN.user_id, auth_ref.user_id) self.assertEqual(PROJECT_SCOPED_TOKEN.role_ids, auth_ref.role_ids) self.assertEqual(PROJECT_SCOPED_TOKEN.role_names, auth_ref.role_names) self.assertIsNone(auth_ref.domain_name) self.assertIsNone(auth_ref.domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.project_name, auth_ref.project_name) self.assertEqual(PROJECT_SCOPED_TOKEN.project_id, auth_ref.project_id) self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.auth_url, ('http://public.com:5000/v3',)) with self.deprecations.expect_deprecations_here(): self.assertEqual(auth_ref.management_url, ('http://admin:35357/v3',)) self.assertEqual(PROJECT_SCOPED_TOKEN.project_domain_id, auth_ref.project_domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.project_domain_name, auth_ref.project_domain_name) self.assertEqual(PROJECT_SCOPED_TOKEN.user_domain_id, auth_ref.user_domain_id) self.assertEqual(PROJECT_SCOPED_TOKEN.user_domain_name, auth_ref.user_domain_name) self.assertFalse(auth_ref.domain_scoped) self.assertTrue(auth_ref.project_scoped) self.assertEqual(PROJECT_SCOPED_TOKEN.audit_id, auth_ref.audit_id) self.assertEqual(PROJECT_SCOPED_TOKEN.audit_chain_id, auth_ref.audit_chain_id) def test_oauth_access(self): consumer_id = uuid.uuid4().hex access_token_id = uuid.uuid4().hex token = fixture.V3Token() token.set_project_scope() token.set_oauth(access_token_id=access_token_id, consumer_id=consumer_id) auth_ref = access.AccessInfo.factory(body=token) self.assertEqual(consumer_id, auth_ref.oauth_consumer_id) self.assertEqual(access_token_id, auth_ref.oauth_access_token_id) self.assertEqual(consumer_id, auth_ref['OS-OAUTH1']['consumer_id']) self.assertEqual(access_token_id, auth_ref['OS-OAUTH1']['access_token_id']) def test_override_auth_token(self): token = fixture.V3Token() token.set_project_scope() new_auth_token = uuid.uuid4().hex auth_ref = access.AccessInfo.factory(body=token, auth_token=new_auth_token) self.assertEqual(new_auth_token, auth_ref.auth_token) def test_federated_property_standard_token(self): """Check if is_federated property returns expected value.""" token = fixture.V3Token() token.set_project_scope() auth_ref = access.AccessInfo.factory(body=token) self.assertFalse(auth_ref.is_federated) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_access_rules.py0000664000175000017500000000300700000000000030003 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import access_rules class AccessRuleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(AccessRuleTests, self).setUp() self.key = 'access_rule' self.collection_key = 'access_rules' self.model = access_rules.AccessRule self.manager = self.client.access_rules self.path_prefix = 'users/%s' % self.TEST_USER_ID def new_ref(self, **kwargs): kwargs = super(AccessRuleTests, self).new_ref(**kwargs) kwargs.setdefault('path', uuid.uuid4().hex) kwargs.setdefault('method', uuid.uuid4().hex) kwargs.setdefault('service', uuid.uuid4().hex) return kwargs def test_update(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_create(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_application_credentials.py0000664000175000017500000001206500000000000032214 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 oslo_utils import timeutils from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import application_credentials class ApplicationCredentialTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ApplicationCredentialTests, self).setUp() self.key = 'application_credential' self.collection_key = 'application_credentials' self.model = application_credentials.ApplicationCredential self.manager = self.client.application_credentials self.path_prefix = 'users/%s' % self.TEST_USER_ID def new_ref(self, **kwargs): kwargs = super(ApplicationCredentialTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('unrestricted', False) return kwargs def test_create_with_roles(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['roles'] = [{'name': 'atestrole'}] req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_with_role_id_and_names(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, uuid.uuid4().hex] req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') req_ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, {'id': ref['roles'][1]}] self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_expires(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['expires_at'] = timeutils.parse_isotime( '2013-03-04T12:00:01.000000Z') req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z' self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_unrestricted(self): ref = self.new_ref(user=uuid.uuid4().hex) ref['unrestricted'] = True req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_with_access_rules(self): ref = self.new_ref(user=uuid.uuid4().hex) access_rules = [ { 'method': 'GET', 'path': '/v3/projects', 'service': 'identity' } ] ref['access_rules'] = access_rules req_ref = ref.copy() req_ref.pop('id') user = req_ref.pop('user') self.stub_entity('POST', ['users', user, self.collection_key], status_code=201, entity=req_ref) super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) def test_get(self): ref = self.new_ref(user=uuid.uuid4().hex) self.stub_entity( 'GET', ['users', ref['user'], self.collection_key, ref['id']], entity=ref) returned = self.manager.get(ref['id'], ref['user']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def test_update(self): self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_auth.py0000664000175000017500000003675400000000000026310 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_serialization import jsonutils from testtools import testcase from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): def setUp(self): super(AuthenticateAgainstKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "token": { "methods": [ "token", "password" ], "expires_at": "2999-01-01T00:00:10.000123Z", "project": { "domain": { "id": self.TEST_DOMAIN_ID, "name": self.TEST_DOMAIN_NAME }, "id": self.TEST_TENANT_ID, "name": self.TEST_TENANT_NAME }, "user": { "domain": { "id": self.TEST_DOMAIN_ID, "name": self.TEST_DOMAIN_NAME }, "id": self.TEST_USER, "name": self.TEST_USER }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": self.TEST_SERVICE_CATALOG }, } self.TEST_REQUEST_BODY = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "domain": { "name": self.TEST_DOMAIN_NAME }, "name": self.TEST_USER, "password": self.TEST_TOKEN } } }, "scope": { "project": { "id": self.TEST_TENANT_ID }, } } } self.TEST_REQUEST_HEADERS = { 'Content-Type': 'application/json', 'User-Agent': 'python-keystoneclient' } self.TEST_RESPONSE_HEADERS = { 'X-Subject-Token': self.TEST_TOKEN } def test_authenticate_success(self): TEST_TOKEN = "abcdef" ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER self.stub_auth(json=self.TEST_RESPONSE_DICT, subject_token=TEST_TOKEN) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, TEST_TOKEN) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_failure(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] ident['password']['user']['password'] = 'bad_key' error = {"unauthorized": {"message": "Unauthorized", "code": "401"}} self.stub_auth(status_code=401, json=error) with testcase.ExpectedException(exceptions.Unauthorized): with self.deprecations.expect_deprecations_here(): client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password="bad_key", project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_redirect(self): headers = {'Location': self.TEST_ADMIN_URL + '/auth/tokens'} self.stub_auth(status_code=305, text='Use proxy', headers=headers) self.stub_auth(json=self.TEST_RESPONSE_DICT, base_url=self.TEST_ADMIN_URL) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) def test_authenticate_success_domain_username_password_scoped(self): self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) def test_authenticate_success_userid_password_domain_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER scope = self.TEST_REQUEST_BODY['auth']['scope'] del scope['project'] scope['domain'] = {} scope['domain']['id'] = self.TEST_DOMAIN_ID token = self.TEST_RESPONSE_DICT['token'] del token['project'] token['domain'] = {} token['domain']['id'] = self.TEST_DOMAIN_ID token['domain']['name'] = self.TEST_DOMAIN_NAME self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, domain_id=self.TEST_DOMAIN_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_domain_id, self.TEST_DOMAIN_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_userid_password_project_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password']['user']['domain'] del ident['password']['user']['name'] ident['password']['user']['id'] = self.TEST_USER self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_id=self.TEST_USER, password=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_tenant_id, self.TEST_TENANT_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_password_unscoped(self): del self.TEST_RESPONSE_DICT['token']['catalog'] del self.TEST_REQUEST_BODY['auth']['scope'] self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(user_domain_name=self.TEST_DOMAIN_NAME, username=self.TEST_USER, password=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_auth_url_token_authentication(self): fake_token = 'fake_token' fake_url = '/fake-url' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_PUBLIC_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(auth_url=self.TEST_URL, token=fake_token) body = jsonutils.loads(self.requests_mock.last_request.body) self.assertEqual(body['auth']['identity']['token']['id'], fake_token) resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) def test_authenticate_success_token_domain_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN scope = self.TEST_REQUEST_BODY['auth']['scope'] del scope['project'] scope['domain'] = {} scope['domain']['id'] = self.TEST_DOMAIN_ID token = self.TEST_RESPONSE_DICT['token'] del token['project'] token['domain'] = {} token['domain']['id'] = self.TEST_DOMAIN_ID token['domain']['name'] = self.TEST_DOMAIN_NAME self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, domain_id=self.TEST_DOMAIN_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_domain_id, self.TEST_DOMAIN_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_project_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, project_id=self.TEST_TENANT_ID, auth_url=self.TEST_URL) self.assertEqual(cs.auth_tenant_id, self.TEST_TENANT_ID) self.assertEqual(cs.management_url, self.TEST_RESPONSE_DICT["token"]["catalog"][3] ['endpoints'][2]["url"]) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_authenticate_success_token_unscoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] del ident['password'] ident['methods'] = ['token'] ident['token'] = {} ident['token']['id'] = self.TEST_TOKEN del self.TEST_REQUEST_BODY['auth']['scope'] del self.TEST_RESPONSE_DICT['token']['catalog'] self.TEST_REQUEST_HEADERS['X-Auth-Token'] = self.TEST_TOKEN self.stub_auth(json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client(token=self.TEST_TOKEN, auth_url=self.TEST_URL) self.assertEqual(cs.auth_token, self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertNotIn('catalog', cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) def test_allow_override_of_auth_token(self): fake_url = '/fake-url' fake_token = 'fake_token' fake_resp = {'result': True} self.stub_auth(json=self.TEST_RESPONSE_DICT) self.stub_url('GET', [fake_url], json=fake_resp, base_url=self.TEST_PUBLIC_IDENTITY_ENDPOINT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL) self.assertEqual(cl.auth_token, self.TEST_TOKEN) # the token returned from the authentication will be used resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) # then override that token and the new token shall be used cl.auth_token = fake_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(fake_token, token) # if we clear that overridden token then we fall back to the original del cl.auth_token resp, body = cl._adapter.get(fake_url) self.assertEqual(fake_resp, body) token = self.requests_mock.last_request.headers.get('X-Auth-Token') self.assertEqual(self.TEST_TOKEN, token) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_auth_manager.py0000664000175000017500000000453600000000000027773 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import auth class AuthProjectsTest(utils.ClientTestCase): def setUp(self): super(AuthProjectsTest, self).setUp() self.v3token = fixture.V3Token() self.stub_auth(json=self.v3token) self.stub_url('GET', [], json={'version': fixture.V3Discovery(self.TEST_URL)}) def create_resource(self, id=None, name=None, **kwargs): kwargs['id'] = id or uuid.uuid4().hex kwargs['name'] = name or uuid.uuid4().hex return kwargs def test_get_projects(self): body = {'projects': [self.create_resource(), self.create_resource(), self.create_resource()]} self.stub_url('GET', ['auth', 'projects'], json=body) projects = self.client.auth.projects() self.assertEqual(3, len(projects)) for p in projects: self.assertIsInstance(p, auth.Project) def test_get_domains(self): body = {'domains': [self.create_resource(), self.create_resource(), self.create_resource()]} self.stub_url('GET', ['auth', 'domains'], json=body) domains = self.client.auth.domains() self.assertEqual(3, len(domains)) for d in domains: self.assertIsInstance(d, auth.Domain) def test_get_systems(self): body = {'system': [{ 'all': True, }]} self.stub_url('GET', ['auth', 'system'], json=body) systems = self.client.auth.systems() system = systems[0] self.assertEqual(1, len(systems)) self.assertIsInstance(system, auth.System) self.assertTrue(system.all) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_auth_oidc.py0000664000175000017500000001664300000000000027301 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 import uuid from oslo_config import fixture as config import testtools from keystoneclient.auth import conf from keystoneclient.contrib.auth.v3 import oidc from keystoneclient import session from keystoneclient.tests.unit.v3 import utils ACCESS_TOKEN_ENDPOINT_RESP = {"access_token": "z5H1ITZLlJVDHQXqJun", "token_type": "bearer", "expires_in": 3599, "scope": "profile", "refresh_token": "DCERsh83IAhu9bhavrp"} KEYSTONE_TOKEN_VALUE = uuid.uuid4().hex UNSCOPED_TOKEN = { "token": { "issued_at": "2014-06-09T09:48:59.643406Z", "extras": {}, "methods": ["oidc"], "expires_at": "2014-06-09T10:48:59.643375Z", "user": { "OS-FEDERATION": { "identity_provider": { "id": "bluepages" }, "protocol": { "id": "oidc" }, "groups": [ {"id": "1764fa5cf69a49a4918131de5ce4af9a"} ] }, "id": "oidc_user%40example.com", "name": "oidc_user@example.com" } } } class AuthenticateOIDCTests(utils.TestCase): GROUP = 'auth' def setUp(self): super(AuthenticateOIDCTests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session() self.IDENTITY_PROVIDER = 'bluepages' self.PROTOCOL = 'oidc' self.USER_NAME = 'oidc_user@example.com' self.PASSWORD = uuid.uuid4().hex self.CLIENT_ID = uuid.uuid4().hex self.CLIENT_SECRET = uuid.uuid4().hex self.ACCESS_TOKEN_ENDPOINT = 'https://localhost:8020/oidc/token' self.FEDERATION_AUTH_URL = '%s/%s' % ( self.TEST_URL, 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') self.oidcplugin = oidc.OidcPassword( self.TEST_URL, self.IDENTITY_PROVIDER, self.PROTOCOL, username=self.USER_NAME, password=self.PASSWORD, client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT) @testtools.skip("TypeError: __init__() got an unexpected keyword" " argument 'project_name'") def test_conf_params(self): """Ensure OpenID Connect config options work.""" section = uuid.uuid4().hex identity_provider = uuid.uuid4().hex protocol = uuid.uuid4().hex username = uuid.uuid4().hex password = uuid.uuid4().hex client_id = uuid.uuid4().hex client_secret = uuid.uuid4().hex access_token_endpoint = uuid.uuid4().hex self.conf_fixture.config(auth_section=section, group=self.GROUP) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.conf_fixture.register_opts(oidc.OidcPassword.get_options(), group=section) self.conf_fixture.config(auth_plugin='v3oidcpassword', identity_provider=identity_provider, protocol=protocol, username=username, password=password, client_id=client_id, client_secret=client_secret, access_token_endpoint=access_token_endpoint, group=section) a = conf.load_from_conf_options(self.conf_fixture.conf, self.GROUP) self.assertEqual(identity_provider, a.identity_provider) self.assertEqual(protocol, a.protocol) self.assertEqual(username, a.username) self.assertEqual(password, a.password) self.assertEqual(client_id, a.client_id) self.assertEqual(client_secret, a.client_secret) self.assertEqual(access_token_endpoint, a.access_token_endpoint) def test_initial_call_to_get_access_token(self): """Test initial call, expect JSON access token.""" # Mock the output that creates the access token self.requests_mock.post( self.ACCESS_TOKEN_ENDPOINT, json=ACCESS_TOKEN_ENDPOINT_RESP) # Prep all the values and send the request grant_type = 'password' scope = 'profile email' client_auth = (self.CLIENT_ID, self.CLIENT_SECRET) payload = {'grant_type': grant_type, 'username': self.USER_NAME, 'password': self.PASSWORD, 'scope': scope} res = self.oidcplugin._get_access_token(self.session, client_auth, payload, self.ACCESS_TOKEN_ENDPOINT) # Verify the request matches the expected structure self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, res.request.url) self.assertEqual('POST', res.request.method) encoded_payload = urllib.parse.urlencode(payload) self.assertEqual(encoded_payload, res.request.body) def test_second_call_to_protected_url(self): """Test subsequent call, expect Keystone token.""" # Mock the output that creates the keystone token self.requests_mock.post( self.FEDERATION_AUTH_URL, json=UNSCOPED_TOKEN, headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) # Prep all the values and send the request access_token = uuid.uuid4().hex headers = {'Authorization': 'Bearer ' + access_token} res = self.oidcplugin._get_keystone_token(self.session, headers, self.FEDERATION_AUTH_URL) # Verify the request matches the expected structure self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) self.assertEqual('POST', res.request.method) self.assertEqual(headers['Authorization'], res.request.headers['Authorization']) def test_end_to_end_workflow(self): """Test full OpenID Connect workflow.""" # Mock the output that creates the access token self.requests_mock.post( self.ACCESS_TOKEN_ENDPOINT, json=ACCESS_TOKEN_ENDPOINT_RESP) # Mock the output that creates the keystone token self.requests_mock.post( self.FEDERATION_AUTH_URL, json=UNSCOPED_TOKEN, headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) response = self.oidcplugin.get_unscoped_auth_ref(self.session) self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_auth_saml2.py0000664000175000017500000006562400000000000027404 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 urllib.parse import uuid from lxml import etree from oslo_config import fixture as config import requests from keystoneclient.auth import conf from keystoneclient.contrib.auth.v3 import saml2 from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import saml2_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib.federation import saml as saml_manager ROOTDIR = os.path.dirname(os.path.abspath(__file__)) XMLDIR = os.path.join(ROOTDIR, 'examples', 'xml/') def make_oneline(s): return etree.tostring(etree.XML(s)).replace(b'\n', b'') def _load_xml(filename): with open(XMLDIR + filename, 'rb') as f: return make_oneline(f.read()) class AuthenticateviaSAML2Tests(utils.TestCase): GROUP = 'auth' class _AuthenticatedResponse(object): headers = { 'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER } def json(self): return saml2_fixtures.UNSCOPED_TOKEN class _AuthenticatedResponseInvalidJson(_AuthenticatedResponse): def json(self): raise ValueError() class _AuthentiatedResponseMissingTokenID(_AuthenticatedResponse): headers = {} def setUp(self): super(AuthenticateviaSAML2Tests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session() self.ECP_SP_EMPTY_REQUEST_HEADERS = { 'Accept': 'text/html; application/vnd.paos+xml', 'PAOS': ('ver="urn:liberty:paos:2003-08";' '"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"') } self.ECP_SP_SAML2_REQUEST_HEADERS = { 'Content-Type': 'application/vnd.paos+xml' } self.ECP_SAML2_NAMESPACES = { 'ecp': 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp', 'S': 'http://schemas.xmlsoap.org/soap/envelope/', 'paos': 'urn:liberty:paos:2003-08' } self.ECP_RELAY_STATE = '//ecp:RelayState' self.ECP_SERVICE_PROVIDER_CONSUMER_URL = ('/S:Envelope/S:Header/paos:' 'Request/' '@responseConsumerURL') self.ECP_IDP_CONSUMER_URL = ('/S:Envelope/S:Header/ecp:Response/' '@AssertionConsumerServiceURL') self.IDENTITY_PROVIDER = 'testidp' self.IDENTITY_PROVIDER_URL = 'http://local.url' self.PROTOCOL = 'saml2' self.FEDERATION_AUTH_URL = '%s/%s' % ( self.TEST_URL, 'OS-FEDERATION/identity_providers/testidp/protocols/saml2/auth') self.SHIB_CONSUMER_URL = ('https://openstack4.local/' 'Shibboleth.sso/SAML2/ECP') self.saml2plugin = saml2.Saml2UnscopedToken( self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL, self.TEST_USER, self.TEST_TOKEN) def test_conf_params(self): pass def test_initial_sp_call(self): """Test initial call, expect SOAP message.""" self.requests_mock.get( self.FEDERATION_AUTH_URL, content=make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)) a = self.saml2plugin._send_service_provider_request(self.session) self.assertFalse(a) fixture_soap_response = make_oneline( saml2_fixtures.SP_SOAP_RESPONSE) sp_soap_response = make_oneline( etree.tostring(self.saml2plugin.saml2_authn_request)) error_msg = "Expected %s instead of %s" % (fixture_soap_response, sp_soap_response) self.assertEqual(fixture_soap_response, sp_soap_response, error_msg) self.assertEqual( self.saml2plugin.sp_response_consumer_url, self.SHIB_CONSUMER_URL, "Expected consumer_url set to %s instead of %s" % ( self.SHIB_CONSUMER_URL, str(self.saml2plugin.sp_response_consumer_url))) def test_initial_sp_call_when_saml_authenticated(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) a = self.saml2plugin._send_service_provider_request(self.session) self.assertTrue(a) self.assertEqual( saml2_fixtures.UNSCOPED_TOKEN['token'], self.saml2plugin.authenticated_response.json()['token']) self.assertEqual( saml2_fixtures.UNSCOPED_TOKEN_HEADER, self.saml2plugin.authenticated_response.headers['X-Subject-Token']) def test_get_unscoped_token_when_authenticated(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER, 'Content-Type': 'application/json'}) token, token_body = self.saml2plugin._get_unscoped_token(self.session) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_body) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, token) def test_initial_sp_call_invalid_response(self): """Send initial SP HTTP request and receive wrong server response.""" self.requests_mock.get(self.FEDERATION_AUTH_URL, text='NON XML RESPONSE') self.assertRaises( exceptions.AuthorizationFailure, self.saml2plugin._send_service_provider_request, self.session) def test_send_authn_req_to_idp(self): self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=saml2_fixtures.SAML2_ASSERTION) self.saml2plugin.sp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin.saml2_authn_request = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE) self.saml2plugin._send_idp_saml2_authn_request(self.session) idp_response = make_oneline(etree.tostring( self.saml2plugin.saml2_idp_authn_response)) saml2_assertion_oneline = make_oneline( saml2_fixtures.SAML2_ASSERTION) error = "Expected %s instead of %s" % (saml2_fixtures.SAML2_ASSERTION, idp_response) self.assertEqual(idp_response, saml2_assertion_oneline, error) def test_fail_basicauth_idp_authentication(self): self.requests_mock.post(self.IDENTITY_PROVIDER_URL, status_code=401) self.saml2plugin.sp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin.saml2_authn_request = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE) self.assertRaises( exceptions.Unauthorized, self.saml2plugin._send_idp_saml2_authn_request, self.session) def test_mising_username_password_in_plugin(self): self.assertRaises(TypeError, saml2.Saml2UnscopedToken, self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL) def test_send_authn_response_to_sp(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.saml2plugin.relay_state = etree.XML( saml2_fixtures.SP_SOAP_RESPONSE).xpath( self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES)[0] self.saml2plugin.saml2_idp_authn_response = etree.XML( saml2_fixtures.SAML2_ASSERTION) self.saml2plugin.idp_response_consumer_url = self.SHIB_CONSUMER_URL self.saml2plugin._send_service_provider_saml2_authn_response( self.session) token_json = self.saml2plugin.authenticated_response.json()['token'] token = self.saml2plugin.authenticated_response.headers[ 'X-Subject-Token'] self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_json) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, token) def test_consumer_url_mismatch_success(self): self.saml2plugin._check_consumer_urls( self.session, self.SHIB_CONSUMER_URL, self.SHIB_CONSUMER_URL) def test_consumer_url_mismatch(self): self.requests_mock.post(self.SHIB_CONSUMER_URL) invalid_consumer_url = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.saml2plugin._check_consumer_urls, self.session, self.SHIB_CONSUMER_URL, invalid_consumer_url) def test_custom_302_redirection(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, text='BODY', headers={'location': self.FEDERATION_AUTH_URL}, status_code=302) self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.session.redirect = False response = self.session.post( self.SHIB_CONSUMER_URL, data='CLIENT BODY') self.assertEqual(302, response.status_code) self.assertEqual(self.FEDERATION_AUTH_URL, response.headers['location']) response = self.saml2plugin._handle_http_ecp_redirect( self.session, response, 'GET') self.assertEqual(self.FEDERATION_AUTH_URL, response.request.url) self.assertEqual('GET', response.request.method) def test_custom_303_redirection(self): self.requests_mock.post( self.SHIB_CONSUMER_URL, text='BODY', headers={'location': self.FEDERATION_AUTH_URL}, status_code=303) self.requests_mock.get( self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER}) self.session.redirect = False response = self.session.post( self.SHIB_CONSUMER_URL, data='CLIENT BODY') self.assertEqual(303, response.status_code) self.assertEqual(self.FEDERATION_AUTH_URL, response.headers['location']) response = self.saml2plugin._handle_http_ecp_redirect( self.session, response, 'GET') self.assertEqual(self.FEDERATION_AUTH_URL, response.request.url) self.assertEqual('GET', response.request.method) def test_end_to_end_workflow(self): self.requests_mock.get( self.FEDERATION_AUTH_URL, content=make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)) self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=saml2_fixtures.SAML2_ASSERTION) self.requests_mock.post( self.SHIB_CONSUMER_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': saml2_fixtures.UNSCOPED_TOKEN_HEADER, 'Content-Type': 'application/json'}) self.session.redirect = False response = self.saml2plugin.get_auth_ref(self.session) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN_HEADER, response.auth_token) class ScopeFederationTokenTests(AuthenticateviaSAML2Tests): TEST_TOKEN = client_fixtures.AUTH_SUBJECT_TOKEN def setUp(self): super(ScopeFederationTokenTests, self).setUp() self.PROJECT_SCOPED_TOKEN_JSON = client_fixtures.project_scoped_token() self.PROJECT_SCOPED_TOKEN_JSON['methods'] = ['saml2'] # for better readability self.TEST_TENANT_ID = self.PROJECT_SCOPED_TOKEN_JSON.project_id self.TEST_TENANT_NAME = self.PROJECT_SCOPED_TOKEN_JSON.project_name self.DOMAIN_SCOPED_TOKEN_JSON = client_fixtures.domain_scoped_token() self.DOMAIN_SCOPED_TOKEN_JSON['methods'] = ['saml2'] # for better readability self.TEST_DOMAIN_ID = self.DOMAIN_SCOPED_TOKEN_JSON.domain_id self.TEST_DOMAIN_NAME = self.DOMAIN_SCOPED_TOKEN_JSON.domain_name self.saml2_scope_plugin = saml2.Saml2ScopedToken( self.TEST_URL, saml2_fixtures.UNSCOPED_TOKEN_HEADER, project_id=self.TEST_TENANT_ID) def test_scope_saml2_token_to_project(self): self.stub_auth(json=self.PROJECT_SCOPED_TOKEN_JSON) token = self.saml2_scope_plugin.get_auth_ref(self.session) self.assertTrue(token.project_scoped, "Received token is not scoped") self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) self.assertEqual(self.TEST_TENANT_ID, token.project_id) self.assertEqual(self.TEST_TENANT_NAME, token.project_name) def test_scope_saml2_token_to_invalid_project(self): self.stub_auth(status_code=401) self.saml2_scope_plugin.project_id = uuid.uuid4().hex self.saml2_scope_plugin.project_name = None self.assertRaises(exceptions.Unauthorized, self.saml2_scope_plugin.get_auth_ref, self.session) def test_scope_saml2_token_to_invalid_domain(self): self.stub_auth(status_code=401) self.saml2_scope_plugin.project_id = None self.saml2_scope_plugin.project_name = None self.saml2_scope_plugin.domain_id = uuid.uuid4().hex self.saml2_scope_plugin.domain_name = None self.assertRaises(exceptions.Unauthorized, self.saml2_scope_plugin.get_auth_ref, self.session) def test_scope_saml2_token_to_domain(self): self.stub_auth(json=self.DOMAIN_SCOPED_TOKEN_JSON) token = self.saml2_scope_plugin.get_auth_ref(self.session) self.assertTrue(token.domain_scoped, "Received token is not scoped") self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) self.assertEqual(self.TEST_DOMAIN_ID, token.domain_id) self.assertEqual(self.TEST_DOMAIN_NAME, token.domain_name) def test_dont_set_project_nor_domain(self): self.saml2_scope_plugin.project_id = None self.saml2_scope_plugin.domain_id = None self.assertRaises(exceptions.ValidationError, saml2.Saml2ScopedToken, self.TEST_URL, client_fixtures.AUTH_SUBJECT_TOKEN) class AuthenticateviaADFSTests(utils.TestCase): GROUP = 'auth' NAMESPACES = { 's': 'http://www.w3.org/2003/05/soap-envelope', 'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512', 'wsa': 'http://www.w3.org/2005/08/addressing', 'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy', 'a': 'http://www.w3.org/2005/08/addressing', 'o': ('http://docs.oasis-open.org/wss/2004/01/oasis' '-200401-wss-wssecurity-secext-1.0.xsd') } USER_XPATH = ('/s:Envelope/s:Header' '/o:Security' '/o:UsernameToken' '/o:Username') PASSWORD_XPATH = ('/s:Envelope/s:Header' '/o:Security' '/o:UsernameToken' '/o:Password') ADDRESS_XPATH = ('/s:Envelope/s:Body' '/trust:RequestSecurityToken' '/wsp:AppliesTo/wsa:EndpointReference' '/wsa:Address') TO_XPATH = ('/s:Envelope/s:Header' '/a:To') @property def _uuid4(self): return '4b911420-4982-4009-8afc-5c596cd487f5' def setUp(self): super(AuthenticateviaADFSTests, self).setUp() self.deprecations.expect_deprecations() self.conf_fixture = self.useFixture(config.Config()) conf.register_conf_options(self.conf_fixture.conf, group=self.GROUP) self.session = session.Session(session=requests.Session()) self.IDENTITY_PROVIDER = 'adfs' self.IDENTITY_PROVIDER_URL = ('http://adfs.local/adfs/service/trust/13' '/usernamemixed') self.FEDERATION_AUTH_URL = '%s/%s' % ( self.TEST_URL, 'OS-FEDERATION/identity_providers/adfs/protocols/saml2/auth') self.SP_ENDPOINT = 'https://openstack4.local/Shibboleth.sso/ADFS' self.adfsplugin = saml2.ADFSUnscopedToken( self.TEST_URL, self.IDENTITY_PROVIDER, self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, self.TEST_USER, self.TEST_TOKEN) self.ADFS_SECURITY_TOKEN_RESPONSE = _load_xml( 'ADFS_RequestSecurityTokenResponse.xml') self.ADFS_FAULT = _load_xml('ADFS_fault.xml') def test_conf_params(self): pass def test_get_adfs_security_token(self): """Test ADFSUnscopedToken._get_adfs_security_token().""" self.requests_mock.post( self.IDENTITY_PROVIDER_URL, content=make_oneline(self.ADFS_SECURITY_TOKEN_RESPONSE), status_code=200) self.adfsplugin._prepare_adfs_request() self.adfsplugin._get_adfs_security_token(self.session) adfs_response = etree.tostring(self.adfsplugin.adfs_token) fixture_response = self.ADFS_SECURITY_TOKEN_RESPONSE self.assertEqual(fixture_response, adfs_response) def test_adfs_request_user(self): self.adfsplugin._prepare_adfs_request() user = self.adfsplugin.prepared_request.xpath( self.USER_XPATH, namespaces=self.NAMESPACES)[0] self.assertEqual(self.TEST_USER, user.text) def test_adfs_request_password(self): self.adfsplugin._prepare_adfs_request() password = self.adfsplugin.prepared_request.xpath( self.PASSWORD_XPATH, namespaces=self.NAMESPACES)[0] self.assertEqual(self.TEST_TOKEN, password.text) def test_adfs_request_to(self): self.adfsplugin._prepare_adfs_request() to = self.adfsplugin.prepared_request.xpath( self.TO_XPATH, namespaces=self.NAMESPACES)[0] self.assertEqual(self.IDENTITY_PROVIDER_URL, to.text) def test_prepare_adfs_request_address(self): self.adfsplugin._prepare_adfs_request() address = self.adfsplugin.prepared_request.xpath( self.ADDRESS_XPATH, namespaces=self.NAMESPACES)[0] self.assertEqual(self.SP_ENDPOINT, address.text) def test_prepare_sp_request(self): assertion = etree.XML(self.ADFS_SECURITY_TOKEN_RESPONSE) assertion = assertion.xpath( saml2.ADFSUnscopedToken.ADFS_ASSERTION_XPATH, namespaces=saml2.ADFSUnscopedToken.ADFS_TOKEN_NAMESPACES) assertion = assertion[0] assertion = etree.tostring(assertion) assertion = assertion.replace( b'http://docs.oasis-open.org/ws-sx/ws-trust/200512', b'http://schemas.xmlsoap.org/ws/2005/02/trust') assertion = urllib.parse.quote(assertion) assertion = 'wa=wsignin1.0&wresult=' + assertion self.adfsplugin.adfs_token = etree.XML( self.ADFS_SECURITY_TOKEN_RESPONSE) self.adfsplugin._prepare_sp_request() self.assertEqual(assertion, self.adfsplugin.encoded_assertion) def test_get_adfs_security_token_authn_fail(self): """Test proper parsing XML fault after bad authentication. An exceptions.AuthorizationFailure should be raised including error message from the XML message indicating where was the problem. """ self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=make_oneline(self.ADFS_FAULT), status_code=500) self.adfsplugin._prepare_adfs_request() self.assertRaises(exceptions.AuthorizationFailure, self.adfsplugin._get_adfs_security_token, self.session) # TODO(marek-denis): Python3 tests complain about missing 'message' # attributes # self.assertEqual('a:FailedAuthentication', e.message) def test_get_adfs_security_token_bad_response(self): """Test proper handling HTTP 500 and mangled (non XML) response. This should never happen yet, keystoneclient should be prepared and correctly raise exceptions.InternalServerError once it cannot parse XML fault message """ self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=b'NOT XML', status_code=500) self.adfsplugin._prepare_adfs_request() self.assertRaises(exceptions.InternalServerError, self.adfsplugin._get_adfs_security_token, self.session) # TODO(marek-denis): Need to figure out how to properly send cookies # from the request_uri() method. def _send_assertion_to_service_provider(self): """Test whether SP issues a cookie.""" cookie = uuid.uuid4().hex self.requests_mock.post(self.SP_ENDPOINT, headers={"set-cookie": cookie}, status_code=302) self.adfsplugin.adfs_token = self._build_adfs_request() self.adfsplugin._prepare_sp_request() self.adfsplugin._send_assertion_to_service_provider(self.session) self.assertEqual(1, len(self.session.session.cookies)) def test_send_assertion_to_service_provider_bad_status(self): self.requests_mock.post(self.SP_ENDPOINT, status_code=500) self.adfsplugin.adfs_token = etree.XML( self.ADFS_SECURITY_TOKEN_RESPONSE) self.adfsplugin._prepare_sp_request() self.assertRaises( exceptions.InternalServerError, self.adfsplugin._send_assertion_to_service_provider, self.session) def test_access_sp_no_cookies_fail(self): # There are no cookies in the session initially, and # _access_service_provider requires a cookie in the session. self.assertRaises(exceptions.AuthorizationFailure, self.adfsplugin._access_service_provider, self.session) def test_check_valid_token_when_authenticated(self): self.requests_mock.get(self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers=client_fixtures.AUTH_RESPONSE_HEADERS) # _access_service_provider requires a cookie in the session. cookie = requests.cookies.create_cookie( name=self.getUniqueString(), value=self.getUniqueString()) self.session.session.cookies.set_cookie(cookie) self.adfsplugin._access_service_provider(self.session) response = self.adfsplugin.authenticated_response self.assertEqual(client_fixtures.AUTH_RESPONSE_HEADERS, response.headers) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], response.json()['token']) def test_end_to_end_workflow(self): self.requests_mock.post(self.IDENTITY_PROVIDER_URL, content=self.ADFS_SECURITY_TOKEN_RESPONSE, status_code=200) self.requests_mock.post(self.SP_ENDPOINT, headers={"set-cookie": 'x'}, status_code=302) self.requests_mock.get(self.FEDERATION_AUTH_URL, json=saml2_fixtures.UNSCOPED_TOKEN, headers=client_fixtures.AUTH_RESPONSE_HEADERS) # NOTE(marek-denis): We need to mimic this until self.requests_mock can # issue cookies properly. cookie = requests.cookies.create_cookie( name=self.getUniqueString(), value=self.getUniqueString()) self.session.session.cookies.set_cookie(cookie) token, token_json = self.adfsplugin._get_unscoped_token(self.session) self.assertEqual(token, client_fixtures.AUTH_SUBJECT_TOKEN) self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_json) class SAMLGenerationTests(utils.ClientTestCase): def setUp(self): super(SAMLGenerationTests, self).setUp() self.manager = self.client.federation.saml self.SAML2_FULL_URL = ''.join([self.TEST_URL, saml_manager.SAML2_ENDPOINT]) self.ECP_FULL_URL = ''.join([self.TEST_URL, saml_manager.ECP_ENDPOINT]) def test_saml_create(self): """Test that a token can be exchanged for a SAML assertion.""" token_id = uuid.uuid4().hex service_provider_id = uuid.uuid4().hex # Mock the returned text for '/auth/OS-FEDERATION/saml2 self.requests_mock.post(self.SAML2_FULL_URL, text=saml2_fixtures.TOKEN_BASED_SAML) text = self.manager.create_saml_assertion(service_provider_id, token_id) # Ensure returned text is correct self.assertEqual(saml2_fixtures.TOKEN_BASED_SAML, text) # Ensure request headers and body are correct req_json = self.requests_mock.last_request.json() self.assertEqual(token_id, req_json['auth']['identity']['token']['id']) self.assertEqual(service_provider_id, req_json['auth']['scope']['service_provider']['id']) self.assertRequestHeaderEqual('Content-Type', 'application/json') def test_ecp_create(self): """Test that a token can be exchanged for an ECP wrapped assertion.""" token_id = uuid.uuid4().hex service_provider_id = uuid.uuid4().hex # Mock returned text for '/auth/OS-FEDERATION/saml2/ecp self.requests_mock.post(self.ECP_FULL_URL, text=saml2_fixtures.TOKEN_BASED_ECP) text = self.manager.create_ecp_assertion(service_provider_id, token_id) # Ensure returned text is correct self.assertEqual(saml2_fixtures.TOKEN_BASED_ECP, text) # Ensure request headers and body are correct req_json = self.requests_mock.last_request.json() self.assertEqual(token_id, req_json['auth']['identity']['token']['id']) self.assertEqual(service_provider_id, req_json['auth']['scope']['service_provider']['id']) self.assertRequestHeaderEqual('Content-Type', 'application/json') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_client.py0000664000175000017500000002765000000000000026620 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 import uuid from oslo_serialization import jsonutils from keystoneauth1 import session as auth_session from keystoneclient.auth import token_endpoint from keystoneclient import exceptions from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client class KeystoneClientTest(utils.TestCase): def test_unscoped_init(self): token = client_fixtures.unscoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_domain_name=token.user_domain_name, username=token.user_name, password='password', auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertFalse(c.has_service_catalog()) self.assertEqual(token.user_id, c.get_user_id(session=None)) self.assertIsNone(c.get_project_id(session=None)) def test_domain_scoped_init(self): token = client_fixtures.domain_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', domain_name=token.domain_name, auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertTrue(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertEqual(token.domain_id, c.auth_domain_id) def test_project_scoped_init(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token), # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', user_domain_name=token.user_domain_name, project_name=token.project_name, auth_url=self.TEST_URL) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertTrue(c.auth_ref.project_scoped) self.assertEqual(token.user_id, c.auth_user_id) self.assertEqual(token.project_id, c.auth_tenant_id) self.assertEqual(token.user_id, c.get_user_id(session=None)) self.assertEqual(token.project_id, c.get_project_id(session=None)) def test_auth_ref_load(self): token = client_fixtures.project_scoped_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=token.user_id, password='password', project_id=token.project_id, auth_url=self.TEST_URL) cache = jsonutils.dumps(c.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache)) self.assertIsNotNone(new_client.auth_ref) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertEqual(token.user_name, new_client.username) self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v3') def test_auth_ref_load_with_overridden_arguments(self): new_auth_url = 'https://newkeystone.com/v3' user_id = uuid.uuid4().hex user_name = uuid.uuid4().hex project_id = uuid.uuid4().hex first = client_fixtures.project_scoped_token(user_id=user_id, user_name=user_name, project_id=project_id) second = client_fixtures.project_scoped_token(user_id=user_id, user_name=user_name, project_id=project_id) self.stub_auth(json=first) self.stub_auth(json=second, base_url=new_auth_url) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_id=user_id, password='password', project_id=project_id, auth_url=self.TEST_URL) cache = jsonutils.dumps(c.auth_ref) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): new_client = client.Client(auth_ref=jsonutils.loads(cache), auth_url=new_auth_url) self.assertIsNotNone(new_client.auth_ref) self.assertFalse(new_client.auth_ref.domain_scoped) self.assertTrue(new_client.auth_ref.project_scoped) self.assertEqual(new_auth_url, new_client.auth_url) self.assertEqual(user_name, new_client.username) self.assertIsNone(new_client.password) self.assertEqual(new_client.management_url, 'http://admin:35357/v3') def test_trust_init(self): token = client_fixtures.trust_token() self.stub_auth(json=token) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): c = client.Client(user_domain_name=token.user_domain_name, username=token.user_name, password='password', auth_url=self.TEST_URL, trust_id=token.trust_id) self.assertIsNotNone(c.auth_ref) self.assertFalse(c.auth_ref.domain_scoped) self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(token.trust_id, c.auth_ref.trust_id) self.assertEqual(token.trustee_user_id, c.auth_ref.trustee_user_id) self.assertEqual(token.trustor_user_id, c.auth_ref.trustor_user_id) self.assertTrue(c.auth_ref.trust_scoped) self.assertEqual(token.user_id, c.auth_user_id) def test_init_err_no_auth_url(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, username='exampleuser', password='password') def _management_url_is_updated(self, fixture, **kwargs): second = copy.deepcopy(fixture) first_url = 'http://admin:35357/v3' second_url = "http://secondurl:%d/v3'" for entry in second['token']['catalog']: if entry['type'] == 'identity': entry['endpoints'] = [{ 'url': second_url % 5000, 'region': 'RegionOne', 'interface': 'public' }, { 'url': second_url % 5000, 'region': 'RegionOne', 'interface': 'internal' }, { 'url': second_url % 35357, 'region': 'RegionOne', 'interface': 'admin' }] self.stub_auth(response_list=[{'json': fixture}, {'json': second}]) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cl = client.Client(username='exampleuser', password='password', auth_url=self.TEST_URL, **kwargs) self.assertEqual(cl.management_url, first_url) with self.deprecations.expect_deprecations_here(): cl.authenticate() self.assertEqual(cl.management_url, second_url % 35357) def test_management_url_is_updated_with_project(self): self._management_url_is_updated(client_fixtures.project_scoped_token(), project_name='exampleproject') def test_management_url_is_updated_with_domain(self): self._management_url_is_updated(client_fixtures.domain_scoped_token(), domain_name='exampledomain') def test_client_with_region_name_passes_to_service_catalog(self): # NOTE(jamielennox): this is deprecated behaviour that should be # removed ASAP, however must remain compatible. self.deprecations.expect_deprecations() self.stub_auth(json=client_fixtures.auth_response_body()) cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='North') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'http://glance.north.host/glanceapi/public') cl = client.Client(username='exampleuser', password='password', project_name='exampleproject', auth_url=self.TEST_URL, region_name='South') self.assertEqual(cl.service_catalog.url_for(service_type='image'), 'http://glance.south.host/glanceapi/public') def test_client_without_auth_params(self): # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): self.assertRaises(exceptions.AuthorizationFailure, client.Client, project_name='exampleproject', auth_url=self.TEST_URL) def test_client_params(self): with self.deprecations.expect_deprecations_here(): sess = session.Session() auth = token_endpoint.Token('a', 'b') opts = {'auth': auth, 'connect_retries': 50, 'endpoint_override': uuid.uuid4().hex, 'interface': uuid.uuid4().hex, 'region_name': uuid.uuid4().hex, 'service_name': uuid.uuid4().hex, 'user_agent': uuid.uuid4().hex, } cl = client.Client(session=sess, **opts) for k, v in opts.items(): self.assertEqual(v, getattr(cl._adapter, k)) self.assertEqual('identity', cl._adapter.service_type) self.assertEqual((3, 0), cl._adapter.version) def test_empty_service_catalog_param(self): # Client().service_catalog should return None if the client is not # authenticated sess = auth_session.Session() cl = client.Client(session=sess) self.assertIsNone(cl.service_catalog) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_credentials.py0000664000175000017500000000243500000000000027631 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import credentials class CredentialTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(CredentialTests, self).setUp() self.key = 'credential' self.collection_key = 'credentials' self.model = credentials.Credential self.manager = self.client.credentials def new_ref(self, **kwargs): kwargs = super(CredentialTests, self).new_ref(**kwargs) kwargs.setdefault('blob', uuid.uuid4().hex) kwargs.setdefault('project_id', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('user_id', uuid.uuid4().hex) return kwargs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_discover.py0000664000175000017500000000742200000000000027153 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 keystoneclient.generic import client from keystoneclient.tests.unit.v3 import utils class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): def setUp(self): super(DiscoverKeystoneTests, self).setUp() self.TEST_RESPONSE_DICT = { "versions": { "values": [{"id": "v3.0", "status": "beta", "updated": "2013-03-06T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v3.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "identity-dev-guide-3.pdf", }, ]}, {"id": "v2.0", "status": "beta", "updated": "2013-03-06T00:00:00Z", "links": [ {"rel": "self", "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "content/", }, {"rel": "describedby", "type": "application/pdf", "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", } ]}], }, } self.TEST_REQUEST_HEADERS = { 'User-Agent': 'python-keystoneclient', 'Accept': 'application/json', } def test_get_version_local(self): self.requests_mock.get("http://localhost:35357/", status_code=300, json=self.TEST_RESPONSE_DICT) # Creating a HTTPClient not using session is deprecated. with self.deprecations.expect_deprecations_here(): cs = client.Client() versions = cs.discover() self.assertIsInstance(versions, dict) self.assertIn('message', versions) self.assertIn('v3.0', versions) self.assertEqual( versions['v3.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][0]['links'][0] ['href']) self.assertEqual( versions['v2.0']['url'], self.TEST_RESPONSE_DICT['versions']['values'][1]['links'][0] ['href']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_domain_configs.py0000664000175000017500000000670600000000000030320 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import domain_configs class DomainConfigsTests(utils.ClientTestCase, utils.CrudTests): """Test domain config database management.""" def setUp(self): super(DomainConfigsTests, self).setUp() self.key = 'config' self.model = domain_configs.DomainConfig self.manager = self.client.domain_configs def new_ref(self, **kwargs): config_groups = {'identity': {uuid.uuid4().hex: uuid.uuid4().hex}, 'ldap': {uuid.uuid4().hex: uuid.uuid4().hex}} kwargs.setdefault('config', config_groups) return kwargs def _assert_resource_attributes(self, resource, req_ref): for attr in req_ref: self.assertEqual( getattr(resource, attr), req_ref[attr], 'Expected different %s' % attr) def test_create(self): domain_id = uuid.uuid4().hex config = self.new_ref() self.stub_url('PUT', parts=['domains', domain_id, 'config'], json=config, status_code=201) res = self.manager.create(domain_id, config) self._assert_resource_attributes(res, config['config']) self.assertEntityRequestBodyIs(config) def test_update(self): domain_id = uuid.uuid4().hex config = self.new_ref() self.stub_url('PATCH', parts=['domains', domain_id, 'config'], json=config, status_code=200) res = self.manager.update(domain_id, config) self._assert_resource_attributes(res, config['config']) self.assertEntityRequestBodyIs(config) def test_get(self): domain_id = uuid.uuid4().hex config = self.new_ref() config = config['config'] self.stub_entity('GET', parts=['domains', domain_id, 'config'], entity=config) res = self.manager.get(domain_id) self._assert_resource_attributes(res, config) def test_delete(self): domain_id = uuid.uuid4().hex self.stub_url('DELETE', parts=['domains', domain_id, 'config'], status_code=204) self.manager.delete(domain_id) def test_list(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_list_by_id(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_list_params(self): # List not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) def test_find(self): # Find not supported for domain config self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_domains.py0000664000175000017500000000364400000000000026771 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import domains class DomainTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(DomainTests, self).setUp() self.key = 'domain' self.collection_key = 'domains' self.model = domains.Domain self.manager = self.client.domains def new_ref(self, **kwargs): kwargs = super(DomainTests, self).new_ref(**kwargs) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_filter_for_default_domain_by_id(self): ref = self.new_ref(id='default') super(DomainTests, self).test_list_by_id( ref=ref, id=ref['id']) def test_list_filter_name(self): super(DomainTests, self).test_list(name='adomain123') def test_list_filter_enabled(self): super(DomainTests, self).test_list(enabled=True) def test_list_filter_disabled(self): # False is converted to '0' ref bug #1267530 expected_query = {'enabled': '0'} super(DomainTests, self).test_list(expected_query=expected_query, enabled=False) def test_update_enabled_defaults_to_none(self): super(DomainTests, self).test_update( req_ref={'name': uuid.uuid4().hex}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_ec2.py0000664000175000017500000000724200000000000026006 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import ec2 class EC2Tests(utils.ClientTestCase): def test_create(self): user_id = 'usr' tenant_id = 'tnt' req_body = { "tenant_id": tenant_id, } resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('POST', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) cred = self.client.ec2.create(user_id, tenant_id) self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') self.assertRequestBodyIs(json=req_body) def test_get(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credential": { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2', 'access'], json=resp_body) cred = self.client.ec2.get(user_id, 'access') self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_list(self): user_id = 'usr' tenant_id = 'tnt' resp_body = { "credentials": { "values": [ { "access": "access", "secret": "secret", "tenant_id": tenant_id, "created": "12/12/12", "enabled": True, }, { "access": "another", "secret": "key", "tenant_id": tenant_id, "created": "12/12/31", "enabled": True, } ] } } self.stub_url('GET', ['users', user_id, 'credentials', 'OS-EC2'], json=resp_body) creds = self.client.ec2.list(user_id) self.assertEqual(len(creds), 2) cred = creds[0] self.assertIsInstance(cred, ec2.EC2) self.assertEqual(cred.tenant_id, tenant_id) self.assertEqual(cred.enabled, True) self.assertEqual(cred.access, 'access') self.assertEqual(cred.secret, 'secret') def test_delete(self): user_id = 'usr' access = 'access' self.stub_url('DELETE', ['users', user_id, 'credentials', 'OS-EC2', access], status_code=204) self.client.ec2.delete(user_id, access) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_endpoint_filter.py0000664000175000017500000003022100000000000030513 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. import uuid from keystoneclient.tests.unit.v3 import utils class EndpointTestUtils(object): """Mixin class with shared methods between Endpoint Filter & Policy.""" def new_ref(self, **kwargs): # copied from CrudTests as we need to create endpoint and project # refs for our tests. EndpointFilter is not exactly CRUD API. kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def new_endpoint_ref(self, **kwargs): # copied from EndpointTests as we need endpoint refs for our tests kwargs = self.new_ref(**kwargs) kwargs.setdefault('interface', 'public') kwargs.setdefault('region', uuid.uuid4().hex) kwargs.setdefault('service_id', uuid.uuid4().hex) kwargs.setdefault('url', uuid.uuid4().hex) return kwargs def new_endpoint_group_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('filters') return kwargs class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils): """Test project-endpoint associations (a.k.a. EndpointFilter Extension). Endpoint filter provides associations between service endpoints and projects. These assciations are then used to create ad-hoc catalogs for each project-scoped token request. """ def setUp(self): super(EndpointFilterTests, self).setUp() self.manager = self.client.endpoint_filter def new_project_ref(self, **kwargs): # copied from ProjectTests as we need project refs for our tests kwargs = self.new_ref(**kwargs) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_add_endpoint_to_project_via_id(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.add_endpoint_to_project(project=project_id, endpoint=endpoint_id) def test_add_endpoint_to_project_via_obj(self): project_ref = self.new_project_ref() endpoint_ref = self.new_endpoint_ref() project = self.client.projects.resource_class(self.client.projects, project_ref, loaded=True) endpoint = self.client.endpoints.resource_class(self.client.endpoints, endpoint_ref, loaded=True) self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'projects', project_ref['id'], 'endpoints', endpoint_ref['id']], status_code=201) self.manager.add_endpoint_to_project(project=project, endpoint=endpoint) def test_delete_endpoint_from_project(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('DELETE', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.delete_endpoint_from_project(project=project_id, endpoint=endpoint_id) def test_check_endpoint_in_project(self): endpoint_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('HEAD', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints', endpoint_id], status_code=201) self.manager.check_endpoint_in_project(project=project_id, endpoint=endpoint_id) def test_list_endpoints_for_project(self): project_id = uuid.uuid4().hex endpoints = {'endpoints': [self.new_endpoint_ref(), self.new_endpoint_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoints'], json=endpoints, status_code=200) endpoints_resp = self.manager.list_endpoints_for_project( project=project_id) expected_endpoint_ids = [ endpoint['id'] for endpoint in endpoints['endpoints']] actual_endpoint_ids = [endpoint.id for endpoint in endpoints_resp] self.assertEqual(expected_endpoint_ids, actual_endpoint_ids) def test_list_projects_for_endpoint(self): endpoint_id = uuid.uuid4().hex projects = {'projects': [self.new_project_ref(), self.new_project_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'endpoints', endpoint_id, 'projects'], json=projects, status_code=200) projects_resp = self.manager.list_projects_for_endpoint( endpoint=endpoint_id) expected_project_ids = [ project['id'] for project in projects['projects']] actual_project_ids = [project.id for project in projects_resp] self.assertEqual(expected_project_ids, actual_project_ids) def test_list_projects_for_endpoint_group(self): endpoint_group_id = uuid.uuid4().hex projects = {'projects': [self.new_project_ref(), self.new_project_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects'], json=projects, status_code=200) projects_resp = self.manager.list_projects_for_endpoint_group( endpoint_group=endpoint_group_id) expected_project_ids = [ project['id'] for project in projects['projects']] actual_project_ids = [project.id for project in projects_resp] self.assertEqual(expected_project_ids, actual_project_ids) def test_list_projects_for_endpoint_group_value_error(self): self.assertRaises(ValueError, self.manager.list_projects_for_endpoint_group, endpoint_group='') self.assertRaises(ValueError, self.manager.list_projects_for_endpoint_group, endpoint_group=None) def test_list_endpoint_groups_for_project(self): project_id = uuid.uuid4().hex endpoint_groups = { 'endpoint_groups': [self.new_endpoint_group_ref(), self.new_endpoint_group_ref()]} self.stub_url('GET', [self.manager.OS_EP_FILTER_EXT, 'projects', project_id, 'endpoint_groups'], json=endpoint_groups, status_code=200) endpoint_groups_resp = self.manager.list_endpoint_groups_for_project( project=project_id) expected_endpoint_group_ids = [ endpoint_group['id'] for endpoint_group in endpoint_groups['endpoint_groups'] ] actual_endpoint_group_ids = [ endpoint_group.id for endpoint_group in endpoint_groups_resp ] self.assertEqual(expected_endpoint_group_ids, actual_endpoint_group_ids) def test_list_endpoint_groups_for_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.list_endpoint_groups_for_project, project=value) def test_add_endpoint_group_to_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('PUT', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.add_endpoint_group_to_project( project=project_id, endpoint_group=endpoint_group_id) def test_add_endpoint_group_to_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.add_endpoint_group_to_project, project=value, endpoint_group=uuid.uuid4().hex) def test_check_endpoint_group_in_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('HEAD', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.check_endpoint_group_in_project( project=project_id, endpoint_group=endpoint_group_id) def test_check_endpoint_group_in_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.check_endpoint_group_in_project, project=value, endpoint_group=uuid.uuid4().hex) def test_delete_endpoint_group_from_project(self): endpoint_group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.stub_url('DELETE', [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups', endpoint_group_id, 'projects', project_id], status_code=201) self.manager.delete_endpoint_group_from_project( project=project_id, endpoint_group=endpoint_group_id) def test_delete_endpoint_group_from_project_value_error(self): for value in ('', None): self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=value, endpoint_group=value) self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=uuid.uuid4().hex, endpoint_group=value) self.assertRaises(ValueError, self.manager.delete_endpoint_group_from_project, project=value, endpoint_group=uuid.uuid4().hex) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_endpoint_groups.py0000664000175000017500000000245500000000000030555 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import endpoint_groups class EndpointGroupTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(EndpointGroupTests, self).setUp() self.key = 'endpoint_group' self.collection_key = 'endpoint_groups' self.model = endpoint_groups.EndpointGroup self.manager = self.client.endpoint_groups self.path_prefix = 'OS-EP-FILTER' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('filters', '{"interface": "public"}') kwargs.setdefault('description', uuid.uuid4().hex) return kwargs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_endpoint_policy.py0000664000175000017500000002453500000000000030540 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. import uuid from keystoneclient.tests.unit.v3 import test_endpoint_filter from keystoneclient.tests.unit.v3 import utils class EndpointPolicyTests(utils.ClientTestCase, test_endpoint_filter.EndpointTestUtils): """Test policy-endpoint associations (a.k.a. EndpointPolicy Extension).""" def setUp(self): super(EndpointPolicyTests, self).setUp() self.manager = self.client.endpoint_policy def new_policy_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('blob', uuid.uuid4().hex) return kwargs def new_region_ref(self, **kwargs): kwargs = self.new_ref(**kwargs) return kwargs def new_service_ref(self, **kwargs): kwargs = self.new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) return kwargs def _crud_policy_association_for_endpoint_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex endpoint_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'endpoints', endpoint_id], status_code=204) manager_action(policy=policy_id, endpoint=endpoint_id) def _crud_policy_association_for_endpoint_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() endpoint_ref = self.new_endpoint_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) endpoint = self.client.endpoints.resource_class( self.client.endpoints, endpoint_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'endpoints', endpoint_ref['id']], status_code=204) manager_action(policy=policy, endpoint=endpoint) def test_create_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'PUT', self.manager.create_policy_association_for_endpoint) def test_create_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'PUT', self.manager.create_policy_association_for_endpoint) def test_check_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'HEAD', self.manager.check_policy_association_for_endpoint) def test_check_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'HEAD', self.manager.check_policy_association_for_endpoint) def test_delete_policy_association_for_endpoint_via_id(self): self._crud_policy_association_for_endpoint_via_id( 'DELETE', self.manager.delete_policy_association_for_endpoint) def test_delete_policy_association_for_endpoint_via_obj(self): self._crud_policy_association_for_endpoint_via_obj( 'DELETE', self.manager.delete_policy_association_for_endpoint) def _crud_policy_association_for_service_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex service_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'services', service_id], status_code=204) manager_action(policy=policy_id, service=service_id) def _crud_policy_association_for_service_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() service_ref = self.new_service_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) service = self.client.services.resource_class( self.client.services, service_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'services', service_ref['id']], status_code=204) manager_action(policy=policy, service=service) def test_create_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'PUT', self.manager.create_policy_association_for_service) def test_create_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'PUT', self.manager.create_policy_association_for_service) def test_check_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'HEAD', self.manager.check_policy_association_for_service) def test_check_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'HEAD', self.manager.check_policy_association_for_service) def test_delete_policy_association_for_service_via_id(self): self._crud_policy_association_for_service_via_id( 'DELETE', self.manager.delete_policy_association_for_service) def test_delete_policy_association_for_service_via_obj(self): self._crud_policy_association_for_service_via_obj( 'DELETE', self.manager.delete_policy_association_for_service) def _crud_policy_association_for_region_and_service_via_id( self, http_action, manager_action): policy_id = uuid.uuid4().hex region_id = uuid.uuid4().hex service_id = uuid.uuid4().hex self.stub_url(http_action, ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'services', service_id, 'regions', region_id], status_code=204) manager_action(policy=policy_id, region=region_id, service=service_id) def _crud_policy_association_for_region_and_service_via_obj( self, http_action, manager_action): policy_ref = self.new_policy_ref() region_ref = self.new_region_ref() service_ref = self.new_service_ref() policy = self.client.policies.resource_class( self.client.policies, policy_ref, loaded=True) region = self.client.regions.resource_class( self.client.regions, region_ref, loaded=True) service = self.client.services.resource_class( self.client.services, service_ref, loaded=True) self.stub_url(http_action, ['policies', policy_ref['id'], self.manager.OS_EP_POLICY_EXT, 'services', service_ref['id'], 'regions', region_ref['id']], status_code=204) manager_action(policy=policy, region=region, service=service) def test_create_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'PUT', self.manager.create_policy_association_for_region_and_service) def test_create_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'PUT', self.manager.create_policy_association_for_region_and_service) def test_check_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'HEAD', self.manager.check_policy_association_for_region_and_service) def test_check_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'HEAD', self.manager.check_policy_association_for_region_and_service) def test_delete_policy_association_for_region_and_service_via_id(self): self._crud_policy_association_for_region_and_service_via_id( 'DELETE', self.manager.delete_policy_association_for_region_and_service) def test_delete_policy_association_for_region_and_service_via_obj(self): self._crud_policy_association_for_region_and_service_via_obj( 'DELETE', self.manager.delete_policy_association_for_region_and_service) def test_get_policy_for_endpoint(self): endpoint_id = uuid.uuid4().hex expected_policy = self.new_policy_ref() self.stub_url('GET', ['endpoints', endpoint_id, self.manager.OS_EP_POLICY_EXT, 'policy'], json={'policy': expected_policy}, status_code=200) policy_resp = self.manager.get_policy_for_endpoint( endpoint=endpoint_id) self.assertEqual(expected_policy['id'], policy_resp.id) self.assertEqual(expected_policy['blob'], policy_resp.blob) self.assertEqual(expected_policy['type'], policy_resp.type) def test_list_endpoints_for_policy(self): policy_id = uuid.uuid4().hex endpoints = {'endpoints': [self.new_endpoint_ref(), self.new_endpoint_ref()]} self.stub_url('GET', ['policies', policy_id, self.manager.OS_EP_POLICY_EXT, 'endpoints'], json=endpoints, status_code=200) endpoints_resp = self.manager.list_endpoints_for_policy( policy=policy_id) expected_endpoint_ids = [ endpoint['id'] for endpoint in endpoints['endpoints']] actual_endpoint_ids = [endpoint.id for endpoint in endpoints_resp] self.assertEqual(expected_endpoint_ids, actual_endpoint_ids) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_endpoints.py0000664000175000017500000001025700000000000027340 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import endpoints class EndpointTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(EndpointTests, self).setUp() self.key = 'endpoint' self.collection_key = 'endpoints' self.model = endpoints.Endpoint self.manager = self.client.endpoints def new_ref(self, **kwargs): kwargs = super(EndpointTests, self).new_ref(**kwargs) kwargs.setdefault('interface', 'public') kwargs.setdefault('region', uuid.uuid4().hex) kwargs.setdefault('service_id', uuid.uuid4().hex) kwargs.setdefault('url', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_create_public_interface(self): ref = self.new_ref(interface='public') self.test_create(ref) def test_create_admin_interface(self): ref = self.new_ref(interface='admin') self.test_create(ref) def test_create_internal_interface(self): ref = self.new_ref(interface='internal') self.test_create(ref) def test_create_invalid_interface(self): ref = self.new_ref(interface=uuid.uuid4().hex) self.assertRaises(exceptions.ValidationError, self.manager.create, **utils.parameterize(ref)) def test_update_public_interface(self): ref = self.new_ref(interface='public') self.test_update(ref) def test_update_admin_interface(self): ref = self.new_ref(interface='admin') self.test_update(ref) def test_update_internal_interface(self): ref = self.new_ref(interface='internal') self.test_update(ref) def test_update_invalid_interface(self): ref = self.new_ref(interface=uuid.uuid4().hex) ref['endpoint'] = "fake_endpoint" self.assertRaises(exceptions.ValidationError, self.manager.update, **utils.parameterize(ref)) def test_list_public_interface(self): interface = 'public' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_admin_interface(self): interface = 'admin' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_internal_interface(self): interface = 'admin' expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.test_list(expected_path=expected_path, interface=interface) def test_list_invalid_interface(self): interface = uuid.uuid4().hex expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) self.assertRaises(exceptions.ValidationError, self.manager.list, expected_path=expected_path, interface=interface) def test_list_filtered_by_region(self): region_id = uuid.uuid4().hex ref_list = [self.new_ref(region=region_id), self.new_ref(region=region_id)] expected_path = 'v3/%s?region_id=%s' % (self.collection_key, region_id) expected_query = {'region_id': region_id} # Validate passing either region or region_id result to the API call. self.test_list(ref_list=ref_list, expected_path=expected_path, expected_query=expected_query, region=region_id) self.test_list(ref_list=ref_list, expected_path=expected_path, expected_query=expected_query, region_id=region_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_federation.py0000664000175000017500000007536100000000000027464 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 import fixtures import uuid from keystoneauth1 import exceptions from keystoneauth1 import fixture from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneauth1.tests.unit import k2k_fixtures from testtools import matchers from keystoneclient import access from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import client from keystoneclient.v3.contrib.federation import base from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3.contrib.federation import mappings from keystoneclient.v3.contrib.federation import protocols from keystoneclient.v3.contrib.federation import service_providers from keystoneclient.v3 import domains from keystoneclient.v3 import projects class IdentityProviderTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(IdentityProviderTests, self).setUp() self.key = 'identity_provider' self.collection_key = 'identity_providers' self.model = identity_providers.IdentityProvider self.manager = self.client.federation.identity_providers self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_positional_parameters_expect_fail(self): """Ensure CrudManager raises TypeError exceptions. After passing wrong number of positional arguments an exception should be raised. Operations to be tested: * create() * get() * list() * delete() * update() """ POS_PARAM_1 = uuid.uuid4().hex POS_PARAM_2 = uuid.uuid4().hex POS_PARAM_3 = uuid.uuid4().hex PARAMETERS = { 'create': (POS_PARAM_1, POS_PARAM_2), 'get': (POS_PARAM_1, POS_PARAM_2), 'list': (POS_PARAM_1, POS_PARAM_2), 'update': (POS_PARAM_1, POS_PARAM_2, POS_PARAM_3), 'delete': (POS_PARAM_1, POS_PARAM_2) } for f_name, args in PARAMETERS.items(): self.assertRaises(TypeError, getattr(self.manager, f_name), *args) def test_create(self): ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') self.stub_entity('PUT', entity=ref, id=ref['id'], status_code=201) returned = self.manager.create(**ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) class MappingTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(MappingTests, self).setUp() self.key = 'mapping' self.collection_key = 'mappings' self.model = mappings.Mapping self.manager = self.client.federation.mappings self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('rules', [uuid.uuid4().hex, uuid.uuid4().hex]) return kwargs def test_create(self): ref = self.new_ref() manager_ref = ref.copy() mapping_id = manager_ref.pop('id') req_ref = ref.copy() self.stub_entity('PUT', entity=req_ref, id=mapping_id, status_code=201) returned = self.manager.create(mapping_id=mapping_id, **manager_ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(manager_ref) class ProtocolTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ProtocolTests, self).setUp() self.key = 'protocol' self.collection_key = 'protocols' self.model = protocols.Protocol self.manager = self.client.federation.protocols self.path_prefix = 'OS-FEDERATION/identity_providers' def _transform_to_response(self, ref): """Construct a response body from a dictionary.""" response = copy.deepcopy(ref) del response['identity_provider'] return response def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('mapping_id', uuid.uuid4().hex) kwargs.setdefault('identity_provider', uuid.uuid4().hex) return kwargs def build_parts(self, idp_id, protocol_id=None): """Build array used to construct mocking URL. Construct and return array with URL parts later used by methods like utils.TestCase.stub_entity(). Example of URL: ``OS-FEDERATION/identity_providers/{idp_id}/ protocols/{protocol_id}`` """ parts = ['OS-FEDERATION', 'identity_providers', idp_id, 'protocols'] if protocol_id: parts.append(protocol_id) return parts def test_build_url_provide_base_url(self): base_url = uuid.uuid4().hex parameters = {'base_url': base_url} url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual('/'.join([base_url, self.collection_key]), url) def test_build_url_w_idp_id(self): """Test whether kwargs ``base_url`` discards object's base_url. This test shows, that when ``base_url`` is specified in the dict_args_in_out dictionary, values like ``identity_provider_id`` are not taken into consideration while building the url. """ base_url, identity_provider_id = uuid.uuid4().hex, uuid.uuid4().hex parameters = { 'base_url': base_url, 'identity_provider_id': identity_provider_id } url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual('/'.join([base_url, self.collection_key]), url) def test_build_url_default_base_url(self): identity_provider_id = uuid.uuid4().hex parameters = { 'identity_provider_id': identity_provider_id } url = self.manager.build_url(dict_args_in_out=parameters) self.assertEqual( '/'.join([self.manager.base_url, identity_provider_id, self.manager.collection_key]), url) def test_create(self): """Test creating federation protocol tied to an Identity Provider. URL to be tested: PUT /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('PUT', entity=expected, parts=parts, status_code=201) returned = self.manager.create( protocol_id=ref['id'], identity_provider=ref['identity_provider'], mapping=ref['mapping_id']) self.assertEqual(expected, returned.to_dict()) request_body = {'mapping_id': ref['mapping_id']} self.assertEntityRequestBodyIs(request_body) def test_get(self): """Fetch federation protocol object. URL to be tested: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('GET', entity=expected, parts=parts, status_code=201) returned = self.manager.get(ref['identity_provider'], ref['id']) self.assertIsInstance(returned, self.model) self.assertEqual(expected, returned.to_dict()) def test_delete(self): """Delete federation protocol object. URL to be tested: DELETE /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('DELETE', parts=parts, status_code=204) self.manager.delete(ref['identity_provider'], ref['id']) def test_list(self): """Test listing all federation protocols tied to the Identity Provider. URL to be tested: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols """ def _ref_protocols(): return { 'id': uuid.uuid4().hex, 'mapping_id': uuid.uuid4().hex } ref = self.new_ref() expected = [_ref_protocols() for _ in range(3)] parts = self.build_parts(idp_id=ref['identity_provider']) self.stub_entity('GET', parts=parts, entity=expected, status_code=200) returned = self.manager.list(ref['identity_provider']) for obj, ref_obj in zip(returned, expected): self.assertEqual(obj.to_dict(), ref_obj) def test_list_by_id(self): # The test in the parent class needs to be overridden because it # assumes globally unique IDs, which is not the case with protocol IDs # (which are contextualized per identity provider). ref = self.new_ref() super(ProtocolTests, self).test_list_by_id( ref=ref, identity_provider=ref['identity_provider'], id=ref['id']) def test_list_params(self): request_args = self.new_ref() filter_kwargs = {uuid.uuid4().hex: uuid.uuid4().hex} parts = self.build_parts(request_args['identity_provider']) # Return HTTP 401 as we don't accept such requests. self.stub_entity('GET', parts=parts, status_code=401) self.assertRaises(exceptions.Unauthorized, self.manager.list, request_args['identity_provider'], **filter_kwargs) self.assertQueryStringContains(**filter_kwargs) def test_update(self): """Test updating federation protocol. URL to be tested: PATCH /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol """ ref = self.new_ref() expected = self._transform_to_response(ref) parts = self.build_parts( idp_id=ref['identity_provider'], protocol_id=ref['id']) self.stub_entity('PATCH', parts=parts, entity=expected, status_code=200) returned = self.manager.update(ref['identity_provider'], ref['id'], mapping=ref['mapping_id']) self.assertIsInstance(returned, self.model) self.assertEqual(expected, returned.to_dict()) request_body = {'mapping_id': ref['mapping_id']} self.assertEntityRequestBodyIs(request_body) class EntityManagerTests(utils.ClientTestCase): def test_create_object_expect_fail(self): self.assertRaises(TypeError, base.EntityManager, self.client) class FederationProjectTests(utils.ClientTestCase): def setUp(self): super(FederationProjectTests, self).setUp() self.key = 'project' self.collection_key = 'projects' self.model = projects.Project self.manager = self.client.federation.projects self.URL = "%s%s" % (self.TEST_URL, '/auth/projects') def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_list_accessible_projects(self): projects_ref = [self.new_ref(), self.new_ref()] projects_json = { self.collection_key: [self.new_ref(), self.new_ref()] } self.requests_mock.get(self.URL, json=projects_json) returned_list = self.manager.list() self.assertEqual(len(projects_ref), len(returned_list)) for project in returned_list: self.assertIsInstance(project, self.model) class K2KFederatedProjectTests(utils.TestCase): TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') TEST_PASS = 'password' REQUEST_ECP_URL = TEST_URL + '/auth/OS-FEDERATION/saml2/ecp' SP_ID = 'sp1' SP_ROOT_URL = 'https://example.com/v3' SP_URL = 'https://example.com/Shibboleth.sso/SAML2/ECP' SP_AUTH_URL = (SP_ROOT_URL + '/OS-FEDERATION/identity_providers' '/testidp/protocols/saml2/auth') def setUp(self): super(K2KFederatedProjectTests, self).setUp() self.token_v3 = fixture.V3Token() self.token_v3.add_service_provider( self.SP_ID, self.SP_AUTH_URL, self.SP_URL) self.session = session.Session() self.collection_key = 'projects' self.model = projects.Project self.URL = '%s%s' % (self.SP_ROOT_URL, '/auth/projects') self.k2kplugin = self.get_plugin() self._mock_k2k_flow_urls() def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def _get_base_plugin(self): self.stub_url('POST', ['auth', 'tokens'], headers={'X-Subject-Token': uuid.uuid4().hex}, json=self.token_v3) return v3.Password(self.TEST_URL, username=self.TEST_USER, password=self.TEST_PASS) def _mock_k2k_flow_urls(self): # We need to check the auth versions available self.requests_mock.get( self.TEST_URL, json={'version': fixture.V3Discovery(self.TEST_URL)}, headers={'Content-Type': 'application/json'}) # The identity provider receives a request for an ECP wrapped # assertion. This assertion contains the user authentication info # and will be presented to the service provider self.requests_mock.register_uri( 'POST', self.REQUEST_ECP_URL, content=k2k_fixtures.ECP_ENVELOPE.encode(), headers={'Content-Type': 'application/vnd.paos+xml'}, status_code=200) # The service provider is presented with the ECP wrapped assertion # generated by the identity provider and should return a redirect # (302 or 303) upon successful authentication self.requests_mock.register_uri( 'POST', self.SP_URL, content=k2k_fixtures.TOKEN_BASED_ECP.encode(), headers={'Content-Type': 'application/vnd.paos+xml'}, status_code=302) # Should not follow the redirect URL, but use the auth_url attribute self.requests_mock.register_uri( 'GET', self.SP_AUTH_URL, json=k2k_fixtures.UNSCOPED_TOKEN, headers={'X-Subject-Token': k2k_fixtures.UNSCOPED_TOKEN_HEADER}) def get_plugin(self, **kwargs): kwargs.setdefault('base_plugin', self._get_base_plugin()) kwargs.setdefault('service_provider', self.SP_ID) return v3.Keystone2Keystone(**kwargs) def test_list_projects(self): k2k_client = client.Client(session=self.session, auth=self.k2kplugin) self.requests_mock.get(self.URL, json={ self.collection_key: [self.new_ref(), self.new_ref()] }) self.requests_mock.get(self.SP_ROOT_URL, json={ 'version': fixture.discovery.V3Discovery(self.SP_ROOT_URL) }) returned_list = k2k_client.federation.projects.list() self.assertThat(returned_list, matchers.HasLength(2)) for project in returned_list: self.assertIsInstance(project, self.model) class FederationDomainTests(utils.ClientTestCase): def setUp(self): super(FederationDomainTests, self).setUp() self.key = 'domain' self.collection_key = 'domains' self.model = domains.Domain self.manager = self.client.federation.domains self.URL = "%s%s" % (self.TEST_URL, '/auth/domains') def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) return kwargs def test_list_accessible_domains(self): domains_ref = [self.new_ref(), self.new_ref()] domains_json = { self.collection_key: domains_ref } self.requests_mock.get(self.URL, json=domains_json) returned_list = self.manager.list() self.assertEqual(len(domains_ref), len(returned_list)) for domain in returned_list: self.assertIsInstance(domain, self.model) class FederatedTokenTests(utils.ClientTestCase): def setUp(self): super(FederatedTokenTests, self).setUp() token = fixture.V3FederationToken() token.set_project_scope() token.add_role() self.federated_token = access.AccessInfo.factory(body=token) def test_federated_property_federated_token(self): """Check if is_federated property returns expected value.""" self.assertTrue(self.federated_token.is_federated) def test_get_user_domain_name(self): """Ensure a federated user's domain name does not exist.""" self.assertIsNone(self.federated_token.user_domain_name) def test_get_user_domain_id(self): """Ensure a federated user's domain ID does not exist.""" self.assertEqual('Federated', self.federated_token.user_domain_id) class ServiceProviderTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ServiceProviderTests, self).setUp() self.key = 'service_provider' self.collection_key = 'service_providers' self.model = service_providers.ServiceProvider self.manager = self.client.federation.service_providers self.path_prefix = 'OS-FEDERATION' def new_ref(self, **kwargs): kwargs.setdefault('auth_url', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault('sp_url', uuid.uuid4().hex) return kwargs def test_positional_parameters_expect_fail(self): """Ensure CrudManager raises TypeError exceptions. After passing wrong number of positional arguments an exception should be raised. Operations to be tested: * create() * get() * list() * delete() * update() """ POS_PARAM_1 = uuid.uuid4().hex POS_PARAM_2 = uuid.uuid4().hex POS_PARAM_3 = uuid.uuid4().hex PARAMETERS = { 'create': (POS_PARAM_1, POS_PARAM_2), 'get': (POS_PARAM_1, POS_PARAM_2), 'list': (POS_PARAM_1, POS_PARAM_2), 'update': (POS_PARAM_1, POS_PARAM_2, POS_PARAM_3), 'delete': (POS_PARAM_1, POS_PARAM_2) } for f_name, args in PARAMETERS.items(): self.assertRaises(TypeError, getattr(self.manager, f_name), *args) def test_create(self): ref = self.new_ref() # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) req_ref = ref.copy() req_ref.pop('id') self.stub_entity('PUT', entity=ref, id=ref['id'], status_code=201) returned = self.manager.create(**ref) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) class IdentityProviderRequestIdTests(utils.TestRequestId): def setUp(self): super(IdentityProviderRequestIdTests, self).setUp() self.mgr = identity_providers.IdentityProviderManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_identity_provider(self): body = {"identity_provider": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin') def test_list_identity_provider(self): body = {"identity_providers": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/identity_providers?') def test_create_identity_provider(self): body = {"identity_provider": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(id="admin", description='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin', body={'identity_provider': {'description': 'fake'}}) def test_update_identity_provider(self): body = {"identity_provider": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin', body={ 'identity_provider': {}}) def test_delete_identity_provider(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin') class MappingRequestIdTests(utils.TestRequestId): def setUp(self): super(MappingRequestIdTests, self).setUp() self.mgr = mappings.MappingManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_mapping(self): body = {"mapping": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings/admin') def test_list_mapping(self): body = {"mappings": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings?') def test_create_mapping(self): body = {"mapping": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(mapping_id="admin", description='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/mappings/admin', body={ 'mapping': {'description': 'fake'}}) def test_update_mapping(self): body = {"mapping": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/mappings/admin', body={'mapping': {}}) def test_delete_mapping(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/mappings/admin') class ProtocolRequestIdTests(utils.TestRequestId): def setUp(self): super(ProtocolRequestIdTests, self).setUp() self.mgr = protocols.ProtocolManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_protocol(self): body = {"protocol": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin", "protocol") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/admin/protocols/protocol') def test_list_protocol(self): body = {"protocols": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list("identity_provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/identity_provider/protocols?') def test_create_protocol(self): body = {"protocol": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create( protocol_id="admin", identity_provider='fake', mapping='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/fake/protocols/admin', body={ 'protocol': {'mapping_id': 'fake'}}) def test_update_protocol(self): body = {"protocol": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update(protocol="admin", identity_provider='fake', mapping='fake') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/fake/protocols/admin', body={ 'protocol': {'mapping_id': 'fake'}}) def test_delete_protocol(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("identity_provider", "protocol") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/identity_providers/' 'identity_provider/protocols/protocol') class ServiceProviderRequestIdTests(utils.TestRequestId): def setUp(self): super(ServiceProviderRequestIdTests, self).setUp() self.mgr = service_providers.ServiceProviderManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_service_provider(self): body = {"service_provider": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider') def test_list_service_provider(self): body = {"service_providers": [{"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.list() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('OS-FEDERATION/service_providers?') def test_create_service_provider(self): body = {"service_provider": {"name": "admin"}} self._mock_request_method(method='post', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.create(id='provider') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) put_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider', body={ 'service_provider': {}}) def test_update_service_provider(self): body = {"service_provider": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("provider") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider', body={ 'service_provider': {}}) def test_delete_service_provider(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("provider") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( 'OS-FEDERATION/service_providers/provider') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_groups.py0000664000175000017500000000407700000000000026657 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 uuid from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import groups class GroupTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(GroupTests, self).setUp() self.key = 'group' self.collection_key = 'groups' self.model = groups.Group self.manager = self.client.groups def new_ref(self, **kwargs): kwargs = super(GroupTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_list_groups_for_user(self): user_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['users', user_id, self.collection_key], status_code=200, entity=ref_list) returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_groups_for_domain(self): ref_list = [self.new_ref(), self.new_ref()] domain_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], status_code=200, entity=ref_list) returned_list = self.manager.list(domain=domain_id) self.assertTrue(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('domain_id=%s' % domain_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_limits.py0000664000175000017500000000561300000000000026636 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import limits class LimitTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(LimitTests, self).setUp() self.key = 'limit' self.collection_key = 'limits' self.model = limits.Limit self.manager = self.client.limits def new_ref(self, **kwargs): ref = { 'id': uuid.uuid4().hex, 'project_id': uuid.uuid4().hex, 'service_id': uuid.uuid4().hex, 'resource_name': uuid.uuid4().hex, 'resource_limit': 15, 'description': uuid.uuid4().hex } ref.update(kwargs) return ref def test_create(self): # This test overrides the generic test case provided by the CrudTests # class because the limits API supports creating multiple limits in a # single POST request. As a result, it returns the limits as a list of # all the created limits from the request. This is different from what # the base test_create() method assumes about keystone's API. The # changes here override the base test to closely model how the actual # limit API behaves. ref = self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') req_ref = [manager_ref.copy()] self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**utils.parameterize(manager_ref)) self.assertIsInstance(returned, self.model) expected_limit = req_ref.pop() for attr in expected_limit: self.assertEqual( getattr(returned, attr), expected_limit[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs([expected_limit]) def test_list_filter_by_service(self): service_id = uuid.uuid4().hex expected_query = {'service_id': service_id} self.test_list(expected_query=expected_query, service=service_id) def test_list_filtered_by_resource_name(self): resource_name = uuid.uuid4().hex self.test_list(resource_name=resource_name) def test_list_filtered_by_region(self): region_id = uuid.uuid4().hex expected_query = {'region_id': region_id} self.test_list(expected_query=expected_query, region=region_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_oauth1.py0000664000175000017500000003253700000000000026543 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 urllib import parse as urlparse import uuid from testtools import matchers from keystoneclient import session from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient import utils as client_utils from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import auth from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens try: from oauthlib import oauth1 except ImportError: oauth1 = None class ConsumerTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(ConsumerTests, self).setUp() self.key = 'consumer' self.collection_key = 'consumers' self.model = consumers.Consumer self.manager = self.client.oauth1.consumers self.path_prefix = 'OS-OAUTH1' def new_ref(self, **kwargs): kwargs = super(ConsumerTests, self).new_ref(**kwargs) kwargs.setdefault('description', uuid.uuid4().hex) return kwargs def test_description_is_optional(self): consumer_id = uuid.uuid4().hex resp_ref = {'consumer': {'description': None, 'id': consumer_id}} self.stub_url('POST', [self.path_prefix, self.collection_key], status_code=201, json=resp_ref) consumer = self.manager.create() self.assertEqual(consumer_id, consumer.id) self.assertIsNone(consumer.description) def test_description_not_included(self): consumer_id = uuid.uuid4().hex resp_ref = {'consumer': {'id': consumer_id}} self.stub_url('POST', [self.path_prefix, self.collection_key], status_code=201, json=resp_ref) consumer = self.manager.create() self.assertEqual(consumer_id, consumer.id) class TokenTests(object): def _new_oauth_token(self): key = uuid.uuid4().hex secret = uuid.uuid4().hex params = {'oauth_token': key, 'oauth_token_secret': secret} token = urlparse.urlencode(params) return (key, secret, token) def _new_oauth_token_with_expires_at(self): key, secret, token = self._new_oauth_token() expires_at = client_utils.strtime() params = {'oauth_token': key, 'oauth_token_secret': secret, 'oauth_expires_at': expires_at} token = urlparse.urlencode(params) return (key, secret, expires_at, token) def _validate_oauth_headers(self, auth_header, oauth_client): """Validate data in the headers. Assert that the data in the headers matches the data that is produced from oauthlib. """ self.assertThat(auth_header, matchers.StartsWith('OAuth ')) parameters = dict( oauth1.rfc5849.utils.parse_authorization_header(auth_header)) self.assertEqual('HMAC-SHA1', parameters['oauth_signature_method']) self.assertEqual('1.0', parameters['oauth_version']) self.assertIsInstance(parameters['oauth_nonce'], str) self.assertEqual(oauth_client.client_key, parameters['oauth_consumer_key']) if oauth_client.resource_owner_key: self.assertEqual(oauth_client.resource_owner_key, parameters['oauth_token'],) if oauth_client.verifier: self.assertEqual(oauth_client.verifier, parameters['oauth_verifier']) if oauth_client.callback_uri: self.assertEqual(oauth_client.callback_uri, parameters['oauth_callback']) return parameters class RequestTokenTests(utils.ClientTestCase, TokenTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(RequestTokenTests, self).setUp() self.model = request_tokens.RequestToken self.manager = self.client.oauth1.request_tokens self.path_prefix = 'OS-OAUTH1' def test_authorize_request_token(self): request_key = uuid.uuid4().hex info = {'id': request_key, 'key': request_key, 'secret': uuid.uuid4().hex} request_token = request_tokens.RequestToken(self.manager, info) verifier = uuid.uuid4().hex resp_ref = {'token': {'oauth_verifier': verifier}} self.stub_url('PUT', [self.path_prefix, 'authorize', request_key], status_code=200, json=resp_ref) # Assert the manager is returning the expected data role_id = uuid.uuid4().hex token = request_token.authorize([role_id]) self.assertEqual(verifier, token.oauth_verifier) # Assert that the request was sent in the expected structure exp_body = {'roles': [{'id': role_id}]} self.assertRequestBodyIs(json=exp_body) def test_create_request_token(self): project_id = uuid.uuid4().hex consumer_key = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex request_key, request_secret, resp_ref = self._new_oauth_token() headers = {'Content-Type': 'application/x-www-form-urlencoded'} self.stub_url('POST', [self.path_prefix, 'request_token'], status_code=201, text=resp_ref, headers=headers) # Assert the manager is returning request token object request_token = self.manager.create(consumer_key, consumer_secret, project_id) self.assertIsInstance(request_token, self.model) self.assertEqual(request_key, request_token.key) self.assertEqual(request_secret, request_token.secret) # Assert that the project id is in the header self.assertRequestHeaderEqual('requested-project-id', project_id) req_headers = self.requests_mock.last_request.headers oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, signature_method=oauth1.SIGNATURE_HMAC, callback_uri="oob") self._validate_oauth_headers(req_headers['Authorization'], oauth_client) class AccessTokenTests(utils.ClientTestCase, TokenTests): def setUp(self): if oauth1 is None: self.skipTest('oauthlib package not available') super(AccessTokenTests, self).setUp() self.manager = self.client.oauth1.access_tokens self.model = access_tokens.AccessToken self.path_prefix = 'OS-OAUTH1' def test_create_access_token_expires_at(self): verifier = uuid.uuid4().hex consumer_key = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex request_key = uuid.uuid4().hex request_secret = uuid.uuid4().hex t = self._new_oauth_token_with_expires_at() access_key, access_secret, expires_at, resp_ref = t headers = {'Content-Type': 'application/x-www-form-urlencoded'} self.stub_url('POST', [self.path_prefix, 'access_token'], status_code=201, text=resp_ref, headers=headers) # Assert that the manager creates an access token object access_token = self.manager.create(consumer_key, consumer_secret, request_key, request_secret, verifier) self.assertIsInstance(access_token, self.model) self.assertEqual(access_key, access_token.key) self.assertEqual(access_secret, access_token.secret) self.assertEqual(expires_at, access_token.expires) req_headers = self.requests_mock.last_request.headers oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, resource_owner_key=request_key, resource_owner_secret=request_secret, signature_method=oauth1.SIGNATURE_HMAC, verifier=verifier) self._validate_oauth_headers(req_headers['Authorization'], oauth_client) class AuthenticateWithOAuthTests(utils.TestCase, TokenTests): def setUp(self): super(AuthenticateWithOAuthTests, self).setUp() if oauth1 is None: self.skipTest('optional package oauthlib is not installed') def test_oauth_authenticate_success(self): consumer_key = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex access_key = uuid.uuid4().hex access_secret = uuid.uuid4().hex # Just use an existing project scoped token and change # the methods to oauth1, and add an OS-OAUTH1 section. oauth_token = client_fixtures.project_scoped_token() oauth_token['methods'] = ["oauth1"] oauth_token['OS-OAUTH1'] = {"consumer_id": consumer_key, "access_token_id": access_key} self.stub_auth(json=oauth_token) with self.deprecations.expect_deprecations_here(): a = auth.OAuth(self.TEST_URL, consumer_key=consumer_key, consumer_secret=consumer_secret, access_key=access_key, access_secret=access_secret) s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) OAUTH_REQUEST_BODY = { "auth": { "identity": { "methods": ["oauth1"], "oauth1": {} } } } self.assertRequestBodyIs(json=OAUTH_REQUEST_BODY) # Assert that the headers have the same oauthlib data req_headers = self.requests_mock.last_request.headers oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, resource_owner_key=access_key, resource_owner_secret=access_secret, signature_method=oauth1.SIGNATURE_HMAC) self._validate_oauth_headers(req_headers['Authorization'], oauth_client) class OauthRequestIdTests(utils.TestRequestId, TokenTests): def setUp(self): super(OauthRequestIdTests, self).setUp() self.mgr = consumers.ConsumerManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_consumers(self): body = {"consumer": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get("admin") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin') def test_create_consumers(self): body = {"consumer": {"name": "admin"}} post_mock = self._mock_request_method(method='post', body=body) response = self.mgr.create(name="admin", description="fake") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) post_mock.assert_called_once_with('/OS-OAUTH1/consumers', body={ 'consumer': {'name': 'admin', 'description': 'fake'}}) def test_update_consumers(self): body = {"consumer": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) self._mock_request_method(method='post', body=body) response = self.mgr.update("admin", "demo") self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin', body={ 'consumer': {'description': 'demo'}}) def test_delete_consumers(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin') class TestOAuthLibModule(utils.TestCase): def test_no_oauthlib_installed(self): with mock.patch.object(auth, 'oauth1', None): self.assertRaises(NotImplementedError, auth.OAuth, self.TEST_URL, consumer_key=uuid.uuid4().hex, consumer_secret=uuid.uuid4().hex, access_key=uuid.uuid4().hex, access_secret=uuid.uuid4().hex) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_policies.py0000664000175000017500000000216100000000000027137 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import policies class PolicyTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(PolicyTests, self).setUp() self.key = 'policy' self.collection_key = 'policies' self.model = policies.Policy self.manager = self.client.policies def new_ref(self, **kwargs): kwargs = super(PolicyTests, self).new_ref(**kwargs) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('blob', uuid.uuid4().hex) return kwargs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_projects.py0000664000175000017500000004104100000000000027161 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 fixtures import uuid from keystoneauth1 import exceptions as ksa_exceptions from keystoneclient import exceptions as ksc_exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import projects class ProjectTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ProjectTests, self).setUp() self.key = 'project' self.collection_key = 'projects' self.model = projects.Project self.manager = self.client.projects def new_ref(self, **kwargs): kwargs = super(ProjectTests, self).new_ref(**kwargs) return self._new_project_ref(ref=kwargs) def _new_project_ref(self, ref=None): ref = ref or {} ref.setdefault('domain_id', uuid.uuid4().hex) ref.setdefault('enabled', True) ref.setdefault('name', uuid.uuid4().hex) return ref def test_list_projects_for_user(self): ref_list = [self.new_ref(), self.new_ref()] user_id = uuid.uuid4().hex self.stub_entity('GET', ['users', user_id, self.collection_key], entity=ref_list) returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_projects_for_domain(self): ref_list = [self.new_ref(), self.new_ref()] domain_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list(domain=domain_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('domain_id=%s' % domain_id) def test_list_projects_for_parent(self): ref_list = [self.new_ref(), self.new_ref()] parent_id = uuid.uuid4().hex self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list(parent=parent_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] self.assertQueryStringIs('parent_id=%s' % parent_id) def test_create_with_parent(self): parent_ref = self.new_ref() parent_ref['parent_id'] = uuid.uuid4().hex parent = self.test_create(ref=parent_ref) parent.id = parent_ref['id'] # Create another project under 'parent' in the hierarchy ref = self.new_ref() ref['parent_id'] = parent.id child_ref = ref.copy() del child_ref['parent_id'] child_ref['parent'] = parent # test_create() pops the 'id' of the mocked response del ref['id'] # Resource objects may peform lazy-loading. The create() method of # ProjectManager will try to access the 'uuid' attribute of the parent # object, which will trigger a call to fetch the Resource attributes. self.stub_entity('GET', id=parent_ref['id'], entity=parent_ref) self.test_create(ref=child_ref, req_ref=ref) def test_create_with_parent_id(self): ref = self._new_project_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('POST', entity=ref, status_code=201) returned = self.manager.create(name=ref['name'], domain=ref['domain_id'], parent_id=ref['parent_id']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(ref) def test_create_with_parent_and_parent_id(self): ref = self._new_project_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('POST', entity=ref, status_code=201) # Should ignore the 'parent_id' argument since we are also passing # 'parent' returned = self.manager.create(name=ref['name'], domain=ref['domain_id'], parent=ref['parent_id'], parent_id=uuid.uuid4().hex) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(ref) def _create_projects_hierarchy(self, hierarchy_size=3): """Create a project hierarchy with specified size. :param hierarchy_size: the desired hierarchy size, default is 3. :returns: a list of the projects in the created hierarchy. """ ref = self.new_ref() project_id = ref['id'] projects = [ref] for i in range(1, hierarchy_size): new_ref = self.new_ref() new_ref['parent_id'] = project_id projects.append(new_ref) project_id = new_ref['id'] return projects def test_get_with_subtree_as_ids(self): projects = self._create_projects_hierarchy() ref = projects[0] # We will query for projects[0] subtree, it should include projects[1] # and projects[2] structured like the following: # { # projects[1]: { # projects[2]: None # } # } ref['subtree'] = { projects[1]['id']: { projects[2]['id']: None } } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], subtree_as_ids=True) self.assertQueryStringIs('subtree_as_ids') self.assertEqual(ref['subtree'], returned.subtree) def test_get_with_parents_as_ids(self): projects = self._create_projects_hierarchy() ref = projects[2] # We will query for projects[2] parents, it should include projects[1] # and projects[0] structured like the following: # { # projects[1]: { # projects[0]: None # } # } ref['parents'] = { projects[1]['id']: { projects[0]['id']: None } } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_ids=True) self.assertQueryStringIs('parents_as_ids') self.assertEqual(ref['parents'], returned.parents) def test_get_with_parents_as_ids_and_subtree_as_ids(self): ref = self.new_ref() projects = self._create_projects_hierarchy() ref = projects[1] # We will query for projects[1] subtree and parents. The subtree should # include projects[2] and the parents should include projects[2]. ref['parents'] = { projects[0]['id']: None } ref['subtree'] = { projects[2]['id']: None } self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_ids=True, subtree_as_ids=True) self.assertQueryStringIs('subtree_as_ids&parents_as_ids') self.assertEqual(ref['parents'], returned.parents) self.assertEqual(ref['subtree'], returned.subtree) def test_get_with_subtree_as_list(self): projects = self._create_projects_hierarchy() ref = projects[0] ref['subtree_as_list'] = [] for i in range(1, len(projects)): ref['subtree_as_list'].append(projects[i]) self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], subtree_as_list=True) self.assertQueryStringIs('subtree_as_list') for i in range(1, len(projects)): for attr in projects[i]: child = getattr(returned, 'subtree_as_list')[i - 1] self.assertEqual( child[attr], projects[i][attr], 'Expected different %s' % attr) def test_get_with_parents_as_list(self): projects = self._create_projects_hierarchy() ref = projects[2] ref['parents_as_list'] = [] for i in range(0, len(projects) - 1): ref['parents_as_list'].append(projects[i]) self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_list=True) self.assertQueryStringIs('parents_as_list') for i in range(0, len(projects) - 1): for attr in projects[i]: parent = getattr(returned, 'parents_as_list')[i] self.assertEqual( parent[attr], projects[i][attr], 'Expected different %s' % attr) def test_get_with_parents_as_list_and_subtree_as_list(self): ref = self.new_ref() projects = self._create_projects_hierarchy() ref = projects[1] ref['parents_as_list'] = [projects[0]] ref['subtree_as_list'] = [projects[2]] self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id'], parents_as_list=True, subtree_as_list=True) self.assertQueryStringIs('subtree_as_list&parents_as_list') for attr in projects[0]: parent = getattr(returned, 'parents_as_list')[0] self.assertEqual( parent[attr], projects[0][attr], 'Expected different %s' % attr) for attr in projects[2]: child = getattr(returned, 'subtree_as_list')[0] self.assertEqual( child[attr], projects[2][attr], 'Expected different %s' % attr) def test_get_with_invalid_parameters_combination(self): # subtree_as_list and subtree_as_ids can not be included at the # same time in the call. self.assertRaises(ksc_exceptions.ValidationError, self.manager.get, project=uuid.uuid4().hex, subtree_as_list=True, subtree_as_ids=True) # parents_as_list and parents_as_ids can not be included at the # same time in the call. self.assertRaises(ksc_exceptions.ValidationError, self.manager.get, project=uuid.uuid4().hex, parents_as_list=True, parents_as_ids=True) def test_update_with_parent_project(self): ref = self.new_ref() ref['parent_id'] = uuid.uuid4().hex self.stub_entity('PATCH', id=ref['id'], entity=ref, status_code=403) req_ref = ref.copy() req_ref.pop('id') # NOTE(rodrigods): this is the expected behaviour of the Identity # server, a different implementation might not fail this request. self.assertRaises(ksa_exceptions.Forbidden, self.manager.update, ref['id'], **utils.parameterize(req_ref)) def test_add_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=201) self.manager.add_tag(ref['id'], tag_name) def test_update_tags(self): new_tags = ["blue", "orange"] ref = self.new_ref() self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags"], json={"tags": new_tags}, status_code=200) ret = self.manager.update_tags(ref['id'], new_tags) self.assertEqual(ret, new_tags) def test_delete_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("DELETE", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=204) self.manager.delete_tag(ref['id'], tag_name) def test_delete_all_tags(self): ref = self.new_ref() self.stub_url("PUT", parts=[self.collection_key, ref['id'], "tags"], json={"tags": []}, status_code=200) ret = self.manager.update_tags(ref['id'], []) self.assertEqual([], ret) def test_list_tags(self): ref = self.new_ref() tags = ["blue", "orange", "green"] self.stub_url("GET", parts=[self.collection_key, ref['id'], "tags"], json={"tags": tags}, status_code=200) ret_tags = self.manager.list_tags(ref['id']) self.assertEqual(tags, ret_tags) def test_check_tag(self): ref = self.new_ref() tag_name = "blue" self.stub_url("HEAD", parts=[self.collection_key, ref['id'], "tags", tag_name], status_code=204) self.assertTrue(self.manager.check_tag(ref['id'], tag_name)) no_tag = "orange" self.stub_url("HEAD", parts=[self.collection_key, ref['id'], "tags", no_tag], status_code=404) self.assertFalse(self.manager.check_tag(ref['id'], no_tag)) def _build_project_response(self, tags): project_id = uuid.uuid4().hex ret = {"projects": [ {"is_domain": False, "description": "", "tags": tags, "enabled": True, "id": project_id, "parent_id": "default", "domain_id": "default", "name": project_id} ]} return ret class ProjectsRequestIdTests(utils.TestRequestId): url = "/projects" def setUp(self): super(ProjectsRequestIdTests, self).setUp() self.mgr = projects.ProjectManager(self.client) self.mgr.resource_class = projects.Project def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_get_project(self): body = {"project": {"name": "admin"}} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get(project='admin') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '/admin') def test_create_project(self): body = {"project": {"name": "admin", "domain": "admin"}} post_mock = self._mock_request_method(method='post', body=body) response = self.mgr.create('admin', 'admin') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) post_mock.assert_called_once_with(self.url, body={'project': { 'name': 'admin', 'enabled': True, 'domain_id': 'admin'}}) def test_list_project(self): body = {"projects": [{"name": "admin"}, {"name": "admin"}]} get_mock = self._mock_request_method(method='get', body=body) returned_list = self.mgr.list() self.assertEqual(returned_list.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '?') def test_update_project(self): body = {"project": {"name": "admin"}} patch_mock = self._mock_request_method(method='patch', body=body) put_mock = self._mock_request_method(method='put', body=body) response = self.mgr.update("admin", domain='demo') self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) patch_mock.assert_called_once_with(self.url + '/admin', body={ 'project': {'domain_id': 'demo'}}) self.assertFalse(put_mock.called) def test_delete_project(self): get_mock = self._mock_request_method(method='delete') _, resp = self.mgr.delete("admin") self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with(self.url + '/admin') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_regions.py0000664000175000017500000000237100000000000027001 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import regions class RegionTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RegionTests, self).setUp() self.key = 'region' self.collection_key = 'regions' self.model = regions.Region self.manager = self.client.regions def new_ref(self, **kwargs): kwargs = super(RegionTests, self).new_ref(**kwargs) kwargs.setdefault('enabled', True) kwargs.setdefault('id', uuid.uuid4().hex) return kwargs def test_update_enabled_defaults_to_none(self): super(RegionTests, self).test_update( req_ref={'description': uuid.uuid4().hex}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_registered_limits.py0000664000175000017500000000571200000000000031053 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import registered_limits class RegisteredLimitTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RegisteredLimitTests, self).setUp() self.key = 'registered_limit' self.collection_key = 'registered_limits' self.model = registered_limits.RegisteredLimit self.manager = self.client.registered_limits def new_ref(self, **kwargs): ref = { 'id': uuid.uuid4().hex, 'service_id': uuid.uuid4().hex, 'resource_name': uuid.uuid4().hex, 'default_limit': 10, 'description': uuid.uuid4().hex } ref.update(kwargs) return ref def test_create(self): # This test overrides the generic test case provided by the CrudTests # class because the registered limits API supports creating multiple # limits in a single POST request. As a result, it returns the # registered limits as a list of all the created limits from the # request. This is different from what the base test_create() method # assumes about keystone's API. The changes here override the base test # to closely model how the actual registered limit API behaves. ref = self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') req_ref = [manager_ref.copy()] self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**utils.parameterize(manager_ref)) self.assertIsInstance(returned, self.model) expected_limit = req_ref.pop() for attr in expected_limit: self.assertEqual( getattr(returned, attr), expected_limit[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs([expected_limit]) def test_list_filter_by_service(self): service_id = uuid.uuid4().hex expected_query = {'service_id': service_id} self.test_list(expected_query=expected_query, service=service_id) def test_list_filter_resource_name(self): resource_name = uuid.uuid4().hex self.test_list(resource_name=resource_name) def test_list_filter_region(self): region_id = uuid.uuid4().hex expected_query = {'region_id': region_id} self.test_list(expected_query=expected_query, region=region_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_role_assignments.py0000664000175000017500000003065100000000000030711 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 keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import role_assignments class RoleAssignmentsTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RoleAssignmentsTests, self).setUp() self.key = 'role_assignment' self.collection_key = 'role_assignments' self.model = role_assignments.RoleAssignment self.manager = self.client.role_assignments self.TEST_USER_SYSTEM_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'system': { 'all': True } }, 'user': { 'id': self.TEST_USER_ID } }] self.TEST_GROUP_SYSTEM_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'system': { 'all': True } }, 'group': { 'id': self.TEST_GROUP_ID } }] self.TEST_USER_DOMAIN_LIST = [{ 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'domain': { 'id': self.TEST_DOMAIN_ID } }, 'user': { 'id': self.TEST_USER_ID } }] self.TEST_GROUP_PROJECT_LIST = [{ 'group': { 'id': self.TEST_GROUP_ID }, 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'project': { 'id': self.TEST_TENANT_ID } } }] self.TEST_USER_PROJECT_LIST = [{ 'user': { 'id': self.TEST_USER_ID }, 'role': { 'id': self.TEST_ROLE_ID }, 'scope': { 'project': { 'id': self.TEST_TENANT_ID } } }] self.TEST_ALL_RESPONSE_LIST = (self.TEST_USER_PROJECT_LIST + self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_DOMAIN_LIST + self.TEST_USER_SYSTEM_LIST + self.TEST_GROUP_SYSTEM_LIST) def _assert_returned_list(self, ref_list, returned_list): self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_by_id(self): # It doesn't make sense to "list role assignments by ID" at all, given # that they don't have globally unique IDs in the first place. But # calling RoleAssignmentsManager.list(id=...) should still raise a # TypeError when given an unexpected keyword argument 'id', so we don't # actually have to modify the test in the superclass... I just wanted # to make a note here in case the superclass changes. super(RoleAssignmentsTests, self).test_list_by_id() def test_list_params(self): ref_list = self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s&user.id=%s' % (self.TEST_TENANT_ID, self.TEST_USER_ID)], entity=ref_list) returned_list = self.manager.list(user=self.TEST_USER_ID, project=self.TEST_TENANT_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID, 'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_all_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key], entity=ref_list) returned_list = self.manager.list() self._assert_returned_list(ref_list, returned_list) kwargs = {} self.assertQueryStringContains(**kwargs) def test_project_assignments_list(self): ref_list = self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s' % self.TEST_TENANT_ID], entity=ref_list) returned_list = self.manager.list(project=self.TEST_TENANT_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID} self.assertQueryStringContains(**kwargs) def test_project_assignments_list_include_subtree(self): ref_list = self.TEST_GROUP_PROJECT_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?scope.project.id=%s&include_subtree=True' % self.TEST_TENANT_ID], entity=ref_list) returned_list = self.manager.list(project=self.TEST_TENANT_ID, include_subtree=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.project.id': self.TEST_TENANT_ID, 'include_subtree': 'True'} self.assertQueryStringContains(**kwargs) def test_domain_assignments_list(self): ref_list = self.TEST_USER_DOMAIN_LIST self.stub_entity('GET', [self.collection_key, '?scope.domain.id=%s' % self.TEST_DOMAIN_ID], entity=ref_list) returned_list = self.manager.list(domain=self.TEST_DOMAIN_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.domain.id': self.TEST_DOMAIN_ID} self.assertQueryStringContains(**kwargs) def test_system_assignment_list(self): ref_list = self.TEST_USER_SYSTEM_LIST + self.TEST_GROUP_SYSTEM_LIST self.stub_entity('GET', [self.collection_key, '?scope.system=all'], entity=ref_list) returned_list = self.manager.list(system='all') self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all'} self.assertQueryStringContains(**kwargs) def test_system_assignment_list_for_user(self): ref_list = self.TEST_USER_SYSTEM_LIST self.stub_entity('GET', [self.collection_key, '?user.id=%s&scope.system=all' % self.TEST_USER_ID], entity=ref_list) returned_list = self.manager.list(system='all', user=self.TEST_USER_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all', 'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_system_assignment_list_for_group(self): ref_list = self.TEST_GROUP_SYSTEM_LIST self.stub_entity( 'GET', [self.collection_key, '?group.id=%s&scope.system=all' % self.TEST_GROUP_ID], entity=ref_list) returned_list = self.manager.list( system='all', group=self.TEST_GROUP_ID ) self._assert_returned_list(ref_list, returned_list) kwargs = {'scope.system': 'all', 'group.id': self.TEST_GROUP_ID} self.assertQueryStringContains(**kwargs) def test_group_assignments_list(self): ref_list = self.TEST_GROUP_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?group.id=%s' % self.TEST_GROUP_ID], entity=ref_list) returned_list = self.manager.list(group=self.TEST_GROUP_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'group.id': self.TEST_GROUP_ID} self.assertQueryStringContains(**kwargs) def test_user_assignments_list(self): ref_list = self.TEST_USER_DOMAIN_LIST + self.TEST_USER_PROJECT_LIST self.stub_entity('GET', [self.collection_key, '?user.id=%s' % self.TEST_USER_ID], entity=ref_list) returned_list = self.manager.list(user=self.TEST_USER_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'user.id': self.TEST_USER_ID} self.assertQueryStringContains(**kwargs) def test_effective_assignments_list(self): ref_list = self.TEST_USER_PROJECT_LIST + self.TEST_USER_DOMAIN_LIST self.stub_entity('GET', [self.collection_key, '?effective=True'], entity=ref_list) returned_list = self.manager.list(effective=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'effective': 'True'} self.assertQueryStringContains(**kwargs) def test_include_names_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?include_names=True'], entity=ref_list) returned_list = self.manager.list(include_names=True) self._assert_returned_list(ref_list, returned_list) kwargs = {'include_names': 'True'} self.assertQueryStringContains(**kwargs) def test_role_assignments_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?role.id=' + self.TEST_ROLE_ID], entity=ref_list) returned_list = self.manager.list(role=self.TEST_ROLE_ID) self._assert_returned_list(ref_list, returned_list) kwargs = {'role.id': self.TEST_ROLE_ID} self.assertQueryStringContains(**kwargs) def test_role_assignments_inherited_list(self): ref_list = self.TEST_ALL_RESPONSE_LIST self.stub_entity('GET', [self.collection_key, '?scope.OS-INHERIT:inherited_to=projects'], entity=ref_list ) returned_list = self.manager.list( os_inherit_extension_inherited_to='projects') self._assert_returned_list(ref_list, returned_list) query_string = 'scope.OS-INHERIT:inherited_to=projects' self.assertQueryStringIs(query_string) def test_domain_and_project_list(self): # Should only accept either domain or project, never both self.assertRaises(exceptions.ValidationError, self.manager.list, domain=self.TEST_DOMAIN_ID, project=self.TEST_TENANT_ID) def test_user_and_group_list(self): # Should only accept either user or group, never both self.assertRaises(exceptions.ValidationError, self.manager.list, user=self.TEST_USER_ID, group=self.TEST_GROUP_ID) def test_create(self): # Create not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) def test_update(self): # Update not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_delete(self): # Delete not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.delete) def test_get(self): # Get not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.get) def test_find(self): # Find not supported for role assignments self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_roles.py0000664000175000017500000007222100000000000026460 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 uuid from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import roles from testtools import matchers class RoleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(RoleTests, self).setUp() self.key = 'role' self.collection_key = 'roles' self.model = roles.Role self.manager = self.client.roles def new_ref(self, **kwargs): kwargs = super(RoleTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def _new_domain_ref(self, **kwargs): kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) return kwargs def test_create_with_domain_id(self): ref = self.new_ref() ref['domain_id'] = uuid.uuid4().hex self.test_create(ref=ref) def test_create_with_domain(self): ref = self.new_ref() domain_ref = self._new_domain_ref() domain_ref['id'] = uuid.uuid4().hex ref['domain_id'] = domain_ref['id'] self.stub_entity('POST', entity=ref, status_code=201) returned = self.manager.create(name=ref['name'], domain=domain_ref) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def test_domain_role_grant(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, user=user_id) def test_domain_role_grant_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_grant_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.grant(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_grant(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, group=group_id) def test_domain_group_role_grant_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=201) self.manager.grant(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_grant_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.grant(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_domain_role_list(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['domains', domain_id, 'users', user_id, self.collection_key], entity=ref_list) self.manager.list(domain=domain_id, user=user_id) def test_domain_role_list_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(domain=domain_id, user=user_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_project_user_role_list_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(project=project_id, user=user_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_domain_group_role_list(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['domains', domain_id, 'groups', group_id, self.collection_key], entity=ref_list) self.manager.list(domain=domain_id, group=group_id) def test_domain_group_role_list_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(domain=domain_id, group=group_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_project_group_role_list_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, 'inherited_to_projects'], entity=ref_list) returned_list = self.manager.list(project=project_id, group=group_id, os_inherit_extension_inherited=True) self.assertThat(ref_list, matchers.HasLength(len(returned_list))) [self.assertIsInstance(r, self.model) for r in returned_list] def test_domain_role_check(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, user=user_id) def test_domain_role_check_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_check_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_check(self): return group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, group=group_id) def test_domain_group_role_check_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_check_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.check(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_domain_role_revoke(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['domains', domain_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id) def test_domain_group_role_revoke(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['domains', domain_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, group=group_id) def test_domain_role_revoke_inherited(self): user_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'domains', domain_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id, os_inherit_extension_inherited=True) def test_project_role_revoke_inherited(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'projects', project_id, 'users', user_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, user=user_id, os_inherit_extension_inherited=True) def test_domain_group_role_revoke_inherited(self): group_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'domains', domain_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=200) self.manager.revoke(role=ref['id'], domain=domain_id, group=group_id, os_inherit_extension_inherited=True) def test_project_group_role_revoke_inherited(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['OS-INHERIT', 'projects', project_id, 'groups', group_id, self.collection_key, ref['id'], 'inherited_to_projects'], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, group=group_id, os_inherit_extension_inherited=True) def test_project_role_grant(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_grant(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=201) self.manager.grant(role=ref['id'], project=project_id, group=group_id) def test_project_role_list(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['projects', project_id, 'users', user_id, self.collection_key], entity=ref_list) self.manager.list(project=project_id, user=user_id) def test_project_group_role_list(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['projects', project_id, 'groups', group_id, self.collection_key], entity=ref_list) self.manager.list(project=project_id, group=group_id) def test_project_role_check(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=200) self.manager.check(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_check(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=200) self.manager.check(role=ref['id'], project=project_id, group=group_id) def test_project_role_revoke(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['projects', project_id, 'users', user_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, user=user_id) def test_project_group_role_revoke(self): group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['projects', project_id, 'groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.revoke(role=ref['id'], project=project_id, group=group_id) def test_domain_project_role_grant_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.grant, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_list_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.manager.list, domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_check_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.check, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_domain_project_role_revoke_fails(self): user_id = uuid.uuid4().hex project_id = uuid.uuid4().hex domain_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.revoke, role=ref['id'], domain=domain_id, project=project_id, user=user_id) def test_user_group_role_grant_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.grant, role=ref['id'], project=project_id, group=group_id, user=user_id) def test_user_group_role_list_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex self.assertRaises( exceptions.ValidationError, self.manager.list, project=project_id, group=group_id, user=user_id) def test_user_group_role_check_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.check, role=ref['id'], project=project_id, group=group_id, user=user_id) def test_user_group_role_revoke_fails(self): user_id = uuid.uuid4().hex group_id = uuid.uuid4().hex project_id = uuid.uuid4().hex ref = self.new_ref() self.assertRaises( exceptions.ValidationError, self.manager.revoke, role=ref['id'], project=project_id, group=group_id, user=user_id) class DeprecatedImpliedRoleTests(utils.ClientTestCase): def setUp(self): super(DeprecatedImpliedRoleTests, self).setUp() self.key = 'role' self.collection_key = 'roles' self.model = roles.Role self.manager = self.client.roles def test_implied_create(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('PUT', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=201) with self.deprecations.expect_deprecations_here(): manager_result = self.manager.create_implied(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) class ImpliedRoleTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ImpliedRoleTests, self).setUp() self.key = 'role_inference' self.collection_key = 'role_inferences' self.model = roles.InferenceRule self.manager = self.client.inference_rules def test_check(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('HEAD', ['roles', prior_role_id, 'implies', implied_role_id], status_code=204) result = self.manager.check(prior_role_id, implied_role_id) self.assertTrue(result) def test_get(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('GET', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=200) manager_result = self.manager.get(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_create(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": { "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }, "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } } } self.stub_url('PUT', ['roles', prior_id, 'implies', implied_id], json=mock_response, status_code=201) manager_result = self.manager.create(prior_id, implied_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_delete(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('DELETE', ['roles', prior_role_id, 'implies', implied_role_id], status_code=204) status, body = self.manager.delete(prior_role_id, implied_role_id) self.assertEqual(204, status.status_code) self.assertIsNone(body) def test_list_role_inferences(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inferences": [{ "implies": [{ "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }], "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } }] } self.stub_url('GET', ['role_inferences'], json=mock_response, status_code=200) manager_result = self.manager.list_inference_roles() self.assertEqual(1, len(manager_result)) self.assertIsInstance(manager_result[0], roles.InferenceRule) self.assertEqual(mock_response['role_inferences'][0]['implies'], manager_result[0].implies) self.assertEqual(mock_response['role_inferences'][0]['prior_role'], manager_result[0].prior_role) def test_list(self): prior_id = uuid.uuid4().hex prior_name = uuid.uuid4().hex implied_id = uuid.uuid4().hex implied_name = uuid.uuid4().hex mock_response = { "role_inference": { "implies": [{ "id": implied_id, "links": {"self": "http://host/v3/roles/%s" % implied_id}, "name": implied_name }], "prior_role": { "id": prior_id, "links": {"self": "http://host/v3/roles/%s" % prior_id}, "name": prior_name } }, "links": {"self": "http://host/v3/roles/%s/implies" % prior_id} } self.stub_url('GET', ['roles', prior_id, 'implies'], json=mock_response, status_code=200) manager_result = self.manager.list(prior_id) self.assertIsInstance(manager_result, roles.InferenceRule) self.assertEqual(1, len(manager_result.implies)) self.assertEqual(mock_response['role_inference']['implies'], manager_result.implies) self.assertEqual(mock_response['role_inference']['prior_role'], manager_result.prior_role) def test_update(self): # Update not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) def test_find(self): # Find not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) def test_put(self): # Put not supported for rule inferences self.assertRaises(exceptions.MethodNotImplemented, self.manager.put) def test_list_params(self): # Put not supported for rule inferences self.skipTest("list params not supported by rule inferences") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_service_catalog.py0000664000175000017500000003071200000000000030465 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 fixture from keystoneclient import access from keystoneclient import exceptions from keystoneclient.tests.unit import utils as test_utils from keystoneclient.tests.unit.v3 import client_fixtures from keystoneclient.tests.unit.v3 import utils class ServiceCatalogTest(utils.TestCase): def setUp(self): super(ServiceCatalogTest, self).setUp() self.AUTH_RESPONSE_BODY = client_fixtures.auth_response_body() self.RESPONSE = test_utils.test_response( headers=client_fixtures.AUTH_RESPONSE_HEADERS ) self.north_endpoints = {'public': 'http://glance.north.host/glanceapi/public', 'internal': 'http://glance.north.host/glanceapi/internal', 'admin': 'http://glance.north.host/glanceapi/admin'} self.south_endpoints = {'public': 'http://glance.south.host/glanceapi/public', 'internal': 'http://glance.south.host/glanceapi/internal', 'admin': 'http://glance.south.host/glanceapi/admin'} def test_building_a_service_catalog(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog self.assertEqual(sc.url_for(service_type='compute'), "https://compute.north.host/novapi/public") self.assertEqual(sc.url_for(service_type='compute', endpoint_type='internal'), "https://compute.north.host/novapi/internal") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='public') self.assertEqual(public_ep['compute'][0]['region'], 'North') self.assertEqual(public_ep['compute'][0]['url'], "https://compute.north.host/novapi/public") def test_service_catalog_regions(self): self.AUTH_RESPONSE_BODY['token']['region_name'] = "North" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='public') self.assertEqual(url, "http://glance.north.host/glanceapi/public") self.AUTH_RESPONSE_BODY['token']['region_name'] = "South" # Setting region_name on the catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', endpoint_type='internal') self.assertEqual(url, "http://glance.south.host/glanceapi/internal") def test_service_catalog_empty(self): self.AUTH_RESPONSE_BODY['token']['catalog'] = [] auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) self.assertRaises(exceptions.EmptyCatalog, auth_ref.service_catalog.url_for, service_type='image', endpoint_type='internalURL') def test_service_catalog_get_endpoints_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog endpoints = sc.get_endpoints(service_type='image', region_name='North') self.assertEqual(len(endpoints), 1) for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.north_endpoints[endpoint['interface']]) endpoints = sc.get_endpoints(service_type='image', region_name='South') self.assertEqual(len(endpoints), 1) for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.south_endpoints[endpoint['interface']]) endpoints = sc.get_endpoints(service_type='compute') self.assertEqual(len(endpoints['compute']), 3) endpoints = sc.get_endpoints(service_type='compute', region_name='North') self.assertEqual(len(endpoints['compute']), 3) endpoints = sc.get_endpoints(service_type='compute', region_name='West') self.assertEqual(len(endpoints['compute']), 0) def test_service_catalog_url_for_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image', region_name='North') self.assertEqual(url, self.north_endpoints['public']) url = sc.url_for(service_type='image', region_name='South') self.assertEqual(url, self.south_endpoints['public']) self.assertRaises(exceptions.EndpointNotFound, sc.url_for, service_type='image', region_name='West') def test_servcie_catalog_get_url_region_names(self): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog urls = sc.get_urls(service_type='image') self.assertEqual(len(urls), 2) urls = sc.get_urls(service_type='image', region_name='North') self.assertEqual(len(urls), 1) self.assertEqual(urls[0], self.north_endpoints['public']) urls = sc.get_urls(service_type='image', region_name='South') self.assertEqual(len(urls), 1) self.assertEqual(urls[0], self.south_endpoints['public']) urls = sc.get_urls(service_type='image', region_name='West') self.assertIsNone(urls) def test_service_catalog_param_overrides_body_region(self): self.AUTH_RESPONSE_BODY['token']['region_name'] = "North" # Passing region_name to service catalog is deprecated. with self.deprecations.expect_deprecations_here(): auth_ref = access.AccessInfo.factory(None, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_type='image') self.assertEqual(url, self.north_endpoints['public']) url = sc.url_for(service_type='image', region_name='South') self.assertEqual(url, self.south_endpoints['public']) endpoints = sc.get_endpoints(service_type='image') self.assertEqual(len(endpoints['image']), 3) for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.north_endpoints[endpoint['interface']]) endpoints = sc.get_endpoints(service_type='image', region_name='South') self.assertEqual(len(endpoints['image']), 3) for endpoint in endpoints['image']: self.assertEqual(endpoint['url'], self.south_endpoints[endpoint['interface']]) def test_service_catalog_service_name(self): auth_ref = access.AccessInfo.factory(resp=None, body=self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog url = sc.url_for(service_name='glance', endpoint_type='public', service_type='image', region_name='North') self.assertEqual('http://glance.north.host/glanceapi/public', url) url = sc.url_for(service_name='glance', endpoint_type='public', service_type='image', region_name='South') self.assertEqual('http://glance.south.host/glanceapi/public', url) self.assertRaises(exceptions.EndpointNotFound, sc.url_for, service_name='glance', service_type='compute') urls = sc.get_urls(service_type='image', service_name='glance', endpoint_type='public') self.assertIn('http://glance.north.host/glanceapi/public', urls) self.assertIn('http://glance.south.host/glanceapi/public', urls) urls = sc.get_urls(service_type='image', service_name='Servers', endpoint_type='public') self.assertIsNone(urls) def test_service_catalog_without_name(self): pr_auth_ref = access.AccessInfo.factory( resp=None, body=client_fixtures.project_scoped_token()) pr_sc = pr_auth_ref.service_catalog # this will work because there are no service names on that token url_ref = 'http://public.com:8774/v2/225da22d3ce34b15877ea70b2a575f58' url = pr_sc.url_for(service_type='compute', service_name='NotExist', endpoint_type='public') self.assertEqual(url_ref, url) ab_auth_ref = access.AccessInfo.factory(resp=None, body=self.AUTH_RESPONSE_BODY) ab_sc = ab_auth_ref.service_catalog # this won't work because there is a name and it's not this one self.assertRaises(exceptions.EndpointNotFound, ab_sc.url_for, service_type='compute', service_name='NotExist', endpoint_type='public') class ServiceCatalogV3Test(ServiceCatalogTest): def test_building_a_service_catalog(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog self.assertEqual(sc.url_for(service_type='compute'), 'https://compute.north.host/novapi/public') self.assertEqual(sc.url_for(service_type='compute', endpoint_type='internal'), 'https://compute.north.host/novapi/internal') self.assertRaises(exceptions.EndpointNotFound, sc.url_for, 'region_id', 'South', service_type='compute') def test_service_catalog_endpoints(self): auth_ref = access.AccessInfo.factory(self.RESPONSE, self.AUTH_RESPONSE_BODY) sc = auth_ref.service_catalog public_ep = sc.get_endpoints(service_type='compute', endpoint_type='public') self.assertEqual(public_ep['compute'][0]['region_id'], 'North') self.assertEqual(public_ep['compute'][0]['url'], 'https://compute.north.host/novapi/public') def test_service_catalog_multiple_service_types(self): token = fixture.V3Token() token.set_project_scope() for i in range(3): s = token.add_service('compute') s.add_standard_endpoints(public='public-%d' % i, admin='admin-%d' % i, internal='internal-%d' % i, region='region-%d' % i) auth_ref = access.AccessInfo.factory(resp=None, body=token) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='public') self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) urls = auth_ref.service_catalog.get_urls(service_type='compute', endpoint_type='public', region_name='region-1') self.assertEqual(('public-1', ), urls) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_services.py0000664000175000017500000000325300000000000027156 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 keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import services class ServiceTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(ServiceTests, self).setUp() self.key = 'service' self.collection_key = 'services' self.model = services.Service self.manager = self.client.services def new_ref(self, **kwargs): kwargs = super(ServiceTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('type', uuid.uuid4().hex) kwargs.setdefault('enabled', True) return kwargs def test_list_filter_name(self): filter_name = uuid.uuid4().hex expected_query = {'name': filter_name} super(ServiceTests, self).test_list(expected_query=expected_query, name=filter_name) def test_list_filter_type(self): filter_type = uuid.uuid4().hex expected_query = {'type': filter_type} super(ServiceTests, self).test_list(expected_query=expected_query, type=filter_type) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_simple_cert.py0000664000175000017500000000554300000000000027645 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. import fixtures import testresources from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib import simple_cert class SimpleCertTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_get_ca_certificate(self): self.stub_url('GET', ['OS-SIMPLE-CERT', 'ca'], headers={'Content-Type': 'application/x-pem-file'}, text=self.examples.SIGNING_CA) res = self.client.simple_cert.get_ca_certificates() self.assertEqual(self.examples.SIGNING_CA, res) def test_get_certificates(self): self.stub_url('GET', ['OS-SIMPLE-CERT', 'certificates'], headers={'Content-Type': 'application/x-pem-file'}, text=self.examples.SIGNING_CERT) res = self.client.simple_cert.get_certificates() self.assertEqual(self.examples.SIGNING_CERT, res) class SimpleCertRequestIdTests(utils.TestRequestId): def setUp(self): super(SimpleCertRequestIdTests, self).setUp() self.mgr = simple_cert.SimpleCertManager(self.client) def _mock_request_method(self, method=None, body=None): return self.useFixture(fixtures.MockPatchObject( self.client, method, autospec=True, return_value=(self.resp, body)) ).mock def test_list_ca_certificates(self): body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get_ca_certificates() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( '/OS-SIMPLE-CERT/ca', authenticated=False) def test_list_certificates(self): body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]} get_mock = self._mock_request_method(method='get', body=body) response = self.mgr.get_certificates() self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID) get_mock.assert_called_once_with( '/OS-SIMPLE-CERT/certificates', authenticated=False) def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_tokens.py0000664000175000017500000001466200000000000026644 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 exceptions import testresources from keystoneclient import access from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit.v3 import utils class TokenTests(utils.ClientTestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] def test_revoke_token_with_token_id(self): token_id = uuid.uuid4().hex self.stub_url('DELETE', ['/auth/tokens'], status_code=204) self.client.tokens.revoke_token(token_id) self.assertRequestHeaderEqual('X-Subject-Token', token_id) def test_revoke_token_with_access_info_instance(self): token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] token = access.AccessInfoV3(token_id, token_ref['token']) self.stub_url('DELETE', ['/auth/tokens'], status_code=204) self.client.tokens.revoke_token(token) self.assertRequestHeaderEqual('X-Subject-Token', token_id) def test_get_revoked(self): sample_revoked_response = {'signed': '-----BEGIN CMS-----\nMIIB...'} self.stub_url('GET', ['auth', 'tokens', 'OS-PKI', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked() self.assertQueryStringIs() self.assertEqual(sample_revoked_response, resp) def test_get_revoked_audit_id_only(self): # When get_revoked(audit_id_only=True) then ?audit_id_only is set on # the request. sample_revoked_response = { 'revoked': [ { 'audit_id': uuid.uuid4().hex, 'expires': '2016-01-21T15:53:52Z', }, ], } self.stub_url('GET', ['auth', 'tokens', 'OS-PKI', 'revoked'], json=sample_revoked_response) resp = self.client.tokens.get_revoked(audit_id_only=True) self.assertQueryStringIs('audit_id_only') self.assertEqual(sample_revoked_response, resp) def test_validate_token_with_token_id(self): # Can validate a token passing a string token ID. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertEqual(token_data, token_ref) access_info = self.client.tokens.validate(token_id) self.assertRequestHeaderEqual('X-Subject-Token', token_id) self.assertIsInstance(access_info, access.AccessInfoV3) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_with_access_info(self): # Can validate a token passing an access info. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] token = access.AccessInfoV3(token_id, token_ref['token']) self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) access_info = self.client.tokens.validate(token) self.assertRequestHeaderEqual('X-Subject-Token', token_id) self.assertIsInstance(access_info, access.AccessInfoV3) self.assertEqual(token_id, access_info.auth_token) def test_validate_token_invalid(self): # When the token is invalid the server typically returns a 404. token_id = uuid.uuid4().hex self.stub_url('GET', ['auth', 'tokens'], status_code=404) self.assertRaises(exceptions.NotFound, self.client.tokens.get_token_data, token_id) self.assertRaises(exceptions.NotFound, self.client.tokens.validate, token_id) def test_validate_token_catalog(self): # Can validate a token and a catalog is requested by default. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_DEFAULT] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertQueryStringIs() self.assertIn('catalog', token_data['token']) access_info = self.client.tokens.validate(token_id) self.assertQueryStringIs() self.assertTrue(access_info.has_service_catalog()) def test_validate_token_nocatalog(self): # Can validate a token and request no catalog. token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_UNSCOPED] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) token_data = self.client.tokens.get_token_data(token_id) self.assertQueryStringIs() self.assertNotIn('catalog', token_data['token']) access_info = self.client.tokens.validate(token_id, include_catalog=False) self.assertQueryStringIs('nocatalog') self.assertFalse(access_info.has_service_catalog()) def test_validate_token_allow_expired(self): token_id = uuid.uuid4().hex token_ref = self.examples.TOKEN_RESPONSES[ self.examples.v3_UUID_TOKEN_UNSCOPED] self.stub_url('GET', ['auth', 'tokens'], headers={'X-Subject-Token': token_id, }, json=token_ref) self.client.tokens.validate(token_id) self.assertQueryStringIs() self.client.tokens.validate(token_id, allow_expired=True) self.assertQueryStringIs('allow_expired=1') def load_tests(loader, tests, pattern): return testresources.OptimisingTestSuite(tests) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_trusts.py0000664000175000017500000001164400000000000026702 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 oslo_utils import timeutils from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib import trusts class TrustTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(TrustTests, self).setUp() self.key = 'trust' self.collection_key = 'trusts' self.model = trusts.Trust self.manager = self.client.trusts self.path_prefix = 'OS-TRUST' def new_ref(self, **kwargs): kwargs = super(TrustTests, self).new_ref(**kwargs) kwargs.setdefault('project_id', uuid.uuid4().hex) return kwargs def test_create(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False super(TrustTests, self).test_create(ref=ref) def test_create_limited_uses(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False ref['remaining_uses'] = 5 super(TrustTests, self).test_create(ref=ref) def test_create_roles(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a list of role_names, and converts # internally to the slightly odd list-of-dict API format, so we # have to pass the expected request data to allow correct stubbing ref['role_names'] = ['atestrole'] req_ref['roles'] = [{'name': 'atestrole'}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_role_id_and_names(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a list of role_names, and converts # internally to the slightly odd list-of-dict API format, so we # have to pass the expected request data to allow correct stubbing ref['role_names'] = ['atestrole'] ref['role_ids'] = [uuid.uuid4().hex] req_ref['roles'] = [{'name': 'atestrole'}, {'id': ref['role_ids'][0]}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_expires(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = False ref['expires_at'] = timeutils.parse_isotime( '2013-03-04T12:00:01.000000Z') req_ref = ref.copy() req_ref.pop('id') # Note the TrustManager takes a datetime.datetime object for # expires_at, and converts it internally into an iso format datestamp req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z' super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_create_imp(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = True super(TrustTests, self).test_create(ref=ref) def test_create_roles_imp(self): ref = self.new_ref() ref['trustor_user_id'] = uuid.uuid4().hex ref['trustee_user_id'] = uuid.uuid4().hex ref['impersonation'] = True req_ref = ref.copy() req_ref.pop('id') ref['role_names'] = ['atestrole'] req_ref['roles'] = [{'name': 'atestrole'}] super(TrustTests, self).test_create(ref=ref, req_ref=req_ref) def test_list_filter_trustor(self): expected_query = {'trustor_user_id': '12345'} super(TrustTests, self).test_list(expected_query=expected_query, trustor_user='12345') def test_list_filter_trustee(self): expected_query = {'trustee_user_id': '12345'} super(TrustTests, self).test_list(expected_query=expected_query, trustee_user='12345') def test_update(self): # Update not supported for the OS-TRUST API self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/test_users.py0000664000175000017500000002655600000000000026507 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 uuid from keystoneclient import exceptions from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3 import users class UserTests(utils.ClientTestCase, utils.CrudTests): def setUp(self): super(UserTests, self).setUp() self.key = 'user' self.collection_key = 'users' self.model = users.User self.manager = self.client.users def new_ref(self, **kwargs): kwargs = super(UserTests, self).new_ref(**kwargs) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('domain_id', uuid.uuid4().hex) kwargs.setdefault('enabled', True) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('default_project_id', uuid.uuid4().hex) return kwargs def test_add_user_to_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('PUT', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.add_to_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.remove_from_group, user=ref['id'], group=None) def test_list_users_in_group(self): group_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', ['groups', group_id, self.collection_key], entity=ref_list) returned_list = self.manager.list(group=group_id) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] def test_check_user_in_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('HEAD', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.check_in_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.check_in_group, user=ref['id'], group=None) def test_remove_user_from_group(self): group_id = uuid.uuid4().hex ref = self.new_ref() self.stub_url('DELETE', ['groups', group_id, self.collection_key, ref['id']], status_code=204) self.manager.remove_from_group(user=ref['id'], group=group_id) self.assertRaises(exceptions.ValidationError, self.manager.remove_from_group, user=ref['id'], group=None) def test_create_doesnt_log_password(self): password = uuid.uuid4().hex ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() param_ref['password'] = password params = utils.parameterize(param_ref) self.manager.create(**params) self.assertNotIn(password, self.logger.output) def test_create_with_project(self): # Can create a user with the deprecated project option rather than # default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() # Use deprecated project_id rather than new default_project_id. param_ref['project_id'] = param_ref.pop('default_project_id') params = utils.parameterize(param_ref) returned = self.manager.create(**params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_create_with_project_and_default_project(self): # Can create a user with the deprecated project and default_project_id. # The backend call should only pass the default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() self.stub_entity('POST', [self.collection_key], status_code=201, entity=ref) req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() # Add the deprecated project_id in the call, the value will be ignored. param_ref['project_id'] = 'project' params = utils.parameterize(param_ref) returned = self.manager.create(**params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_doesnt_log_password(self): password = uuid.uuid4().hex ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) param_ref['password'] = password params = utils.parameterize(param_ref) self.manager.update(ref['id'], **params) self.assertNotIn(password, self.logger.output) def test_update_with_project(self): # Can update a user with the deprecated project option rather than # default_project_id. self.deprecations.expect_deprecations() ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) # Use deprecated project_id rather than new default_project_id. param_ref['project_id'] = param_ref.pop('default_project_id') params = utils.parameterize(param_ref) returned = self.manager.update(ref['id'], **params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_with_project_and_default_project(self, ref=None): self.deprecations.expect_deprecations() ref = self.new_ref() req_ref = ref.copy() req_ref.pop('id') param_ref = req_ref.copy() self.stub_entity('PATCH', [self.collection_key, ref['id']], status_code=200, entity=ref) # Add the deprecated project_id in the call, the value will be ignored. param_ref['project_id'] = 'project' params = utils.parameterize(param_ref) returned = self.manager.update(ref['id'], **params) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_update_password(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex self.stub_url('POST', [self.collection_key, self.TEST_USER_ID, 'password']) self.client.user_id = self.TEST_USER_ID self.manager.update_password(old_password, new_password) exp_req_body = { 'user': { 'password': new_password, 'original_password': old_password } } self.assertEqual( '%s/users/%s/password' % (self.TEST_URL, self.TEST_USER_ID), self.requests_mock.last_request.url) self.assertRequestBodyIs(json=exp_req_body) self.assertNotIn(old_password, self.logger.output) self.assertNotIn(new_password, self.logger.output) def test_update_password_with_no_hardcoded_endpoint_filter(self): # test to ensure the 'endpoint_filter' parameter is not being # passed from the manager. Endpoint filtering should be done at # the Session, not the individual managers. old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex expected_params = {'user': {'password': new_password, 'original_password': old_password}} user_password_update_path = '/users/%s/password' % self.TEST_USER_ID self.client.user_id = self.TEST_USER_ID # NOTE(gyee): user manager subclass keystoneclient.base.Manager # and utilize the _update() method in the base class to interface # with the client session to perform the update. In the case, we # just need to make sure the 'endpoint_filter' parameter is not # there. with mock.patch('keystoneclient.base.Manager._update') as m: self.manager.update_password(old_password, new_password) m.assert_called_with(user_password_update_path, expected_params, method='POST', log=False) def test_update_password_with_bad_inputs(self): old_password = uuid.uuid4().hex new_password = uuid.uuid4().hex # users can't unset their password self.assertRaises(exceptions.ValidationError, self.manager.update_password, old_password, None) self.assertRaises(exceptions.ValidationError, self.manager.update_password, old_password, '') # users can't start with empty passwords self.assertRaises(exceptions.ValidationError, self.manager.update_password, None, new_password) self.assertRaises(exceptions.ValidationError, self.manager.update_password, '', new_password) # this wouldn't result in any change anyway self.assertRaises(exceptions.ValidationError, self.manager.update_password, None, None) self.assertRaises(exceptions.ValidationError, self.manager.update_password, '', '') password = uuid.uuid4().hex self.assertRaises(exceptions.ValidationError, self.manager.update_password, password, password) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/tests/unit/v3/utils.py0000664000175000017500000003161300000000000025435 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 requests import uuid from urllib import parse as urlparse from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.tests.unit import client_fixtures from keystoneclient.tests.unit import utils from keystoneclient.v3 import client def parameterize(ref): """Rewrite attributes to match the kwarg naming convention in client. >>> parameterize({'project_id': 0}) {'project': 0} """ params = ref.copy() for key in ref: if key[-3:] == '_id': params.setdefault(key[:-3], params.pop(key)) return params class UnauthenticatedTestCase(utils.TestCase): """Class used as base for unauthenticated calls.""" TEST_ROOT_URL = 'http://127.0.0.1:5000/' TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3') class TestCase(UnauthenticatedTestCase): TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v3" TEST_PUBLIC_IDENTITY_ENDPOINT = "http://127.0.0.1:5000/v3" TEST_SERVICE_CATALOG = [{ "endpoints": [{ "url": "http://cdn.admin-nets.local:8774/v1.0/", "region": "RegionOne", "interface": "public" }, { "url": "http://127.0.0.1:8774/v1.0", "region": "RegionOne", "interface": "internal" }, { "url": "http://cdn.admin-nets.local:8774/v1.0", "region": "RegionOne", "interface": "admin" }], "type": "nova_compat" }, { "endpoints": [{ "url": "http://nova/novapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://nova/novapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://nova/novapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "compute" }, { "endpoints": [{ "url": "http://glance/glanceapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://glance/glanceapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://glance/glanceapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "image", "name": "glance" }, { "endpoints": [{ "url": "http://127.0.0.1:5000/v3", "region": "RegionOne", "interface": "public" }, { "url": "http://127.0.0.1:5000/v3", "region": "RegionOne", "interface": "internal" }, { "url": TEST_ADMIN_IDENTITY_ENDPOINT, "region": "RegionOne", "interface": "admin" }], "type": "identity" }, { "endpoints": [{ "url": "http://swift/swiftapi/public", "region": "RegionOne", "interface": "public" }, { "url": "http://swift/swiftapi/internal", "region": "RegionOne", "interface": "internal" }, { "url": "http://swift/swiftapi/admin", "region": "RegionOne", "interface": "admin" }], "type": "object-store" }] def stub_auth(self, subject_token=None, **kwargs): if not subject_token: subject_token = self.TEST_TOKEN try: response_list = kwargs['response_list'] except KeyError: headers = kwargs.setdefault('headers', {}) headers['X-Subject-Token'] = subject_token else: for resp in response_list: headers = resp.setdefault('headers', {}) headers['X-Subject-Token'] = subject_token self.stub_url('POST', ['auth', 'tokens'], **kwargs) class ClientTestCase(utils.ClientTestCaseMixin, TestCase): ORIGINAL_CLIENT_TYPE = 'original' KSC_SESSION_CLIENT_TYPE = 'ksc-session' KSA_SESSION_CLIENT_TYPE = 'ksa-session' scenarios = [ ( ORIGINAL_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.OriginalV3, 'client_type': ORIGINAL_CLIENT_TYPE } ), ( KSC_SESSION_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.KscSessionV3, 'client_type': KSC_SESSION_CLIENT_TYPE } ), ( KSA_SESSION_CLIENT_TYPE, { 'client_fixture_class': client_fixtures.KsaSessionV3, 'client_type': KSA_SESSION_CLIENT_TYPE } ) ] @property def is_original_client(self): return self.client_type == self.ORIGINAL_CLIENT_TYPE @property def is_session_client(self): return self.client_type in (self.KSC_SESSION_CLIENT_TYPE, self.KSA_SESSION_CLIENT_TYPE) class CrudTests(object): key = None collection_key = None model = None manager = None path_prefix = None def new_ref(self, **kwargs): kwargs.setdefault('id', uuid.uuid4().hex) kwargs.setdefault(uuid.uuid4().hex, uuid.uuid4().hex) return kwargs def encode(self, entity): if isinstance(entity, dict): return {self.key: entity} if isinstance(entity, list): return {self.collection_key: entity} raise NotImplementedError('Are you sure you want to encode that?') def stub_entity(self, method, parts=None, entity=None, id=None, **kwargs): if entity: entity = self.encode(entity) kwargs['json'] = entity if not parts: parts = [self.collection_key] if self.path_prefix: parts.insert(0, self.path_prefix) if id: if not parts: parts = [] parts.append(id) self.stub_url(method, parts=parts, **kwargs) def assertEntityRequestBodyIs(self, entity): self.assertRequestBodyIs(json=self.encode(entity)) def test_create(self, ref=None, req_ref=None): deprecations = self.useFixture(client_fixtures.Deprecations()) deprecations.expect_deprecations() ref = ref or self.new_ref() manager_ref = ref.copy() manager_ref.pop('id') # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) if req_ref: req_ref = req_ref.copy() else: req_ref = ref.copy() req_ref.pop('id') self.stub_entity('POST', entity=req_ref, status_code=201) returned = self.manager.create(**parameterize(manager_ref)) self.assertIsInstance(returned, self.model) for attr in req_ref: self.assertEqual( getattr(returned, attr), req_ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) # The entity created here may be used in other test cases return returned def test_get(self, ref=None): ref = ref or self.new_ref() self.stub_entity('GET', id=ref['id'], entity=ref) returned = self.manager.get(ref['id']) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) def _get_expected_path(self, expected_path=None): if not expected_path: if self.path_prefix: expected_path = 'v3/%s/%s' % (self.path_prefix, self.collection_key) else: expected_path = 'v3/%s' % self.collection_key return expected_path def test_list_by_id(self, ref=None, **filter_kwargs): """Test ``entities.list(id=x)`` being rewritten as ``GET /v3/entities/x``. # noqa This tests an edge case of each manager's list() implementation, to ensure that it "does the right thing" when users call ``.list()`` when they should have used ``.get()``. """ if 'id' not in filter_kwargs: ref = ref or self.new_ref() filter_kwargs['id'] = ref['id'] self.assertRaises(TypeError, self.manager.list, **filter_kwargs) def test_list(self, ref_list=None, expected_path=None, expected_query=None, **filter_kwargs): ref_list = ref_list or [self.new_ref(), self.new_ref()] expected_path = self._get_expected_path(expected_path) self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path), json=self.encode(ref_list)) returned_list = self.manager.list(**filter_kwargs) self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] qs_args = self.requests_mock.last_request.qs qs_args_expected = expected_query or filter_kwargs for key, value in qs_args_expected.items(): self.assertIn(key, qs_args) # The querystring value is a list. Note we convert the value to a # string and lower, as the query string is always a string and the # filter_kwargs may contain non-string values, for example a # boolean, causing the comaprison to fail. self.assertIn(str(value).lower(), qs_args[key]) # Also check that no query string args exist which are not expected for key in qs_args: self.assertIn(key, qs_args_expected) def test_list_params(self): ref_list = [self.new_ref()] filter_kwargs = {uuid.uuid4().hex: uuid.uuid4().hex} expected_path = self._get_expected_path() self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path), json=self.encode(ref_list)) self.manager.list(**filter_kwargs) self.assertQueryStringContains(**filter_kwargs) def test_find(self, ref=None): ref = ref or self.new_ref() ref_list = [ref] self.stub_entity('GET', entity=ref_list) returned = self.manager.find(name=getattr(ref, 'name', None)) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) if hasattr(ref, 'name'): self.assertQueryStringIs('name=%s' % ref['name']) else: self.assertQueryStringIs('') def test_update(self, ref=None, req_ref=None): deprecations = self.useFixture(client_fixtures.Deprecations()) deprecations.expect_deprecations() ref = ref or self.new_ref() self.stub_entity('PATCH', id=ref['id'], entity=ref) # req_ref argument allows you to specify a different # signature for the request when the manager does some # conversion before doing the request (e.g. converting # from datetime object to timestamp string) if req_ref: req_ref = req_ref.copy() else: req_ref = ref.copy() req_ref.pop('id') returned = self.manager.update(ref['id'], **parameterize(req_ref)) self.assertIsInstance(returned, self.model) for attr in ref: self.assertEqual( getattr(returned, attr), ref[attr], 'Expected different %s' % attr) self.assertEntityRequestBodyIs(req_ref) def test_delete(self, ref=None): ref = ref or self.new_ref() self.stub_entity('DELETE', id=ref['id'], status_code=204) self.manager.delete(ref['id']) class TestRequestId(TestCase): resp = requests.Response() TEST_REQUEST_ID = uuid.uuid4().hex resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID def setUp(self): super(TestRequestId, self).setUp() auth = v3.Token(auth_url='http://127.0.0.1:5000', token=self.TEST_TOKEN) session_ = session.Session(auth=auth) self.client = client.Client(session=session_, include_metadata='True')._adapter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/utils.py0000664000175000017500000001025100000000000022757 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 getpass import hashlib import sys from keystoneauth1 import exceptions as ksa_exceptions from oslo_utils import timeutils from keystoneclient import exceptions as ksc_exceptions def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try the entity as a string try: return manager.get(name_or_id) except (ksa_exceptions.NotFound): # nosec(cjschaef): try to find # 'name_or_id' as a bytes instead pass # finally try to find entity by name try: if isinstance(name_or_id, bytes): name_or_id = name_or_id.decode('utf-8', 'strict') return manager.find(name=name_or_id) except ksa_exceptions.NotFound: msg = ("No %s with a name or ID of '%s' exists." % (manager.resource_class.__name__.lower(), name_or_id)) raise ksc_exceptions.CommandError(msg) except ksc_exceptions.NoUniqueMatch: msg = ("Multiple %s matches found for '%s', use an ID to be more" " specific." % (manager.resource_class.__name__.lower(), name_or_id)) raise ksc_exceptions.CommandError(msg) def hash_signed_token(signed_text, mode='md5'): hash_ = hashlib.new(mode) hash_.update(signed_text) return hash_.hexdigest() def prompt_user_password(): """Prompt user for a password. Prompt for a password if stdin is a tty. """ password = None # If stdin is a tty, try prompting for the password if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: password = getpass.getpass('Password: ') except EOFError: # nosec(cjschaef): return password, which is None if # password was not found pass return password def prompt_for_password(): """Prompt user for password if not provided. Prompt is used so the password doesn't show up in the bash history. """ if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): # nothing to do return while True: try: new_passwd = getpass.getpass('New Password: ') rep_passwd = getpass.getpass('Repeat New Password: ') if new_passwd == rep_passwd: return new_passwd except EOFError: return _ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' _ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' def isotime(at=None, subsecond=False): """Stringify time in ISO 8601 format.""" # Python provides a similar instance method for datetime.datetime objects # called isoformat(). The format of the strings generated by isoformat() # have a couple of problems: # 1) The strings generated by isotime are used in tokens and other public # APIs that we can't change without a deprecation period. The strings # generated by isoformat are not the same format, so we can't just # change to it. # 2) The strings generated by isoformat do not include the microseconds if # the value happens to be 0. This will likely show up as random failures # as parsers may be written to always expect microseconds, and it will # parse correctly most of the time. if not at: at = timeutils.utcnow() st = at.strftime(_ISO8601_TIME_FORMAT if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' st += ('Z' if (tz == 'UTC' or tz == 'UTC+00:00') else tz) return st def strtime(at=None): at = at or timeutils.utcnow() return at.strftime(timeutils.PERFECT_TIME_FORMAT) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2470036 python_keystoneclient-5.6.0/keystoneclient/v2_0/0000775000175000017500000000000000000000000022014 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/__init__.py0000664000175000017500000000012400000000000024122 0ustar00zuulzuul00000000000000from keystoneclient.v2_0.client import Client # noqa __all__ = ( 'client', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/certificates.py0000664000175000017500000000233400000000000025035 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. class CertificatesManager(object): """Manager for certificates.""" def __init__(self, client): self._client = client def get_ca_certificate(self): """Get CA certificate. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/certificates/ca', authenticated=False) return resp.text def get_signing_certificate(self): """Get signing certificate. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/certificates/signing', authenticated=False) return resp.text ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/client.py0000664000175000017500000002337000000000000023651 0ustar00zuulzuul00000000000000# Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import warnings from keystoneclient.auth.identity import v2 as v2_auth from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ from keystoneclient.v2_0 import certificates from keystoneclient.v2_0 import ec2 from keystoneclient.v2_0 import endpoints from keystoneclient.v2_0 import extensions from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import services from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import tokens from keystoneclient.v2_0 import users _logger = logging.getLogger(__name__) class Client(httpclient.HTTPClient): """Client for the OpenStack Keystone v2.0 API. :param string username: Username for authentication. (optional) :param string password: Password for authentication. (optional) :param string token: Token for authentication. (optional) :param string tenant_id: Tenant id. (optional) :param string tenant_name: Tenant name. (optional) :param string auth_url: Keystone service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param string endpoint: A user-supplied endpoint URL for the keystone service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation.(optional) :param integer timeout: Allows customization of the timeout for client http requests. (optional) :param string original_ip: The original IP of the requesting user which will be sent to Keystone in a 'Forwarded' header. (optional) :param string cert: Path to the Privacy Enhanced Mail (PEM) file which contains the corresponding X.509 client certificate needed to established two-way SSL connection with the identity service. (optional) :param string key: Path to the Privacy Enhanced Mail (PEM) file which contains the unencrypted client private key needed to established two-way SSL connection with the identity service. (optional) :param string cacert: Path to the Privacy Enhanced Mail (PEM) file which contains the trusted authority X.509 certificates needed to established SSL connection with the identity service. (optional) :param boolean insecure: Does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) :param dict auth_ref: To allow for consumers of the client to manage their own caching strategy, you may initialize a client with a previously captured auth_reference (token) :param boolean debug: Enables debug logging of all request and responses to keystone. default False (option) .. warning:: If debug is enabled, it may show passwords in plain text as a part of its output. .. warning:: Constructing an instance of this class without a session is deprecated as of the 1.7.0 release and will be removed in the 2.0.0 release. The client can be created and used like a user or in a strictly bootstrap mode. Normal operation expects a username, password, auth_url, and tenant_name or id to be provided. Other values will be lazily loaded as needed from the service catalog. Example:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Password(auth_url=KEYSTONE_URL, ... username=USER, ... password=PASS, ... tenant_name=TENANT_NAME) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.tenants.list() ... >>> user = keystone.users.get(USER_ID) >>> user.delete() Once authenticated, you can store and attempt to re-use the authenticated token. the auth_ref property on the client returns as a dictionary-like-object so that you can export and cache it, re-using it when initiating another client:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Password(auth_url=KEYSTONE_URL, ... username=USER, ... password=PASS, ... tenant_name=TENANT_NAME) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> auth_ref = keystone.auth_ref >>> # pickle or whatever you like here >>> new_client = client.Client(auth_ref=auth_ref) Alternatively, you can provide the administrative token configured in keystone and an endpoint to communicate with directly. See (``admin_token`` in ``keystone.conf``) In this case, authenticate() is not needed, and no service catalog will be loaded. Example:: >>> from keystoneauth1.identity import v2 >>> from keystoneauth1 import session >>> from keystoneclient.v2_0 import client >>> auth = v2.Token(auth_url='http://localhost:35357/v2.0', ... token='12345secret7890') >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.tenants.list() """ version = 'v2.0' def __init__(self, **kwargs): """Initialize a new client for the Keystone v2.0 API.""" if not kwargs.get('session'): warnings.warn( 'Constructing an instance of the ' 'keystoneclient.v2_0.client.Client class without a session is ' 'deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) super(Client, self).__init__(**kwargs) self.certificates = certificates.CertificatesManager(self._adapter) self.endpoints = endpoints.EndpointManager(self._adapter) self.extensions = extensions.ExtensionManager(self._adapter) self.roles = roles.RoleManager(self._adapter) self.services = services.ServiceManager(self._adapter) self.tokens = tokens.TokenManager(self._adapter) self.users = users.UserManager(self._adapter, self.roles) self.tenants = tenants.TenantManager(self._adapter, self.roles, self.users) # extensions self.ec2 = ec2.CredentialsManager(self._adapter) # DEPRECATED: if session is passed then we go to the new behaviour of # authenticating on the first required call. if not kwargs.get('session') and self.management_url is None: self.authenticate() def get_raw_token_from_identity_service(self, auth_url, username=None, password=None, tenant_name=None, tenant_id=None, token=None, project_name=None, project_id=None, trust_id=None, **kwargs): """Authenticate against the v2 Identity API. If a token is provided it will be used in preference over username and password. :returns: access.AccessInfo if authentication was successful. :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token """ try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) new_kwargs = {'trust_id': trust_id, 'tenant_id': project_id or tenant_id, 'tenant_name': project_name or tenant_name} if token: plugin = v2_auth.Token(auth_url, token, **new_kwargs) elif username and password: plugin = v2_auth.Password(auth_url, username, password, **new_kwargs) else: msg = _('A username and password or token is required.') raise exceptions.AuthorizationFailure(msg) return plugin.get_auth_ref(self.session) except (exceptions.AuthorizationFailure, exceptions.Unauthorized): _logger.debug("Authorization Failed.") raise except exceptions.EndpointNotFound: msg = ( _('There was no suitable authentication url for this request')) raise exceptions.AuthorizationFailure(msg) except Exception as e: raise exceptions.AuthorizationFailure( _("Authorization Failed: %s") % e) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/ec2.py0000664000175000017500000000400400000000000023035 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class EC2(base.Resource): def __repr__(self): """Return string representation of EC2 resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) class CredentialsManager(base.ManagerWithFind): resource_class = EC2 def create(self, user_id, tenant_id): """Create a new access/secret pair for the user/tenant pair. :rtype: object of type :class:`EC2` """ params = {'tenant_id': tenant_id} return self._post('/users/%s/credentials/OS-EC2' % user_id, params, "credential") def list(self, user_id): """Get a list of access/secret pairs for a user_id. :rtype: list of :class:`EC2` """ return self._list("/users/%s/credentials/OS-EC2" % user_id, "credentials") def get(self, user_id, access): """Get the access/secret pair for a given access key. :rtype: object of type :class:`EC2` """ return self._get("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access)), "credential") def delete(self, user_id, access): """Delete an access/secret pair for a user.""" return self._delete("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/endpoints.py0000664000175000017500000000326000000000000024372 0ustar00zuulzuul00000000000000# Copyright 2012 Canonical Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Endpoint(base.Resource): """Represents a Keystone endpoint.""" def __repr__(self): """Return string representation of endpoint resource information.""" return "" % self._info class EndpointManager(base.ManagerWithFind): """Manager class for manipulating Keystone endpoints.""" resource_class = Endpoint def list(self): """List all available endpoints.""" return self._list('/endpoints', 'endpoints') def create(self, region, service_id, publicurl, adminurl=None, internalurl=None): """Create a new endpoint.""" body = {'endpoint': {'region': region, 'service_id': service_id, 'publicurl': publicurl, 'adminurl': adminurl, 'internalurl': internalurl}} return self._post('/endpoints', body, 'endpoint') def delete(self, id): """Delete an endpoint.""" return self._delete('/endpoints/%s' % id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/extensions.py0000664000175000017500000000211100000000000024560 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 keystoneclient import base class Extension(base.Resource): """Represents an Identity API extension.""" def __repr__(self): """Return string representation of extension resource information.""" return "" % self._info class ExtensionManager(base.ManagerWithFind): """Manager class for listing Identity API extensions.""" resource_class = Extension def list(self): """List all available extensions.""" return self._list('/extensions', 'extensions') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/roles.py0000664000175000017500000000620700000000000023517 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Role(base.Resource): """Represents a Keystone role.""" def __repr__(self): """Return string representation of role resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) class RoleManager(base.ManagerWithFind): """Manager class for manipulating Keystone roles.""" resource_class = Role def get(self, role): return self._get("/OS-KSADM/roles/%s" % base.getid(role), "role") def create(self, name): """Create a role.""" params = {"role": {"name": name}} return self._post('/OS-KSADM/roles', params, "role") def delete(self, role): """Delete a role.""" return self._delete("/OS-KSADM/roles/%s" % base.getid(role)) def list(self): """List all available roles.""" return self._list("/OS-KSADM/roles", "roles") def roles_for_user(self, user, tenant=None): user_id = base.getid(user) if tenant: tenant_id = base.getid(tenant) route = "/tenants/%s/users/%s/roles" return self._list(route % (tenant_id, user_id), "roles") else: return self._list("/users/%s/roles" % user_id, "roles") def add_user_role(self, user, role, tenant=None): """Add a role to a user. If tenant is specified, the role is added just for that tenant, otherwise the role is added globally. """ user_id = base.getid(user) role_id = base.getid(role) if tenant: route = "/tenants/%s/users/%s/roles/OS-KSADM/%s" params = (base.getid(tenant), user_id, role_id) return self._update(route % params, None, "role") else: route = "/users/%s/roles/OS-KSADM/%s" return self._update(route % (user_id, role_id), None, "roles") def remove_user_role(self, user, role, tenant=None): """Remove a role from a user. If tenant is specified, the role is removed just for that tenant, otherwise the role is removed from the user's global roles. """ user_id = base.getid(user) role_id = base.getid(role) if tenant: route = "/tenants/%s/users/%s/roles/OS-KSADM/%s" params = (base.getid(tenant), user_id, role_id) return self._delete(route % params) else: route = "/users/%s/roles/OS-KSADM/%s" return self._delete(route % (user_id, role_id)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/services.py0000664000175000017500000000336700000000000024222 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Service(base.Resource): """Represents a Keystone service.""" def __repr__(self): """Return string representation of service resource information.""" return "" % self._info class ServiceManager(base.ManagerWithFind): """Manager class for manipulating Keystone services.""" resource_class = Service def list(self): """List available services.""" return self._list("/OS-KSADM/services", "OS-KSADM:services") def get(self, id): """Retrieve a service by id.""" return self._get("/OS-KSADM/services/%s" % id, "OS-KSADM:service") def create(self, name, service_type, description=None): """Create a new service.""" body = {"OS-KSADM:service": {'name': name, 'type': service_type, 'description': description}} return self._post("/OS-KSADM/services", body, "OS-KSADM:service") def delete(self, id): """Delete a service.""" return self._delete("/OS-KSADM/services/%s" % id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/tenants.py0000664000175000017500000001426100000000000024046 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1 import plugin import urllib.parse from keystoneclient import base from keystoneclient import exceptions class Tenant(base.Resource): """Represents a Keystone tenant. Attributes: * id: a uuid that identifies the tenant * name: tenant name * description: tenant description * enabled: boolean to indicate if tenant is enabled """ def __repr__(self): """Return string representation of tenant resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) def update(self, name=None, description=None, enabled=None): # Preserve the existing settings; keystone legacy resets these? new_name = name if name else self.name if description is not None: new_description = description else: new_description = self.description new_enabled = enabled if enabled is not None else self.enabled try: retval = self.manager.update(self.id, tenant_name=new_name, description=new_description, enabled=new_enabled) self = retval except Exception: retval = None return retval def add_user(self, user, role): return self.manager.role_manager.add_user_role(base.getid(user), base.getid(role), self.id) def remove_user(self, user, role): return self.manager.role_manager.remove_user_role(base.getid(user), base.getid(role), self.id) def list_users(self): return self.manager.list_users(self.id) class TenantManager(base.ManagerWithFind): """Manager class for manipulating Keystone tenants.""" resource_class = Tenant def __init__(self, client, role_manager, user_manager): super(TenantManager, self).__init__(client) self.role_manager = role_manager self.user_manager = user_manager def get(self, tenant_id): return self._get("/tenants/%s" % tenant_id, "tenant") def create(self, tenant_name, description=None, enabled=True, **kwargs): """Create a new tenant.""" params = {"tenant": {"name": tenant_name, "description": description, "enabled": enabled}} # Allow Extras Passthru and ensure we don't clobber primary arguments. for k, v in kwargs.items(): if k not in params['tenant']: params['tenant'][k] = v return self._post('/tenants', params, "tenant") def list(self, limit=None, marker=None): """Get a list of tenants. :param integer limit: maximum number to return. (optional) :param string marker: use when specifying a limit and making multiple calls for querying. (optional) :rtype: list of :class:`Tenant` """ params = {} if limit: params['limit'] = limit if marker: params['marker'] = marker query = "" if params: query = "?" + urllib.parse.urlencode(params) # NOTE(jamielennox): try doing a regular admin query first. If there is # no endpoint that can satisfy the request (eg an unscoped token) then # issue it against the auth_url. try: tenant_list = self._list('/tenants%s' % query, 'tenants') except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} tenant_list = self._list('/tenants%s' % query, 'tenants', endpoint_filter=endpoint_filter) return tenant_list def update(self, tenant_id, tenant_name=None, description=None, enabled=None, **kwargs): """Update a tenant with a new name and description.""" body = {"tenant": {'id': tenant_id}} if tenant_name is not None: body['tenant']['name'] = tenant_name if enabled is not None: body['tenant']['enabled'] = enabled if description is not None: body['tenant']['description'] = description # Allow Extras Passthru and ensure we don't clobber primary arguments. for k, v in kwargs.items(): if k not in body['tenant']: body['tenant'][k] = v # Keystone's API uses a POST rather than a PUT here. return self._post("/tenants/%s" % tenant_id, body, "tenant") def delete(self, tenant): """Delete a tenant.""" return self._delete("/tenants/%s" % (base.getid(tenant))) def list_users(self, tenant): """List users for a tenant.""" return self.user_manager.list(base.getid(tenant)) def add_user(self, tenant, user, role): """Add a user to a tenant with the given role.""" return self.role_manager.add_user_role(base.getid(user), base.getid(role), base.getid(tenant)) def remove_user(self, tenant, user, role): """Remove the specified role from the user on the tenant.""" return self.role_manager.remove_user_role(base.getid(user), base.getid(role), base.getid(tenant)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/tokens.py0000664000175000017500000000774600000000000023707 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 exceptions from keystoneauth1 import plugin from keystoneclient import access from keystoneclient import base from keystoneclient.i18n import _ class Token(base.Resource): def __repr__(self): """Return string representation of resource information.""" return "" % self._info @property def id(self): return self._info['token']['id'] @property def expires(self): return self._info['token']['expires'] @property def tenant(self): return self._info['token'].get('tenant') class TokenManager(base.Manager): resource_class = Token def authenticate(self, username=None, tenant_id=None, tenant_name=None, password=None, token=None, return_raw=False): if token: params = {"auth": {"token": {"id": token}}} elif username and password: params = {"auth": {"passwordCredentials": {"username": username, "password": password}}} else: raise ValueError( _('A username and password or token is required.')) if tenant_id: params['auth']['tenantId'] = tenant_id elif tenant_name: params['auth']['tenantName'] = tenant_name args = ['/tokens', params, 'access'] kwargs = {'return_raw': return_raw, 'log': False} # NOTE(jamielennox): try doing a regular admin query first. If there is # no endpoint that can satisfy the request (eg an unscoped token) then # issue it against the auth_url. try: token_ref = self._post(*args, **kwargs) except exceptions.EndpointNotFound: kwargs['endpoint_filter'] = {'interface': plugin.AUTH_INTERFACE} token_ref = self._post(*args, **kwargs) return token_ref def delete(self, token): return self._delete("/tokens/%s" % base.getid(token)) def endpoints(self, token): return self._get("/tokens/%s/endpoints" % base.getid(token), "token") def validate(self, token): """Validate a token. :param token: Token to be validated. :rtype: :py:class:`.Token` """ return self._get('/tokens/%s' % base.getid(token), 'access') def get_token_data(self, token): """Fetch the data about a token from the identity server. :param str token: The token id. :rtype: dict """ url = '/tokens/%s' % token resp, body = self.client.get(url) return body def validate_access_info(self, token): """Validate a token. :param token: Token to be validated. This can be an instance of :py:class:`keystoneclient.access.AccessInfo` or a string token_id. :rtype: :py:class:`keystoneclient.access.AccessInfoV2` """ def calc_id(token): if isinstance(token, access.AccessInfo): return token.auth_token return base.getid(token) token_id = calc_id(token) body = self.get_token_data(token_id) return access.AccessInfo.factory(auth_token=token_id, body=body) def get_revoked(self): """Return the revoked tokens response. The response will be a dict containing 'signed' which is a CMS-encoded document. """ resp, body = self.client.get('/tokens/revoked') return body ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v2_0/users.py0000664000175000017500000001102000000000000023521 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base import urllib.parse class User(base.Resource): """Represents a Keystone user.""" def __repr__(self): """Return string representation of user resource information.""" return "" % self._info def delete(self): return self.manager.delete(self) def list_roles(self, tenant=None): return self.manager.list_roles(self.id, base.getid(tenant)) class UserManager(base.ManagerWithFind): """Manager class for manipulating Keystone users.""" resource_class = User def __init__(self, client, role_manager): super(UserManager, self).__init__(client) self.role_manager = role_manager def get(self, user): return self._get("/users/%s" % base.getid(user), "user") def update(self, user, **kwargs): """Update user data. Supported arguments include ``name``, ``email``, and ``enabled``. """ # FIXME(gabriel): "tenantId" seems to be accepted by the API but # fails to actually update the default tenant. params = {"user": kwargs} url = "/users/%s" % base.getid(user) return self._update(url, params, "user") def update_enabled(self, user, enabled): """Update enabled-ness.""" params = {"user": {"enabled": enabled}} self._update("/users/%s/OS-KSADM/enabled" % base.getid(user), params, "user") def update_password(self, user, password): """Update password.""" params = {"user": {"password": password}} return self._update("/users/%s/OS-KSADM/password" % base.getid(user), params, "user", log=False) def update_own_password(self, origpasswd, passwd): """Update password.""" params = {"user": {"password": passwd, "original_password": origpasswd}} return self._update("/OS-KSCRUD/users/%s" % self.client.user_id, params, response_key="access", method="PATCH", endpoint_filter={'interface': 'public'}, log=False) def update_tenant(self, user, tenant): """Update default tenant.""" params = {"user": {"tenantId": base.getid(tenant)}} # FIXME(ja): seems like a bad url - default tenant is an attribute # not a subresource!??? return self._update("/users/%s/OS-KSADM/tenant" % base.getid(user), params, "user") def create(self, name, password=None, email=None, tenant_id=None, enabled=True): """Create a user.""" params = {"user": {"name": name, "password": password, "tenantId": tenant_id, "email": email, "enabled": enabled}} return self._post('/users', params, "user", log=not bool(password)) def delete(self, user): """Delete a user.""" return self._delete("/users/%s" % base.getid(user)) def list(self, tenant_id=None, limit=None, marker=None): """Get a list of users (optionally limited to a tenant). :rtype: list of :class:`User` """ params = {} if limit: params['limit'] = int(limit) if marker: params['marker'] = marker query = "" if params: query = "?" + urllib.parse.urlencode(params) if not tenant_id: return self._list("/users%s" % query, "users") else: return self._list("/tenants/%s/users%s" % (tenant_id, query), "users") def list_roles(self, user, tenant=None): return self.role_manager.roles_for_user(base.getid(user), base.getid(tenant)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2510035 python_keystoneclient-5.6.0/keystoneclient/v3/0000775000175000017500000000000000000000000021576 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/__init__.py0000664000175000017500000000012300000000000023703 0ustar00zuulzuul00000000000000 from keystoneclient.v3.client import Client # noqa __all__ = ( 'client', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/access_rules.py0000664000175000017500000000745700000000000024640 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class AccessRule(base.Resource): """Represents an Identity access rule for application credentials. Attributes: * id: a uuid that identifies the access rule * method: The request method that the application credential is permitted to use for a given API endpoint * path: The API path that the application credential is permitted to access * service: The service type identifier for the service that the application credential is permitted to access """ pass class AccessRuleManager(base.CrudManager): """Manager class for manipulating Identity access rules.""" resource_class = AccessRule collection_key = 'access_rules' key = 'access_rule' def get(self, access_rule, user=None): """Retrieve an access rule. :param access_rule: the access rule to be retrieved from the server :type access_rule: str or :class:`keystoneclient.v3.access_rules.AccessRule` :param string user: User ID :returns: the specified access rule :rtype: :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).get( access_rule_id=base.getid(access_rule)) def list(self, user=None, **kwargs): """List access rules. :param string user: User ID :returns: a list of access rules :rtype: list of :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).list(**kwargs) def find(self, user=None, **kwargs): """Find an access rule with attributes matching ``**kwargs``. :param string user: User ID :returns: a list of matching access rules :rtype: list of :class:`keystoneclient.v3.access_rules.AccessRule` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).find(**kwargs) def delete(self, access_rule, user=None): """Delete an access rule. :param access_rule: the access rule to be deleted :type access_rule: str or :class:`keystoneclient.v3.access_rules.AccessRule` :param string user: User ID :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(AccessRuleManager, self).delete( access_rule_id=base.getid(access_rule)) def update(self): raise exceptions.MethodNotImplemented( _('Access rules are immutable, updating is not' ' supported.')) def create(self): raise exceptions.MethodNotImplemented( _('Access rules can only be created as attributes of application ' 'credentials.')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/application_credentials.py0000664000175000017500000001446500000000000027042 0ustar00zuulzuul00000000000000# Copyright 2018 SUSE Linux GmbH # # 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import utils class ApplicationCredential(base.Resource): """Represents an Identity application credential. Attributes: * id: a uuid that identifies the application credential * user: the user who owns the application credential * name: application credential name * secret: application credential secret * description: application credential description * expires_at: expiry time * roles: role assignments on the project * unrestricted: whether the application credential has restrictions applied * access_rules: a list of access rules defining what API requests the application credential may be used for """ pass class ApplicationCredentialManager(base.CrudManager): """Manager class for manipulating Identity application credentials.""" resource_class = ApplicationCredential collection_key = 'application_credentials' key = 'application_credential' def create(self, name, user=None, secret=None, description=None, expires_at=None, roles=None, unrestricted=False, access_rules=None, **kwargs): """Create a credential. :param string name: application credential name :param string user: User ID :param secret: application credential secret :param description: application credential description :param datetime.datetime expires_at: expiry time :param List roles: list of roles on the project. Maybe a list of IDs or a list of dicts specifying role name and domain :param bool unrestricted: whether the application credential has restrictions applied :param List access_rules: a list of dicts representing access rules :returns: the created application credential :rtype: :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} # Convert roles list into list-of-dict API format role_list = [] if roles: if not isinstance(roles, list): roles = [roles] for role in roles: if isinstance(role, str): role_list.extend([{'id': role}]) elif isinstance(role, dict): role_list.extend([role]) else: msg = (_("Roles must be a list of IDs or role dicts.")) raise exceptions.CommandError(msg) if not role_list: role_list = None # Convert datetime.datetime expires_at to iso format string if expires_at: expires_str = utils.isotime(at=expires_at, subsecond=True) else: expires_str = None return super(ApplicationCredentialManager, self).create( name=name, secret=secret, description=description, expires_at=expires_str, roles=role_list, unrestricted=unrestricted, access_rules=access_rules, **kwargs) def get(self, application_credential, user=None): """Retrieve an application credential. :param application_credential: the credential to be retrieved from the server :type applicationcredential: str or :class:`keystoneclient.v3.application_credentials.ApplicationCredential` :returns: the specified application credential :rtype: :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).get( application_credential_id=base.getid(application_credential)) def list(self, user=None, **kwargs): """List application credentials. :param string user: User ID :returns: a list of application credentials :rtype: list of :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).list(**kwargs) def find(self, user=None, **kwargs): """Find an application credential with attributes matching ``**kwargs``. # noqa :param string user: User ID :returns: a list of matching application credentials :rtype: list of :class:`keystoneclient.v3.application_credentials.ApplicationCredential` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).find(**kwargs) def delete(self, application_credential, user=None): """Delete an application credential. :param application_credential: the application credential to be deleted :type credential: str or :class:`keystoneclient.v3.application_credentials.ApplicationCredential` :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ user = user or self.client.user_id self.base_url = '/users/%(user)s' % {'user': user} return super(ApplicationCredentialManager, self).delete( application_credential_id=base.getid(application_credential)) def update(self): raise exceptions.MethodNotImplemented( _('Application credentials are immutable, updating is not' ' supported.')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/auth.py0000664000175000017500000000626400000000000023121 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 exceptions from keystoneauth1 import plugin from keystoneclient import base from keystoneclient.v3 import domains from keystoneclient.v3 import projects from keystoneclient.v3 import system Domain = domains.Domain Project = projects.Project System = system.System class AuthManager(base.Manager): """Retrieve auth context specific information. The information returned by the auth routes is entirely dependent on the authentication information provided by the user. """ _PROJECTS_URL = '/auth/projects' _DOMAINS_URL = '/auth/domains' _SYSTEM_URL = '/auth/system' def projects(self): """List projects that the specified token can be rescoped to. :returns: a list of projects. :rtype: list of :class:`keystoneclient.v3.projects.Project` """ try: return self._list(self._PROJECTS_URL, 'projects', obj_class=Project) except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} return self._list(self._PROJECTS_URL, 'projects', obj_class=Project, endpoint_filter=endpoint_filter) def domains(self): """List Domains that the specified token can be rescoped to. :returns: a list of domains. :rtype: list of :class:`keystoneclient.v3.domains.Domain`. """ try: return self._list(self._DOMAINS_URL, 'domains', obj_class=Domain) except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} return self._list(self._DOMAINS_URL, 'domains', obj_class=Domain, endpoint_filter=endpoint_filter) def systems(self): """List Systems that the specified token can be rescoped to. At the moment this is either empty or "all". :returns: a list of systems. :rtype: list of :class:`keystoneclient.v3.systems.System`. """ try: return self._list(self._SYSTEM_URL, 'system', obj_class=System) except exceptions.EndpointNotFound: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} return self._list(self._SYSTEM_URL, 'system', obj_class=System, endpoint_filter=endpoint_filter) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/client.py0000664000175000017500000003402000000000000023425 0ustar00zuulzuul00000000000000# Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import warnings from oslo_serialization import jsonutils from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ from keystoneclient.v3 import access_rules from keystoneclient.v3 import application_credentials from keystoneclient.v3 import auth from keystoneclient.v3.contrib import endpoint_filter from keystoneclient.v3.contrib import endpoint_policy from keystoneclient.v3.contrib import federation from keystoneclient.v3.contrib import oauth1 from keystoneclient.v3.contrib import simple_cert from keystoneclient.v3.contrib import trusts from keystoneclient.v3 import credentials from keystoneclient.v3 import domain_configs from keystoneclient.v3 import domains from keystoneclient.v3 import ec2 from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoints from keystoneclient.v3 import groups from keystoneclient.v3 import limits from keystoneclient.v3 import policies from keystoneclient.v3 import projects from keystoneclient.v3 import regions from keystoneclient.v3 import registered_limits from keystoneclient.v3 import role_assignments from keystoneclient.v3 import roles from keystoneclient.v3 import services from keystoneclient.v3 import tokens from keystoneclient.v3 import users _logger = logging.getLogger(__name__) class Client(httpclient.HTTPClient): r"""Client for the OpenStack Identity API v3. :param session: Session for requests. (optional) :type session: keystoneauth1.session.Session :param string user_id: User ID for authentication. (optional) :param string username: Username for authentication. (optional) :param string user_domain_id: User's domain ID for authentication. (optional) :param string user_domain_name: User's domain name for authentication. (optional) :param string password: Password for authentication. (optional) :param string token: Token for authentication. (optional) :param string domain_id: Domain ID for domain scoping. (optional) :param string domain_name: Domain name for domain scoping. (optional) :param string project_id: Project ID for project scoping. (optional) :param string project_name: Project name for project scoping. (optional) :param string project_domain_id: Project's domain ID for project scoping. (optional) :param string project_domain_name: Project's domain name for project scoping. (optional) :param string tenant_name: Tenant name. (optional) The tenant_name keyword argument is deprecated as of the 1.7.0 release in favor of project_name and may be removed in the 2.0.0 release. :param string tenant_id: Tenant id. (optional) The tenant_id keyword argument is deprecated as of the 1.7.0 release in favor of project_id and may be removed in the 2.0.0 release. :param string auth_url: Identity service endpoint for authorization. :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param string endpoint: A user-supplied endpoint URL for the identity service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation. (optional) :param integer timeout: Allows customization of the timeout for client http requests. (optional) .. warning:: Constructing an instance of this class without a session is deprecated as of the 1.7.0 release and will be removed in the 2.0.0 release. Example:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(user_domain_name=DOMAIN_NAME, ... username=USER, ... password=PASS, ... project_domain_name=PROJECT_DOMAIN_NAME, ... project_name=PROJECT_NAME, ... auth_url=KEYSTONE_URL) >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() ... >>> user = keystone.users.get(USER_ID) >>> user.delete() Instances of this class have the following managers: .. py:attribute:: credentials :py:class:`keystoneclient.v3.credentials.CredentialManager` .. py:attribute:: domain_configs :py:class:`keystoneclient.v3.domain_configs.DomainConfigManager` .. py:attribute:: ec2 :py:class:`keystoneclient.v3.ec2.EC2Manager` .. py:attribute:: endpoint_filter :py:class:`keystoneclient.v3.contrib.endpoint_filter.\ EndpointFilterManager` .. py:attribute:: endpoint_groups :py:class:`keystoneclient.v3.endpoint_groups.\ EndpointGroupManager` .. py:attribute:: endpoint_policy :py:class:`keystoneclient.v3.contrib.endpoint_policy.\ EndpointPolicyManager` .. py:attribute:: endpoints :py:class:`keystoneclient.v3.endpoints.EndpointManager` .. py:attribute:: domains :py:class:`keystoneclient.v3.domains.DomainManager` .. py:attribute:: federation :py:class:`keystoneclient.v3.contrib.federation.core.FederationManager` .. py:attribute:: groups :py:class:`keystoneclient.v3.groups.GroupManager` .. py:attribute:: limits :py:class:`keystoneclient.v3.limits.LimitManager` .. py:attribute:: oauth1 :py:class:`keystoneclient.v3.contrib.oauth1.core.OAuthManager` .. py:attribute:: policies :py:class:`keystoneclient.v3.policies.PolicyManager` .. py:attribute:: regions :py:class:`keystoneclient.v3.regions.RegionManager` .. py:attribute:: registered_limits :py:class:`keystoneclient.v3.registered_limits.RegisteredLimitManager` .. py:attribute:: role_assignments :py:class:`keystoneclient.v3.role_assignments.RoleAssignmentManager` .. py:attribute:: roles :py:class:`keystoneclient.v3.roles.RoleManager` .. py:attribute:: simple_cert :py:class:`keystoneclient.v3.contrib.simple_cert.SimpleCertManager` .. py:attribute:: services :py:class:`keystoneclient.v3.services.ServiceManager` .. py:attribute:: tokens :py:class:`keystoneclient.v3.tokens.TokenManager` .. py:attribute:: trusts :py:class:`keystoneclient.v3.contrib.trusts.TrustManager` .. py:attribute:: users :py:class:`keystoneclient.v3.users.UserManager` """ version = 'v3' def __init__(self, **kwargs): """Initialize a new client for the Keystone v3 API.""" super(Client, self).__init__(**kwargs) if not kwargs.get('session'): warnings.warn( 'Constructing an instance of the ' 'keystoneclient.v3.client.Client class without a session is ' 'deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) self.access_rules = ( access_rules.AccessRuleManager(self._adapter) ) self.application_credentials = ( application_credentials.ApplicationCredentialManager(self._adapter) ) self.auth = auth.AuthManager(self._adapter) self.credentials = credentials.CredentialManager(self._adapter) self.ec2 = ec2.EC2Manager(self._adapter) self.endpoint_filter = endpoint_filter.EndpointFilterManager( self._adapter) self.endpoint_groups = endpoint_groups.EndpointGroupManager( self._adapter) self.endpoint_policy = endpoint_policy.EndpointPolicyManager( self._adapter) self.endpoints = endpoints.EndpointManager(self._adapter) self.domain_configs = domain_configs.DomainConfigManager(self._adapter) self.domains = domains.DomainManager(self._adapter) self.federation = federation.FederationManager(self._adapter) self.groups = groups.GroupManager(self._adapter) self.limits = limits.LimitManager(self._adapter) self.oauth1 = oauth1.create_oauth_manager(self._adapter) self.policies = policies.PolicyManager(self._adapter) self.projects = projects.ProjectManager(self._adapter) self.registered_limits = registered_limits.RegisteredLimitManager( self._adapter) self.regions = regions.RegionManager(self._adapter) self.role_assignments = ( role_assignments.RoleAssignmentManager(self._adapter)) self.roles = roles.RoleManager(self._adapter) self.inference_rules = roles.InferenceRuleManager(self._adapter) self.services = services.ServiceManager(self._adapter) self.simple_cert = simple_cert.SimpleCertManager(self._adapter) self.tokens = tokens.TokenManager(self._adapter) self.trusts = trusts.TrustManager(self._adapter) self.users = users.UserManager(self._adapter) # DEPRECATED: if session is passed then we go to the new behaviour of # authenticating on the first required call. if 'session' not in kwargs and self.management_url is None: self.authenticate() def serialize(self, entity): return jsonutils.dumps(entity, sort_keys=True) def process_token(self, **kwargs): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ super(Client, self).process_token(**kwargs) if self.auth_ref.domain_scoped: if not self.auth_ref.domain_id: raise exceptions.AuthorizationFailure( _("Token didn't provide domain_id")) self._process_management_url(kwargs.get('region_name')) self.domain_name = self.auth_ref.domain_name self.domain_id = self.auth_ref.domain_id if self._management_url: self._management_url = self._management_url.replace('/v2.0', '/v3') def get_raw_token_from_identity_service(self, auth_url, user_id=None, username=None, user_domain_id=None, user_domain_name=None, password=None, domain_id=None, domain_name=None, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, token=None, trust_id=None, **kwargs): """Authenticate against the v3 Identity API. If password and token methods are both provided then both methods will be used in the request. :returns: access.AccessInfo if authentication was successful. :rtype: :class:`keystoneclient.access.AccessInfoV3` :raises keystoneclient.exceptions.AuthorizationFailure: if unable to authenticate or validate the existing authorization token. :raises keystoneclient.exceptions.Unauthorized: if authentication fails due to invalid token. """ try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) auth_methods = [] if token: auth_methods.append(v3_auth.TokenMethod(token=token)) if password: m = v3_auth.PasswordMethod(user_id=user_id, username=username, user_domain_id=user_domain_id, user_domain_name=user_domain_name, password=password) auth_methods.append(m) if not auth_methods: msg = _('A user and password or token is required.') raise exceptions.AuthorizationFailure(msg) plugin = v3_auth.Auth(auth_url, auth_methods, trust_id=trust_id, domain_id=domain_id, domain_name=domain_name, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name) return plugin.get_auth_ref(self.session) except (exceptions.AuthorizationFailure, exceptions.Unauthorized): _logger.debug('Authorization failed.') raise except exceptions.EndpointNotFound: msg = _('There was no suitable authentication url for this' ' request') raise exceptions.AuthorizationFailure(msg) except Exception as e: raise exceptions.AuthorizationFailure( _('Authorization failed: %s') % e) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2550037 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/0000775000175000017500000000000000000000000023236 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/__init__.py0000664000175000017500000000002300000000000025342 0ustar00zuulzuul00000000000000 __all__ = tuple() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/endpoint_filter.py0000664000175000017500000001536100000000000027003 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. from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoints from keystoneclient.v3 import projects class EndpointFilterManager(base.Manager): """Manager class for manipulating project-endpoint associations. Project-endpoint associations can be with endpoints directly or via endpoint groups. """ OS_EP_FILTER_EXT = '/OS-EP-FILTER' def _build_base_url(self, project=None, endpoint=None): project_id = base.getid(project) endpoint_id = base.getid(endpoint) if project_id and endpoint_id: api_path = '/projects/%s/endpoints/%s' % (project_id, endpoint_id) elif project_id: api_path = '/projects/%s/endpoints' % (project_id) elif endpoint_id: api_path = '/endpoints/%s/projects' % (endpoint_id) else: msg = _('Must specify a project, an endpoint, or both') raise exceptions.ValidationError(msg) return self.OS_EP_FILTER_EXT + api_path def _build_group_base_url(self, project=None, endpoint_group=None): project_id = base.getid(project) endpoint_group_id = base.getid(endpoint_group) if project_id and endpoint_group_id: api_path = '/endpoint_groups/%s/projects/%s' % ( endpoint_group_id, project_id) elif project_id: api_path = '/projects/%s/endpoint_groups' % (project_id) elif endpoint_group_id: api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id) else: msg = _('Must specify a project, an endpoint group, or both') raise exceptions.ValidationError(msg) return self.OS_EP_FILTER_EXT + api_path def add_endpoint_to_project(self, project, endpoint): """Create a project-endpoint association.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._put(url=base_url) def delete_endpoint_from_project(self, project, endpoint): """Remove a project-endpoint association.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._delete(url=base_url) def check_endpoint_in_project(self, project, endpoint): """Check if project-endpoint association exists.""" if not (project and endpoint): raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) return super(EndpointFilterManager, self)._head(url=base_url) def list_endpoints_for_project(self, project): """List all endpoints for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, endpoints.EndpointManager.collection_key, obj_class=endpoints.EndpointManager.resource_class) def list_projects_for_endpoint(self, endpoint): """List all projects for a given endpoint.""" if not endpoint: raise ValueError(_('endpoint is required')) base_url = self._build_base_url(endpoint=endpoint) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class) def add_endpoint_group_to_project(self, endpoint_group, project): """Create a project-endpoint group association.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._put(url=base_url) def delete_endpoint_group_from_project(self, endpoint_group, project): """Remove a project-endpoint group association.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._delete(url=base_url) def check_endpoint_group_in_project(self, endpoint_group, project): """Check if project-endpoint group association exists.""" if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_group_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._head(url=base_url) def list_endpoint_groups_for_project(self, project): """List all endpoint groups for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_group_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, 'endpoint_groups', obj_class=endpoint_groups.EndpointGroupManager.resource_class) def list_projects_for_endpoint_group(self, endpoint_group): """List all projects associated with a given endpoint group.""" if not endpoint_group: raise ValueError(_('endpoint_group is required')) base_url = self._build_group_base_url(endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/endpoint_policy.py0000664000175000017500000001445300000000000027016 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. from keystoneclient import base from keystoneclient.i18n import _ from keystoneclient.v3 import endpoints from keystoneclient.v3 import policies class EndpointPolicyManager(base.Manager): """Manager class for manipulating endpoint-policy associations.""" OS_EP_POLICY_EXT = 'OS-ENDPOINT-POLICY' def _act_on_policy_association_for_endpoint( self, policy, endpoint, action): if not (policy and endpoint): raise ValueError(_('policy and endpoint are required')) policy_id = base.getid(policy) endpoint_id = base.getid(endpoint) url = ('/policies/%(policy_id)s/%(ext_name)s' '/endpoints/%(endpoint_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'endpoint_id': endpoint_id} return action(url=url) def create_policy_association_for_endpoint(self, policy, endpoint): """Create an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._put) def check_policy_association_for_endpoint(self, policy, endpoint): """Check an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._head) def delete_policy_association_for_endpoint(self, policy, endpoint): """Delete an association between a policy and an endpoint.""" return self._act_on_policy_association_for_endpoint( policy, endpoint, self._delete) def _act_on_policy_association_for_service(self, policy, service, action): if not (policy and service): raise ValueError(_('policy and service are required')) policy_id = base.getid(policy) service_id = base.getid(service) url = ('/policies/%(policy_id)s/%(ext_name)s' '/services/%(service_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'service_id': service_id} return action(url=url) def create_policy_association_for_service(self, policy, service): """Create an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._put) def check_policy_association_for_service(self, policy, service): """Check an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._head) def delete_policy_association_for_service(self, policy, service): """Delete an association between a policy and a service.""" return self._act_on_policy_association_for_service( policy, service, self._delete) def _act_on_policy_association_for_region_and_service( self, policy, region, service, action): if not (policy and region and service): raise ValueError(_('policy, region and service are required')) policy_id = base.getid(policy) region_id = base.getid(region) service_id = base.getid(service) url = ('/policies/%(policy_id)s/%(ext_name)s' '/services/%(service_id)s/regions/%(region_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'service_id': service_id, 'region_id': region_id} return action(url=url) def create_policy_association_for_region_and_service( self, policy, region, service): """Create an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._put) def check_policy_association_for_region_and_service( self, policy, region, service): """Check an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._head) def delete_policy_association_for_region_and_service( self, policy, region, service): """Delete an association between a policy and a service in a region.""" return self._act_on_policy_association_for_region_and_service( policy, region, service, self._delete) def get_policy_for_endpoint(self, endpoint): """Get the effective policy for an endpoint. :param endpoint: endpoint object or ID :returns: policies.Policy object """ if not endpoint: raise ValueError(_('endpoint is required')) endpoint_id = base.getid(endpoint) url = ('/endpoints/%(endpoint_id)s/%(ext_name)s/policy') % { 'endpoint_id': endpoint_id, 'ext_name': self.OS_EP_POLICY_EXT} resp, body = self.client.get(url) return self._prepare_return_value( resp, policies.Policy(self, body[policies.PolicyManager.key], loaded=True)) def list_endpoints_for_policy(self, policy): """List endpoints with the effective association to a policy. :param policy: policy object or ID :returns: list of endpoints that are associated with the policy """ if not policy: raise ValueError(_('policy is required')) policy_id = base.getid(policy) url = ('/policies/%(policy_id)s/%(ext_name)s/endpoints') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT} return self._list( url, endpoints.EndpointManager.collection_key, obj_class=endpoints.EndpointManager.resource_class) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2550037 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/0000775000175000017500000000000000000000000025356 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/__init__.py0000664000175000017500000000117500000000000027473 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 keystoneclient.v3.contrib.federation.core import * # noqa ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/base.py0000664000175000017500000000251700000000000026647 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 from keystoneauth1 import exceptions from keystoneauth1 import plugin from keystoneclient import base class EntityManager(base.Manager, metaclass=abc.ABCMeta): """Manager class for listing federated accessible objects.""" resource_class = None @property @abc.abstractmethod def object_type(self): raise exceptions.MethodNotImplemented def list(self): url = '/auth/%s' % self.object_type try: tenant_list = self._list(url, self.object_type) except exceptions.CatalogException: endpoint_filter = {'interface': plugin.AUTH_INTERFACE} tenant_list = self._list(url, self.object_type, endpoint_filter=endpoint_filter) return tenant_list ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/core.py0000664000175000017500000000271600000000000026666 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 keystoneclient.v3.contrib.federation import domains from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3.contrib.federation import mappings from keystoneclient.v3.contrib.federation import projects from keystoneclient.v3.contrib.federation import protocols from keystoneclient.v3.contrib.federation import saml from keystoneclient.v3.contrib.federation import service_providers class FederationManager(object): def __init__(self, api): self.identity_providers = identity_providers.IdentityProviderManager( api) self.mappings = mappings.MappingManager(api) self.protocols = protocols.ProtocolManager(api) self.projects = projects.ProjectManager(api) self.domains = domains.DomainManager(api) self.saml = saml.SamlManager(api) self.service_providers = service_providers.ServiceProviderManager(api) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/domains.py0000664000175000017500000000144200000000000027363 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 keystoneclient.v3.contrib.federation import base as federation_base from keystoneclient.v3 import domains class DomainManager(federation_base.EntityManager): object_type = 'domains' resource_class = domains.Domain ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/identity_providers.py0000664000175000017500000000742500000000000031666 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 keystoneclient import base class IdentityProvider(base.Resource): """Object representing Identity Provider container. Attributes: * id: user-defined unique string identifying Identity Provider. """ pass class IdentityProviderManager(base.CrudManager): """Manager class for manipulating Identity Providers.""" resource_class = IdentityProvider collection_key = 'identity_providers' key = 'identity_provider' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, id, **kwargs): """Create Identity Provider object. Utilize Keystone URI: PUT /OS-FEDERATION/identity_providers/$identity_provider :param id: unique id of the identity provider. :param kwargs: optional attributes: description (str), domain_id (str), enabled (boolean) and remote_ids (list). :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return self._build_url_and_put(identity_provider_id=id, **kwargs) def get(self, identity_provider): """Fetch Identity Provider object. Utilize Keystone URI: GET /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: an object with identity_provider_id stored inside. :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return super(IdentityProviderManager, self).get( identity_provider_id=base.getid(identity_provider)) def list(self, **kwargs): """List all Identity Providers. Utilize Keystone URI: GET /OS-FEDERATION/identity_providers :returns: a list of IdentityProvider resource objects. :rtype: List """ return super(IdentityProviderManager, self).list(**kwargs) def update(self, identity_provider, **kwargs): """Update Identity Provider object. Utilize Keystone URI: PATCH /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: an object with identity_provider_id stored inside. :returns: an IdentityProvider resource object. :rtype: :py:class:`keystoneclient.v3.federation.IdentityProvider` """ return super(IdentityProviderManager, self).update( identity_provider_id=base.getid(identity_provider), **kwargs) def delete(self, identity_provider): """Delete Identity Provider object. Utilize Keystone URI: DELETE /OS-FEDERATION/identity_providers/$identity_provider :param identity_provider: the Identity Provider ID itself or an object with it stored inside. """ return super(IdentityProviderManager, self).delete( identity_provider_id=base.getid(identity_provider)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/mappings.py0000664000175000017500000001055600000000000027555 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 keystoneclient import base class Mapping(base.Resource): """An object representing mapping container. Attributes: * id: user defined unique string identifying mapping. """ pass class MappingManager(base.CrudManager): """Manager class for manipulating federation mappings.""" resource_class = Mapping collection_key = 'mappings' key = 'mapping' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, mapping_id, **kwargs): """Create federation mapping. Utilize Identity API operation: PUT /OS-FEDERATION/mappings/$mapping_id :param mapping_id: user defined string identifier of the federation mapping. :param rules: a list of mapping rules. Example of the ``rules`` parameter:: [ { "local": [ { "group": { "id": "0cd5e9" } } ], "remote": [ { "type": "orgPersonType", "not_any_of": [ "Contractor", "Guest" ] } ] } ] """ return self._build_url_and_put( mapping_id=mapping_id, **kwargs) def get(self, mapping): """Fetch federation mapping identified by mapping id. Utilize Identity API operation: GET /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. """ return super(MappingManager, self).get( mapping_id=base.getid(mapping)) def list(self, **kwargs): """List all federation mappings. Utilize Identity API operation: GET /OS-FEDERATION/mappings """ return super(MappingManager, self).list(**kwargs) def update(self, mapping, **kwargs): """Update federation mapping identified by mapping id. Utilize Identity API operation: PATCH /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. :param rules: a list of mapping rules. Example of the ``rules`` parameter:: [ { "local": [ { "group": { "id": "0cd5e9" } } ], "remote": [ { "type": "orgPersonType", "not_any_of": [ "Contractor", "Guest" ] } ] } ] """ return super(MappingManager, self).update( mapping_id=base.getid(mapping), **kwargs) def delete(self, mapping): """Delete federation mapping identified by mapping id. Utilize Identity API operation: DELETE /OS-FEDERATION/mappings/$mapping_id :param mapping: a Mapping type object with mapping id stored inside. """ return super(MappingManager, self).delete( mapping_id=base.getid(mapping)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/projects.py0000664000175000017500000000144700000000000027567 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 keystoneclient.v3.contrib.federation import base as federation_base from keystoneclient.v3 import projects class ProjectManager(federation_base.EntityManager): object_type = 'projects' resource_class = projects.Project ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/protocols.py0000664000175000017500000001246300000000000027762 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 keystoneclient import base class Protocol(base.Resource): """An object representing federation protocol container. Attributes: * id: user-defined unique per Identity Provider string identifying federation protocol. """ pass class ProtocolManager(base.CrudManager): """Manager class for manipulating federation protocols.""" resource_class = Protocol collection_key = 'protocols' key = 'protocol' base_url = 'OS-FEDERATION/identity_providers' def build_url(self, dict_args_in_out=None): """Build URL for federation protocols.""" if dict_args_in_out is None: dict_args_in_out = {} identity_provider_id = dict_args_in_out.pop('identity_provider_id', None) if identity_provider_id: base_url = '/'.join([self.base_url, identity_provider_id]) else: base_url = self.base_url dict_args_in_out.setdefault('base_url', base_url) return super(ProtocolManager, self).build_url(dict_args_in_out) def _build_url_and_put(self, request_body=None, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: request_body} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, protocol_id, identity_provider, mapping, **kwargs): """Create federation protocol object and tie to the Identity Provider. Utilize Identity API operation: PUT /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param protocol_id: a string type parameter identifying a federation protocol :param identity_provider: a string type parameter identifying an Identity Provider :param mapping: a base.Resource object with federation mapping id """ return self._build_url_and_put( request_body={'mapping_id': base.getid(mapping)}, identity_provider_id=base.getid(identity_provider), protocol_id=protocol_id, **kwargs) def get(self, identity_provider, protocol, **kwargs): """Fetch federation protocol object tied to the Identity Provider. Utilize Identity API operation: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside """ return super(ProtocolManager, self).get( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol), **kwargs) def list(self, identity_provider, **kwargs): """List all federation protocol objects tied to the Identity Provider. Utilize Identity API operation: GET /OS-FEDERATION/identity_providers/ $identity_provider/protocols :param identity_provider: a base.Resource type object with Identity Provider id stored inside """ return super(ProtocolManager, self).list( identity_provider_id=base.getid(identity_provider), **kwargs) def update(self, identity_provider, protocol, mapping, **kwargs): """Update Protocol object tied to the Identity Provider. Utilize Identity API operation: PATCH /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside :param mapping: a base.Resource object with federation mapping id """ return super(ProtocolManager, self).update( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol), mapping_id=base.getid(mapping), **kwargs) def delete(self, identity_provider, protocol): """Delete Protocol object tied to the Identity Provider. Utilize Identity API operation: DELETE /OS-FEDERATION/identity_providers/ $identity_provider/protocols/$protocol :param identity_provider: a base.Resource type object with Identity Provider id stored inside :param protocol: a base.Resource type object with federation protocol id stored inside """ return super(ProtocolManager, self).delete( identity_provider_id=base.getid(identity_provider), protocol_id=base.getid(protocol)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/saml.py0000664000175000017500000000545300000000000026673 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 keystoneclient import base SAML2_ENDPOINT = '/auth/OS-FEDERATION/saml2' ECP_ENDPOINT = '/auth/OS-FEDERATION/saml2/ecp' class SamlManager(base.Manager): """Manager class for creating SAML assertions.""" def create_saml_assertion(self, service_provider, token_id): """Create a SAML assertion from a token. Equivalent Identity API call: POST /auth/OS-FEDERATION/saml2 :param service_provider: Service Provider resource. :type service_provider: string :param token_id: Token to transform to SAML assertion. :type token_id: string :returns: SAML representation of token_id :rtype: string """ headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(SAML2_ENDPOINT, json=body, headers=headers) return self._prepare_return_value(resp, resp.text) def create_ecp_assertion(self, service_provider, token_id): """Create an ECP wrapped SAML assertion from a token. Equivalent Identity API call: POST /auth/OS-FEDERATION/saml2/ecp :param service_provider: Service Provider resource. :type service_provider: string :param token_id: Token to transform to SAML assertion. :type token_id: string :returns: SAML representation of token_id, wrapped in ECP envelope :rtype: string """ headers, body = self._create_common_request(service_provider, token_id) resp, body = self.client.post(ECP_ENDPOINT, json=body, headers=headers) return self._prepare_return_value(resp, resp.text) def _create_common_request(self, service_provider, token_id): headers = {'Content-Type': 'application/json'} body = { 'auth': { 'identity': { 'methods': ['token'], 'token': { 'id': token_id } }, 'scope': { 'service_provider': { 'id': base.getid(service_provider) } } } } return (headers, body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/federation/service_providers.py0000664000175000017500000000645700000000000031501 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 keystoneclient import base class ServiceProvider(base.Resource): """Object representing Service Provider container. Attributes: * id: user-defined unique string identifying Service Provider. * sp_url: the shibboleth endpoint of a Service Provider. * auth_url: the authentication url of Service Provider. """ pass class ServiceProviderManager(base.CrudManager): """Manager class for manipulating Service Providers.""" resource_class = ServiceProvider collection_key = 'service_providers' key = 'service_provider' base_url = 'OS-FEDERATION' def _build_url_and_put(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) body = {self.key: kwargs} return self._update(url, body=body, response_key=self.key, method='PUT') def create(self, id, **kwargs): """Create Service Provider object. Utilize Keystone URI: ``PUT /OS-FEDERATION/service_providers/{id}`` :param id: unique id of the service provider. """ return self._build_url_and_put(service_provider_id=id, **kwargs) def get(self, service_provider): """Fetch Service Provider object. Utilize Keystone URI: ``GET /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).get( service_provider_id=base.getid(service_provider)) def list(self, **kwargs): """List all Service Providers. Utilize Keystone URI: ``GET /OS-FEDERATION/service_providers`` """ return super(ServiceProviderManager, self).list(**kwargs) def update(self, service_provider, **kwargs): """Update the existing Service Provider object on the server. Only properties provided to the function are being updated. Utilize Keystone URI: ``PATCH /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).update( service_provider_id=base.getid(service_provider), **kwargs) def delete(self, service_provider): """Delete Service Provider object. Utilize Keystone URI: ``DELETE /OS-FEDERATION/service_providers/{id}`` :param service_provider: an object with service_provider_id stored inside. """ return super(ServiceProviderManager, self).delete( service_provider_id=base.getid(service_provider)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2590036 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/0000775000175000017500000000000000000000000024437 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/__init__.py0000664000175000017500000000113600000000000026551 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 keystoneclient.v3.contrib.oauth1.core import * # noqa ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/access_tokens.py0000664000175000017500000000366300000000000027645 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 plugin from keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils try: from oauthlib import oauth1 except ImportError: oauth1 = None class AccessToken(base.Resource): pass class AccessTokenManager(base.CrudManager): """Manager class for manipulating identity OAuth access tokens.""" resource_class = AccessToken def create(self, consumer_key, consumer_secret, request_key, request_secret, verifier): endpoint = utils.OAUTH_PATH + '/access_token' oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, resource_owner_key=request_key, resource_owner_secret=request_secret, signature_method=oauth1.SIGNATURE_HMAC, verifier=verifier) url = self.client.get_endpoint(interface=plugin.AUTH_INTERFACE).rstrip( '/') url, headers, body = oauth_client.sign(url + endpoint, http_method='POST') resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) return self._prepare_return_value(resp, self.resource_class(self, token)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/auth.py0000664000175000017500000000402400000000000025752 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 keystoneclient.auth.identity import v3 try: from oauthlib import oauth1 except ImportError: oauth1 = None class OAuthMethod(v3.AuthMethod): """OAuth based authentication method. :param string consumer_key: Consumer key. :param string consumer_secret: Consumer secret. :param string access_key: Access token key. :param string access_secret: Access token secret. """ _method_parameters = ['consumer_key', 'consumer_secret', 'access_key', 'access_secret'] def __init__(self, **kwargs): super(OAuthMethod, self).__init__(**kwargs) if oauth1 is None: raise NotImplementedError('optional package oauthlib' ' is not installed') def get_auth_data(self, session, auth, headers, **kwargs): # Add the oauth specific content into the headers oauth_client = oauth1.Client(self.consumer_key, client_secret=self.consumer_secret, resource_owner_key=self.access_key, resource_owner_secret=self.access_secret, signature_method=oauth1.SIGNATURE_HMAC) o_url, o_headers, o_body = oauth_client.sign(auth.token_url, http_method='POST') headers.update(o_headers) return 'oauth1', {} class OAuth(v3.AuthConstructor): _auth_method_class = OAuthMethod ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/consumers.py0000664000175000017500000000324400000000000027032 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 keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils class Consumer(base.Resource): """Represents an OAuth consumer. Attributes: * id: a uuid that identifies the consumer * description: a short description of the consumer """ pass class ConsumerManager(base.CrudManager): """Manager class for manipulating identity consumers.""" resource_class = Consumer collection_key = 'consumers' key = 'consumer' base_url = utils.OAUTH_PATH def create(self, description=None, **kwargs): return super(ConsumerManager, self).create( description=description, **kwargs) def get(self, consumer): return super(ConsumerManager, self).get( consumer_id=base.getid(consumer)) def update(self, consumer, description=None, **kwargs): return super(ConsumerManager, self).update( consumer_id=base.getid(consumer), description=description, **kwargs) def delete(self, consumer): return super(ConsumerManager, self).delete( consumer_id=base.getid(consumer)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/core.py0000664000175000017500000000512000000000000025737 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 keystoneclient.i18n import _ from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens def create_oauth_manager(self): # NOTE(stevemar): Attempt to import the oauthlib package at this point. try: import oauthlib # noqa # NOTE(stevemar): Return an object instead of raising an exception here, # this will allow users to see an exception only when trying to access the # oauth portions of client. Otherwise an exception would be raised # when the client is created. except ImportError: return OAuthManagerOptionalImportProxy() else: return OAuthManager(self) class OAuthManager(object): def __init__(self, api): self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) self.request_tokens = request_tokens.RequestTokenManager(api) class OAuthManagerOptionalImportProxy(object): """Act as a proxy manager in case oauthlib is no installed. This class will only be created if oauthlib is not in the system, trying to access any of the attributes in name (access_tokens, consumers, request_tokens), will result in a NotImplementedError, and a message. >>> manager.access_tokens.blah NotImplementedError: To use 'access_tokens' oauthlib must be installed Otherwise, if trying to access an attribute other than the ones in name, the manager will state that the attribute does not exist. >>> manager.dne.blah AttributeError: 'OAuthManagerOptionalImportProxy' object has no attribute 'dne' """ def __getattribute__(self, name): """Return error when name is related to oauthlib and not exist.""" if name in ('access_tokens', 'consumers', 'request_tokens'): raise NotImplementedError( _('To use %r oauthlib must be installed') % name) return super(OAuthManagerOptionalImportProxy, self).__getattribute__(name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/request_tokens.py0000664000175000017500000000537200000000000030073 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 as urlparse from keystoneauth1 import plugin from keystoneclient import base from keystoneclient.v3.contrib.oauth1 import utils try: from oauthlib import oauth1 except ImportError: oauth1 = None class RequestToken(base.Resource): def authorize(self, roles): try: retval = self.manager.authorize(self.id, roles) self = retval except Exception: retval = None return retval class RequestTokenManager(base.CrudManager): """Manager class for manipulating identity OAuth request tokens.""" resource_class = RequestToken def authorize(self, request_token, roles): """Authorize a request token with specific roles. Utilize Identity API operation: PUT /OS-OAUTH1/authorize/$request_token_id :param request_token: a request token that will be authorized, and can be exchanged for an access token. :param roles: a list of roles, that will be delegated to the user. """ request_id = urlparse.quote(base.getid(request_token)) endpoint = utils.OAUTH_PATH + '/authorize/%s' % (request_id) body = {'roles': [{'id': base.getid(r_id)} for r_id in roles]} return self._put(endpoint, body, "token") def create(self, consumer_key, consumer_secret, project): endpoint = utils.OAUTH_PATH + '/request_token' headers = {'requested-project-id': base.getid(project)} oauth_client = oauth1.Client(consumer_key, client_secret=consumer_secret, signature_method=oauth1.SIGNATURE_HMAC, callback_uri="oob") url = self.client.get_endpoint(interface=plugin.AUTH_INTERFACE).rstrip( "/") url, headers, body = oauth_client.sign(url + endpoint, http_method='POST', headers=headers) resp, body = self.client.post(endpoint, headers=headers) token = utils.get_oauth_token_from_body(resp.content) return self._prepare_return_value(resp, self.resource_class(self, token)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/oauth1/utils.py0000664000175000017500000000233500000000000026154 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 as urlparse OAUTH_PATH = '/OS-OAUTH1' def get_oauth_token_from_body(body): """Parse the URL response body to retrieve the oauth token key and secret. The response body will look like: 'oauth_token=12345&oauth_token_secret=67890' with 'oauth_expires_at=2013-03-30T05:27:19.463201' possibly there, too. """ body = body.decode('utf-8') credentials = urlparse.parse_qs(body) key = credentials['oauth_token'][0] secret = credentials['oauth_token_secret'][0] token = {'key': key, 'id': key, 'secret': secret} expires_at = credentials.get('oauth_expires_at') if expires_at: token['expires'] = expires_at[0] return token ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/simple_cert.py0000664000175000017500000000266000000000000026122 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. from keystoneclient import base class SimpleCertManager(object): """Manager for the OS-SIMPLE-CERT extension.""" def __init__(self, client): self._client = client self.mgr = base.Manager(self._client) def get_ca_certificates(self): """Get CA certificates. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/OS-SIMPLE-CERT/ca', authenticated=False) return self.mgr._prepare_return_value(resp, resp.text) def get_certificates(self): """Get signing certificates. :returns: PEM-formatted string. :rtype: str """ resp, body = self._client.get('/OS-SIMPLE-CERT/certificates', authenticated=False) return self.mgr._prepare_return_value(resp, resp.text) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/contrib/trusts.py0000664000175000017500000000743200000000000025162 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ from keystoneclient import utils class Trust(base.Resource): """Represents a Trust. Attributes: * id: a uuid that identifies the trust * impersonation: allow explicit impersonation * project_id: project ID * trustee_user_id: a uuid that identifies the trustee * trustor_user_id: a uuid that identifies the trustor """ pass class TrustManager(base.CrudManager): """Manager class for manipulating Trusts.""" resource_class = Trust collection_key = 'trusts' key = 'trust' base_url = '/OS-TRUST' def create(self, trustee_user, trustor_user, role_names=None, role_ids=None, project=None, impersonation=False, expires_at=None, remaining_uses=None, **kwargs): """Create a Trust. :param string trustee_user: user who is capable of consuming the trust :param string trustor_user: user who's authorization is being delegated :param string role_names: subset of trustor's roles to be granted :param string role_ids: subset of trustor's roles to be granted :param string project: project which the trustor is delegating :param boolean impersonation: enable explicit impersonation :param datetime.datetime expires_at: expiry time :param integer remaining_uses: how many times this trust can be used to generate a token. None means unlimited tokens. """ # Convert role_names list into list-of-dict API format roles = [] if role_names: roles.extend([{'name': n} for n in role_names]) if role_ids: roles.extend([{'id': i} for i in role_ids]) if not roles: roles = None # Convert datetime.datetime expires_at to iso format string if expires_at: expires_str = utils.isotime(at=expires_at, subsecond=True) else: expires_str = None return super(TrustManager, self).create( expires_at=expires_str, impersonation=impersonation, project_id=base.getid(project), remaining_uses=remaining_uses, roles=roles, trustee_user_id=base.getid(trustee_user), trustor_user_id=base.getid(trustor_user), **kwargs) def update(self): raise exceptions.MethodNotImplemented( _('Update not supported for trusts')) def list(self, trustee_user=None, trustor_user=None, **kwargs): """List Trusts.""" trustee_user_id = base.getid(trustee_user) trustor_user_id = base.getid(trustor_user) return super(TrustManager, self).list(trustee_user_id=trustee_user_id, trustor_user_id=trustor_user_id, **kwargs) def get(self, trust): """Get a specific trust.""" return super(TrustManager, self).get(trust_id=base.getid(trust)) def delete(self, trust): """Delete a trust.""" return super(TrustManager, self).delete(trust_id=base.getid(trust)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/credentials.py0000664000175000017500000001211500000000000024445 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Credential(base.Resource): """Represents an Identity credential. Attributes: * id: a uuid that identifies the credential * user_id: user ID to which credential belongs * type: the type of credential * blob: the text that represents the credential * project_id: project ID which limits the scope of the credential """ pass class CredentialManager(base.CrudManager): """Manager class for manipulating Identity credentials.""" resource_class = Credential collection_key = 'credentials' key = 'credential' def create(self, user, type, blob, project=None, **kwargs): """Create a credential. :param user: the user to which the credential belongs :type user: str or :class:`keystoneclient.v3.users.User` :param str type: the type of the credential, valid values are: ``ec2``, ``cert`` or ``totp`` :param str blob: the arbitrary blob of the credential data, to be parsed according to the type :param project: the project which limits the scope of the credential, this attribbute is mandatory if the credential type is ec2 :type project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server :returns: the created credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).create( user_id=base.getid(user), type=type, blob=blob, project_id=base.getid(project), **kwargs) def get(self, credential): """Retrieve a credential. :param credential: the credential to be retrieved from the server :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :returns: the specified credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).get( credential_id=base.getid(credential)) def list(self, **kwargs): """List credentials. :param kwargs: If user_id or type is specified then credentials will be filtered accordingly. :returns: a list of credentials :rtype: list of :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).list(**kwargs) def update(self, credential, user, type=None, blob=None, project=None, **kwargs): """Update a credential. :param credential: the credential to be updated on the server :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :param user: the new user to which the credential belongs :type user: str or :class:`keystoneclient.v3.users.User` :param str type: the new type of the credential, valid values are: ``ec2``, ``cert`` or ``totp`` :param str blob: the new blob of the credential data and may be removed in the future release. :param project: the new project which limits the scope of the credential, this attribute is mandatory if the credential type is ec2 :type project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server :returns: the updated credential :rtype: :class:`keystoneclient.v3.credentials.Credential` """ return super(CredentialManager, self).update( credential_id=base.getid(credential), user_id=base.getid(user), type=type, blob=blob, project_id=base.getid(project), **kwargs) def delete(self, credential): """Delete a credential. :param credential: the credential to be deleted :type credential: str or :class:`keystoneclient.v3.credentials.Credential` :returns: response object with 204 status :rtype: :class:`requests.models.Response` """ return super(CredentialManager, self).delete( credential_id=base.getid(credential)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/domain_configs.py0000664000175000017500000001050200000000000025125 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class DomainConfig(base.Resource): """An object representing a domain config association. This resource object does not necessarily contain fixed attributes, as new attributes are added in the server, they are supported here directly. The currently supported configs are `identity` and `ldap`. """ pass class DomainConfigManager(base.Manager): """Manager class for manipulating domain config associations.""" resource_class = DomainConfig key = 'config' def build_url(self, domain): return '/domains/%s/config' % base.getid(domain) def create(self, domain, config): """Create a config for a domain. :param domain: the domain where the config is going to be applied. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :param dict config: a dictionary of domain configurations. Example of the ``config`` parameter:: { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://myldap.com:389/", "user_tree_dn": "ou=Users,dc=my_new_root,dc=org" } } :returns: the created domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) body = {self.key: config} return super(DomainConfigManager, self)._put( base_url, body=body, response_key=self.key) def get(self, domain): """Get a config for a domain. :param domain: the domain for which the config is defined. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :returns: the domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) return super(DomainConfigManager, self)._get(base_url, self.key) def update(self, domain, config): """Update a config for a domain. :param domain: the domain where the config is going to be updated. :type domain: str or :py:class:`keystoneclient.v3.domains.Domain` :param dict config: a dictionary of domain configurations. Example of the ``config`` parameter:: { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://myldap.com:389/", "user_tree_dn": "ou=Users,dc=my_new_root,dc=org" } } :returns: the updated domain config returned from server. :rtype: :class:`keystoneclient.v3.domain_configs.DomainConfig` """ base_url = self.build_url(domain) body = {self.key: config} return super(DomainConfigManager, self)._patch( base_url, body=body, response_key=self.key) def delete(self, domain): """Delete a config for a domain. :param domain: the domain which the config will be deleted on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ base_url = self.build_url(domain) return super(DomainConfigManager, self)._delete(url=base_url) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for domain configs')) def list(self, **kwargs): raise exceptions.MethodNotImplemented( _('List not supported for domain configs')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/domains.py0000664000175000017500000000775200000000000023615 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Domain(base.Resource): """Represents an Identity domain. Attributes: * id: a uuid that identifies the domain * name: the name of the domain * description: a description of the domain * enabled: determines whether the domain is enabled """ pass class DomainManager(base.CrudManager): """Manager class for manipulating Identity domains.""" resource_class = Domain collection_key = 'domains' key = 'domain' def create(self, name, description=None, enabled=True, **kwargs): """Create a domain. :param str name: the name of the domain. :param str description: a description of the domain. :param bool enabled: whether the domain is enabled. :param kwargs: any other attribute provided will be passed to the server. :returns: the created domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).create( name=name, description=description, enabled=enabled, **kwargs) def get(self, domain): """Retrieve a domain. :param domain: the domain to be retrieved from the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: the specified domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).get( domain_id=base.getid(domain)) def list(self, **kwargs): """List domains. :param kwargs: allows filter criteria to be passed where supported by the server. :returns: a list of domains. :rtype: list of :class:`keystoneclient.v3.domains.Domain`. """ # Ref bug #1267530 we have to pass 0 for False to get the expected # results on all keystone versions if kwargs.get('enabled') is False: kwargs['enabled'] = 0 return super(DomainManager, self).list(**kwargs) def update(self, domain, name=None, description=None, enabled=None, **kwargs): """Update a domain. :param domain: the domain to be updated on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str name: the new name of the domain. :param str description: the new description of the domain. :param bool enabled: whether the domain is enabled. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated domain returned from server. :rtype: :class:`keystoneclient.v3.domains.Domain` """ return super(DomainManager, self).update( domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def delete(self, domain): """Delete a domain. :param domain: the domain to be deleted on the server. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(DomainManager, self).delete( domain_id=base.getid(domain)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/ec2.py0000664000175000017500000000735400000000000022632 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 keystoneclient import base class EC2(base.Resource): """Represents an EC2 resource. Attributes: * id: a string that identifies the EC2 resource. * user_id: the ID field of a pre-existing user in the backend. * project_id: the ID field of a pre-existing project in the backend. * access: a string representing access key of the access/secret pair. * secret: a string representing the secret of the access/secret pair. """ def __repr__(self): """Return string representation of EC2 resource information.""" return "" % self._info class EC2Manager(base.ManagerWithFind): resource_class = EC2 def create(self, user_id, project_id): """Create a new access/secret pair. :param user_id: the ID of the user having access/secret pair. :type user_id: str or :class:`keystoneclient.v3.users.User` :param project_id: the ID of the project having access/secret pair. :type project_id: str or :class:`keystoneclient.v3.projects.Project` :returns: the created access/secret pair returned from server. :rtype: :class:`keystoneclient.v3.ec2.EC2` """ # NOTE(jamielennox): Yes, this uses tenant_id as a key even though we # are in the v3 API. return self._post('/users/%s/credentials/OS-EC2' % user_id, body={'tenant_id': project_id}, response_key="credential") def get(self, user_id, access): """Retrieve an access/secret pair for a given access key. :param user_id: the ID of the user whose access/secret pair will be retrieved from the server. :type user_id: str or :class:`keystoneclient.v3.users.User` :param str access: the access key whose access/secret pair will be retrieved from the server. :returns: the specified access/secret pair returned from server. :rtype: :class:`keystoneclient.v3.ec2.EC2` """ url = "/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access)) return self._get(url, response_key="credential") def list(self, user_id): """List access/secret pairs for a given user. :param str user_id: the ID of the user having access/secret pairs will be listed. :returns: a list of access/secret pairs. :rtype: list of :class:`keystoneclient.v3.ec2.EC2` """ return self._list("/users/%s/credentials/OS-EC2" % user_id, response_key="credentials") def delete(self, user_id, access): """Delete an access/secret pair. :param user_id: the ID of the user whose access/secret pair will be deleted on the server. :type user_id: str or :class:`keystoneclient.v3.users.User` :param str access: the access key whose access/secret pair will be deleted on the server. :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return self._delete("/users/%s/credentials/OS-EC2/%s" % (user_id, base.getid(access))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/endpoint_groups.py0000664000175000017500000001153100000000000025370 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 keystoneclient import base class EndpointGroup(base.Resource): """Represents an identity endpoint group. Attributes: * id: a UUID that identifies the endpoint group * name: the endpoint group name * description: the endpoint group description * filters: representation of filters in the format of JSON that define what endpoint entities are part of the group """ pass class EndpointGroupManager(base.CrudManager): """Manager class for Endpoint Groups.""" resource_class = EndpointGroup collection_key = 'endpoint_groups' key = 'endpoint_group' base_url = 'OS-EP-FILTER' def create(self, name, filters, description=None, **kwargs): """Create an endpoint group. :param str name: the name of the endpoint group. :param str filters: representation of filters in the format of JSON that define what endpoint entities are part of the group. :param str description: a description of the endpoint group. :param kwargs: any other attribute provided will be passed to the server. :returns: the created endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).create( name=name, filters=filters, description=description, **kwargs) def get(self, endpoint_group): """Retrieve an endpoint group. :param endpoint_group: the endpoint group to be retrieved from the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: the specified endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).get( endpoint_group_id=base.getid(endpoint_group)) def check(self, endpoint_group): """Check if an endpoint group exists. :param endpoint_group: the endpoint group to be checked against the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: none if the specified endpoint group exists. """ return super(EndpointGroupManager, self).head( endpoint_group_id=base.getid(endpoint_group)) def list(self, **kwargs): """List endpoint groups. Any parameter provided will be passed to the server. :returns: a list of endpoint groups. :rtype: list of :class:`keystoneclient.v3.endpoint_groups.EndpointGroup`. """ return super(EndpointGroupManager, self).list(**kwargs) def update(self, endpoint_group, name=None, filters=None, description=None, **kwargs): """Update an endpoint group. :param str name: the new name of the endpoint group. :param str filters: the new representation of filters in the format of JSON that define what endpoint entities are part of the group. :param str description: the new description of the endpoint group. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated endpoint group returned from server. :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` """ return super(EndpointGroupManager, self).update( endpoint_group_id=base.getid(endpoint_group), name=name, filters=filters, description=description, **kwargs) def delete(self, endpoint_group): """Delete an endpoint group. :param endpoint_group: the endpoint group to be deleted on the server. :type endpoint_group: str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(EndpointGroupManager, self).delete( endpoint_group_id=base.getid(endpoint_group)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/endpoints.py0000664000175000017500000001540100000000000024154 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ VALID_INTERFACES = ['public', 'admin', 'internal'] class Endpoint(base.Resource): """Represents an Identity endpoint. Attributes: * id: a uuid that identifies the endpoint * interface: 'public', 'admin' or 'internal' network interface * region: geographic location of the endpoint * service_id: service to which the endpoint belongs * url: fully qualified service endpoint * enabled: determines whether the endpoint appears in the service catalog """ pass class EndpointManager(base.CrudManager): """Manager class for manipulating Identity endpoints.""" resource_class = Endpoint collection_key = 'endpoints' key = 'endpoint' def _validate_interface(self, interface): if interface is not None and interface not in VALID_INTERFACES: msg = _('"interface" must be one of: %s') msg %= ', '.join(VALID_INTERFACES) raise exceptions.ValidationError(msg) def create(self, service, url, interface=None, region=None, enabled=True, **kwargs): """Create an endpoint. :param service: the service to which the endpoint belongs. :type service: str or :class:`keystoneclient.v3.services.Service` :param str url: the URL of the fully qualified service endpoint. :param str interface: the network interface of the endpoint. Valid values are: ``public``, ``admin`` or ``internal``. :param region: the region to which the endpoint belongs. :type region: str or :class:`keystoneclient.v3.regions.Region` :param bool enabled: whether the endpoint is enabled or not, determining if it appears in the service catalog. :param kwargs: any other attribute provided will be passed to the server. :returns: the created endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ self._validate_interface(interface) return super(EndpointManager, self).create( service_id=base.getid(service), interface=interface, url=url, region=region, enabled=enabled, **kwargs) def get(self, endpoint): """Retrieve an endpoint. :param endpoint: the endpoint to be retrieved from the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :returns: the specified endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ return super(EndpointManager, self).get( endpoint_id=base.getid(endpoint)) def list(self, service=None, interface=None, region=None, enabled=None, region_id=None, **kwargs): """List endpoints. :param service: the service of the endpoints to be filtered on. :type service: str or :class:`keystoneclient.v3.services.Service` :param str interface: the network interface of the endpoints to be filtered on. Valid values are: ``public``, ``admin`` or ``internal``. :param bool enabled: whether to return enabled or disabled endpoints. :param str region_id: filter endpoints by the region_id attribute. If both region and region_id are specified, region takes precedence. :param kwargs: any other attribute provided will filter endpoints on. :returns: a list of endpoints. :rtype: list of :class:`keystoneclient.v3.endpoints.Endpoint` """ # NOTE(lhcheng): region filter is not supported by keystone, # region_id should be used instead. Consider removing the # region argument in the next release. self._validate_interface(interface) return super(EndpointManager, self).list( service_id=base.getid(service), interface=interface, region_id=region_id or base.getid(region), enabled=enabled, **kwargs) def update(self, endpoint, service=None, url=None, interface=None, region=None, enabled=None, **kwargs): """Update an endpoint. :param endpoint: the endpoint to be updated on the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :param service: the new service to which the endpoint belongs. :type service: str or :class:`keystoneclient.v3.services.Service` :param str url: the new URL of the fully qualified service endpoint. :param str interface: the new network interface of the endpoint. Valid values are: ``public``, ``admin`` or ``internal``. :param region: the new region to which the endpoint belongs. :type region: str or :class:`keystoneclient.v3.regions.Region` :param bool enabled: determining if the endpoint appears in the service catalog by enabling or disabling it. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated endpoint returned from server. :rtype: :class:`keystoneclient.v3.endpoints.Endpoint` """ self._validate_interface(interface) return super(EndpointManager, self).update( endpoint_id=base.getid(endpoint), service_id=base.getid(service), interface=interface, url=url, region=region, enabled=enabled, **kwargs) def delete(self, endpoint): """Delete an endpoint. :param endpoint: the endpoint to be deleted on the server. :type endpoint: str or :class:`keystoneclient.v3.endpoints.Endpoint` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(EndpointManager, self).delete( endpoint_id=base.getid(endpoint)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/groups.py0000664000175000017500000001075500000000000023477 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Group(base.Resource): """Represents an Identity user group. Attributes: * id: a uuid that identifies the group * name: group name * description: group description """ def update(self, name=None, description=None): kwargs = { 'name': name if name is not None else self.name, 'description': (description if description is not None else self.description), } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval class GroupManager(base.CrudManager): """Manager class for manipulating Identity groups.""" resource_class = Group collection_key = 'groups' key = 'group' def create(self, name, domain=None, description=None, **kwargs): """Create a group. :param str name: the name of the group. :param domain: the domain of the group. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: a description of the group. :param kwargs: any other attribute provided will be passed to the server. :returns: the created group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).create( name=name, domain_id=base.getid(domain), description=description, **kwargs) def list(self, user=None, domain=None, **kwargs): """List groups. :param user: the user of the groups to be filtered on. :type user: str or :class:`keystoneclient.v3.users.User` :param domain: the domain of the groups to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param kwargs: any other attribute provided will filter groups on. :returns: a list of groups. :rtype: list of :class:`keystoneclient.v3.groups.Group`. """ if user: base_url = '/users/%s' % base.getid(user) else: base_url = None return super(GroupManager, self).list( base_url=base_url, domain_id=base.getid(domain), **kwargs) def get(self, group): """Retrieve a group. :param group: the group to be retrieved from the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: the specified group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).get( group_id=base.getid(group)) def update(self, group, name=None, description=None, **kwargs): """Update a group. :param group: the group to be updated on the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :param str name: the new name of the group. :param str description: the new description of the group. :param kwargs: any other attribute provided will be passed to server. :returns: the updated group returned from server. :rtype: :class:`keystoneclient.v3.groups.Group` """ return super(GroupManager, self).update( group_id=base.getid(group), name=name, description=description, **kwargs) def delete(self, group): """Delete a group. :param group: the group to be deleted on the server. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(GroupManager, self).delete( group_id=base.getid(group)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/limits.py0000664000175000017500000001275400000000000023462 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 keystoneclient import base class Limit(base.Resource): """Represents a project limit. Attributes: * id: a UUID that identifies the project limit * service_id: a UUID that identifies the service for the limit * region_id: a UUID that identifies the region for the limit * project_id: a UUID that identifies the project for the limit * resource_name: the name of the resource to limit * resource_limit: the limit to apply to the project * description: a description for the project limit """ pass class LimitManager(base.CrudManager): """Manager class for project limits.""" resource_class = Limit collection_key = 'limits' key = 'limit' def create(self, project, service, resource_name, resource_limit, description=None, region=None, **kwargs): """Create a project-specific limit. :param project: the project to create a limit for. :type project: str or :class:`keystoneclient.v3.projects.Project` :param service: the service that owns the resource to limit. :type service: str or :class:`keystoneclient.v3.services.Service` :param resource_name: the name of the resource to limit :type resource_name: str :param resource_limit: the quantity of the limit :type resource_limit: int :param description: a description of the limit :type description: str :param region: region the limit applies to :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: a reference of the created limit :rtype: :class:`keystoneclient.v3.limits.Limit` """ limit_data = base.filter_none( project_id=base.getid(project), service_id=base.getid(service), resource_name=resource_name, resource_limit=resource_limit, description=description, region_id=base.getid(region), **kwargs ) body = {self.collection_key: [limit_data]} resp, body = self.client.post('/limits', body=body) limit = body[self.collection_key].pop() return self._prepare_return_value(resp, self.resource_class( self, limit)) def update(self, limit, project=None, service=None, resource_name=None, resource_limit=None, description=None, **kwargs): """Update a project-specific limit. :param limit: a limit to update :param project: the project ID of the limit to update :type project: str or :class:`keystoneclient.v3.projects.Project` :param resource_limit: the limit of the limit's resource to update :type: resource_limit: int :param description: a description of the limit :type description: str :returns: a reference of the updated limit. :rtype: :class:`keystoneclient.v3.limits.Limit` """ return super(LimitManager, self).update( limit_id=base.getid(limit), project_id=base.getid(project), service_id=base.getid(service), resource_name=resource_name, resource_limit=resource_limit, description=description, **kwargs ) def get(self, limit): """Retrieve a project limit. :param limit: the project-specific limit to be retrieved. :type limit: str or :class:`keystoneclient.v3.limit.Limit` :returns: a project-specific limit :rtype: :class:`keystoneclient.v3.limit.Limit` """ return super(LimitManager, self).get(limit_id=base.getid(limit)) def list(self, service=None, region=None, resource_name=None, **kwargs): """List project-specific limits. Any parameter provided will be passed to the server as a filter :param service: service to filter limits by :type service: UUID or :class:`keystoneclient.v3.services.Service` :param region: region to filter limits by :type region: UUID or :class:`keystoneclient.v3.regions.Region` :param resource_name: the name of the resource to filter limits by :type resource_name: str :returns: a list of project-specific limits. :rtype: list of :class:`keystoneclient.v3.limits.Limit` """ return super(LimitManager, self).list( service_id=base.getid(service), region_id=base.getid(region), resource_name=resource_name, **kwargs ) def delete(self, limit): """Delete a project-specific limit. :param limit: the project-specific limit to be deleted. :type limit: str or :class:`keystoneclient.v3.limit.Limit` :returns: Response object with 204 status :rtype: :class:`requests.models.Response` """ return super(LimitManager, self).delete(limit_id=base.getid(limit)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/policies.py0000664000175000017500000000755000000000000023766 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Policy(base.Resource): """Represents an Identity policy. Attributes: * id: a uuid that identifies the policy * blob: a policy document (blob) * type: the MIME type of the policy blob """ def update(self, blob=None, type=None): kwargs = { 'blob': blob if blob is not None else self.blob, 'type': type if type is not None else self.type, } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval class PolicyManager(base.CrudManager): """Manager class for manipulating Identity policies.""" resource_class = Policy collection_key = 'policies' key = 'policy' def create(self, blob, type='application/json', **kwargs): """Create a policy. :param str blob: the policy document. :param str type: the MIME type of the policy blob. :param kwargs: any other attribute provided will be passed to the server. :returns: the created policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).create( blob=blob, type=type, **kwargs) def get(self, policy): """Retrieve a policy. :param policy: the policy to be retrieved from the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :returns: the specified policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).get( policy_id=base.getid(policy)) def list(self, **kwargs): """List policies. :param kwargs: allows filter criteria to be passed where supported by the server. :returns: a list of policies. :rtype: list of :class:`keystoneclient.v3.policies.Policy`. """ return super(PolicyManager, self).list(**kwargs) def update(self, policy, blob=None, type=None, **kwargs): """Update a policy. :param policy: the policy to be updated on the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :param str blob: the new policy document. :param str type: the new MIME type of the policy blob. :param kwargs: any other attribute provided will be passed to the server. :returns: the updated policy returned from server. :rtype: :class:`keystoneclient.v3.policies.Policy` """ return super(PolicyManager, self).update( policy_id=base.getid(policy), blob=blob, type=type, **kwargs) def delete(self, policy): """Delete a policy. :param policy: the policy to be deleted on the server. :type policy: str or :class:`keystoneclient.v3.policies.Policy` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(PolicyManager, self).delete( policy_id=base.getid(policy)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/projects.py0000664000175000017500000003116200000000000024004 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib.parse from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class Project(base.Resource): """Represents an Identity project. Attributes: * id: a uuid that identifies the project * name: project name * description: project description * enabled: boolean to indicate if project is enabled * parent_id: a uuid representing this project's parent in hierarchy * parents: a list or a structured dict containing the parents of this project in the hierarchy * subtree: a list or a structured dict containing the subtree of this project in the hierarchy """ def update(self, name=None, description=None, enabled=None): kwargs = { 'name': name if name is not None else self.name, 'description': (description if description is not None else self.description), 'enabled': enabled if enabled is not None else self.enabled, } try: retval = self.manager.update(self.id, **kwargs) self = retval except Exception: retval = None return retval def add_tag(self, tag): self.manager.add_tag(self, tag) def update_tags(self, tags): return self.manager.update_tags(self, tags) def delete_tag(self, tag): self.manager.delete_tag(self, tag) def delete_all_tags(self): return self.manager.update_tags(self, []) def list_tags(self): return self.manager.list_tags(self) def check_tag(self, tag): return self.manager.check_tag(self, tag) class ProjectManager(base.CrudManager): """Manager class for manipulating Identity projects.""" resource_class = Project collection_key = 'projects' key = 'project' def create(self, name, domain, description=None, enabled=True, parent=None, **kwargs): """Create a project. :param str name: the name of the project. :param domain: the domain of the project. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: the description of the project. :param bool enabled: whether the project is enabled. :param parent: the parent of the project in the hierarchy. :type parent: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server. :returns: the created project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` """ # NOTE(rodrigods): the API must be backwards compatible, so if an # application was passing a 'parent_id' before as kwargs, the call # should not fail. If both 'parent' and 'parent_id' are provided, # 'parent' will be preferred. if parent: kwargs['parent_id'] = base.getid(parent) return super(ProjectManager, self).create( domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def list(self, domain=None, user=None, parent=None, **kwargs): """List projects. :param domain: the domain of the projects to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param user: filter in projects the specified user has role assignments on. :type user: str or :class:`keystoneclient.v3.users.User` :param parent: filter in projects the specified project is a parent for :type parent: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will filter projects on. Project tags filter keyword: ``tags``, ``tags_any``, ``not_tags``, and ``not_tags_any``. tag attribute type string. Pass in a comma separated string to filter with multiple tags. :returns: a list of projects. :rtype: list of :class:`keystoneclient.v3.projects.Project` """ base_url = '/users/%s' % base.getid(user) if user else None projects = super(ProjectManager, self).list( base_url=base_url, domain_id=base.getid(domain), parent_id=base.getid(parent), fallback_to_auth=True, **kwargs) base_response = None list_data = projects if self.client.include_metadata: base_response = projects list_data = projects.data base_response.data = list_data for p in list_data: p.tags = getattr(p, 'tags', []) if self.client.include_metadata: base_response.data = list_data return base_response if self.client.include_metadata else list_data def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids, parents_as_list): if parents_as_ids and parents_as_list: msg = _('Specify either parents_as_ids or parents_as_list ' 'parameters, not both') raise exceptions.ValidationError(msg) def _check_not_subtree_as_ids_and_subtree_as_list(self, subtree_as_ids, subtree_as_list): if subtree_as_ids and subtree_as_list: msg = _('Specify either subtree_as_ids or subtree_as_list ' 'parameters, not both') raise exceptions.ValidationError(msg) def get(self, project, subtree_as_list=False, parents_as_list=False, subtree_as_ids=False, parents_as_ids=False): """Retrieve a project. :param project: the project to be retrieved from the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool subtree_as_list: retrieve projects below this project in the hierarchy as a flat list. It only includes the projects in which the current user has role assignments on. :param bool parents_as_list: retrieve projects above this project in the hierarchy as a flat list. It only includes the projects in which the current user has role assignments on. :param bool subtree_as_ids: retrieve the IDs from the projects below this project in the hierarchy as a structured dictionary. :param bool parents_as_ids: retrieve the IDs from the projects above this project in the hierarchy as a structured dictionary. :returns: the specified project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` :raises keystoneclient.exceptions.ValidationError: if subtree_as_list and subtree_as_ids or parents_as_list and parents_as_ids are included at the same time in the call. """ self._check_not_parents_as_ids_and_parents_as_list( parents_as_ids, parents_as_list) self._check_not_subtree_as_ids_and_subtree_as_list( subtree_as_ids, subtree_as_list) # According to the API spec, the query params are key only query_params = [] if subtree_as_list: query_params.append('subtree_as_list') if subtree_as_ids: query_params.append('subtree_as_ids') if parents_as_list: query_params.append('parents_as_list') if parents_as_ids: query_params.append('parents_as_ids') query = self.build_key_only_query(query_params) dict_args = {'project_id': base.getid(project)} url = self.build_url(dict_args_in_out=dict_args) p = self._get(url + query, self.key) p.tags = getattr(p, 'tags', []) return p def find(self, **kwargs): p = super(ProjectManager, self).find(**kwargs) p.tags = getattr(p, 'tags', []) return p def update(self, project, name=None, domain=None, description=None, enabled=None, **kwargs): """Update a project. :param project: the project to be updated on the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :param str name: the new name of the project. :param domain: the new domain of the project. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param str description: the new description of the project. :param bool enabled: whether the project is enabled. :param kwargs: any other attribute provided will be passed to server. :returns: the updated project returned from server. :rtype: :class:`keystoneclient.v3.projects.Project` """ return super(ProjectManager, self).update( project_id=base.getid(project), domain_id=base.getid(domain), name=name, description=description, enabled=enabled, **kwargs) def delete(self, project): """Delete a project. :param project: the project to be deleted on the server. :type project: str or :class:`keystoneclient.v3.projects.Project` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(ProjectManager, self).delete( project_id=base.getid(project)) def add_tag(self, project, tag): """Add a tag to a project. :param project: project to add a tag to. :param tag: str name of tag. """ url = "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag)) return self._put(url) def update_tags(self, project, tags): """Update tag list of a project. Replaces current tag list with list specified in tags parameter. :param project: project to update. :param tags: list of str tag names to add to the project :returns: list of tags """ url = "/projects/%s/tags" % base.getid(project) for tag in tags: tag = urllib.parse.quote(tag) resp, body = self.client.put(url, body={"tags": tags}) return self._prepare_return_value(resp, body['tags']) def delete_tag(self, project, tag): """Remove tag from project. :param projectd: project to remove tag from. :param tag: str name of tag to remove from project """ return self._delete( "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag))) def list_tags(self, project): """List tags associated with project. :param project: project to list tags for. :returns: list of str tag names """ url = "/projects/%s/tags" % base.getid(project) resp, body = self.client.get(url) return self._prepare_return_value(resp, body['tags']) def check_tag(self, project, tag): """Check if tag is associated with project. :param project: project to check tags for. :param tag: str name of tag :returns: true if tag is associated, false otherwise """ url = "/projects/%s/tags/%s" % (base.getid(project), urllib.parse.quote(tag)) try: resp, body = self.client.head(url) # no errors means found the tag return self._prepare_return_value(resp, True) except exceptions.HttpError as ex: # return false with request_id if include_metadata=True return self._prepare_return_value(ex.response, False) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/regions.py0000664000175000017500000001130500000000000023616 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 debtcollector import removals from keystoneclient import base class Region(base.Resource): """Represents a Catalog region. Attributes: * id: a string that identifies the region. * description: a string that describes the region. * parent_region_id: a pre-existing region in the backend or its ID field. Allows for hierarchical region organization. * enabled: determines whether the endpoint appears in the catalog. """ pass class RegionManager(base.CrudManager): """Manager class for manipulating Identity regions.""" resource_class = Region collection_key = 'regions' key = 'region' @removals.removed_kwarg( 'enabled', message='The enabled parameter is deprecated.', version='3.18.0', removal_version='4.0.0') def create(self, id=None, description=None, enabled=True, parent_region=None, **kwargs): """Create a region. :param str id: the unique identifier of the region. If not specified an ID will be created by the server. :param str description: the description of the region. :param bool enabled: whether the region is enabled or not, determining if it appears in the catalog. :param parent_region: the parent of the region in the hierarchy. :type parent_region: str or :class:`keystoneclient.v3.regions.Region` :param kwargs: any other attribute provided will be passed to the server. :returns: the created region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).create( id=id, description=description, enabled=enabled, parent_region_id=base.getid(parent_region), **kwargs) def get(self, region): """Retrieve a region. :param region: the region to be retrieved from the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: the specified region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).get( region_id=base.getid(region)) def list(self, **kwargs): """List regions. :param kwargs: any attributes provided will filter regions on. :returns: a list of regions. :rtype: list of :class:`keystoneclient.v3.regions.Region`. """ return super(RegionManager, self).list( **kwargs) @removals.removed_kwarg( 'enabled', message='The enabled parameter is deprecated.', version='3.18.0', removal_version='4.0.0') def update(self, region, description=None, enabled=None, parent_region=None, **kwargs): """Update a region. :param region: the region to be updated on the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :param str description: the new description of the region. :param bool enabled: determining if the region appears in the catalog by enabling or disabling it. :param parent_region: the new parent of the region in the hierarchy. :type parent_region: str or :class:`keystoneclient.v3.regions.Region` :param kwargs: any other attribute provided will be passed to server. :returns: the updated region returned from server. :rtype: :class:`keystoneclient.v3.regions.Region` """ return super(RegionManager, self).update( region_id=base.getid(region), description=description, enabled=enabled, parent_region_id=base.getid(parent_region), **kwargs) def delete(self, region): """Delete a region. :param region: the region to be deleted on the server. :type region: str or :class:`keystoneclient.v3.regions.Region` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(RegionManager, self).delete( region_id=base.getid(region)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/registered_limits.py0000664000175000017500000001432700000000000025675 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 keystoneclient import base class RegisteredLimit(base.Resource): """Represents a registered limit. Attributes: * id: a UUID that identifies the registered limit * service_id: a UUID that identifies the service for the limit * region_id: a UUID that identifies the region for the limit * resource_name: the name of the resource to limit * default_limit: the default limit for projects to assume * description: a description of the registered limit """ pass class RegisteredLimitManager(base.CrudManager): """Manager class for registered limits.""" resource_class = RegisteredLimit collection_key = 'registered_limits' key = 'registered_limit' def create(self, service, resource_name, default_limit, description=None, region=None, **kwargs): """Create a registered limit. :param service: a UUID that identifies the service for the limit. :type service: str :param resource_name: the name of the resource to limit. :type resource_name: str :param default_limit: the default limit for projects to assume. :type default_limit: int :param description: a string that describes the limit :type description: str :param region: a UUID that identifies the region for the limit. :type region: str :returns: a reference of the created registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ # NOTE(lbragstad): Keystone's registered limit API supports creation of # limits in batches. This client accepts a single limit and passes it # to the identity service as a list of a single item. limit_data = base.filter_none( service_id=base.getid(service), resource_name=resource_name, default_limit=default_limit, description=description, region_id=base.getid(region), **kwargs ) body = {self.collection_key: [limit_data]} resp, body = self.client.post('/registered_limits', body=body) registered_limit = body[self.collection_key].pop() return self._prepare_return_value(resp, self.resource_class( self, registered_limit)) def update(self, registered_limit, service=None, resource_name=None, default_limit=None, description=None, region=None, **kwargs): """Update a registered limit. :param registered_limit: the UUID or reference of the registered limit to update. :param registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :param service: a UUID that identifies the service for the limit. :type service: str :param resource_name: the name of the resource to limit. :type resource_name: str :param default_limit: the default limit for projects to assume. :type default_limit: int :param description: a string that describes the limit :type description: str :param region: a UUID that identifies the region for the limit. :type region: str :returns: a reference of the updated registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).update( registered_limit_id=base.getid(registered_limit), service_id=base.getid(service), resource_name=resource_name, default_limit=default_limit, description=description, region=region, **kwargs ) def get(self, registered_limit): """Retrieve a registered limit. :param registered_limit: the registered limit to get. :type registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :returns: a specific registered limit. :rtype: :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).get( registered_limit_id=base.getid(registered_limit)) def list(self, service=None, resource_name=None, region=None, **kwargs): """List registered limits. Any parameter provided will be passed to the server as a filter. :param service: filter registered limits by service :type service: a UUID or :class:`keystoneclient.v3.services.Service` :param resource_name: filter registered limits by resource name :type resource_name: str :param region: filter registered limits by region :type region: a UUID or :class:`keystoneclient.v3.regions.Region` :returns: a list of registered limits. :rtype: list of :class:`keystoneclient.v3.registered_limits.RegisteredLimit` """ return super(RegisteredLimitManager, self).list( service_id=base.getid(service), resource_name=resource_name, region_id=base.getid(region), **kwargs) def delete(self, registered_limit): """Delete a registered limit. :param registered_limit: the registered limit to delete. :type registered_limit: str or :class:`keystoneclient.v3.registered_limits.RegisteredLimit` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ registered_limit_id = base.getid(registered_limit) return super(RegisteredLimitManager, self).delete( registered_limit_id=registered_limit_id ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/role_assignments.py0000664000175000017500000001337000000000000025530 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 keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class RoleAssignment(base.Resource): """Represents an Identity role assignment. Attributes: * role: an object which contains a role uuid * user or group: an object which contains either a user or group uuid * scope: an object which has either a project or domain object containing an uuid """ pass class RoleAssignmentManager(base.CrudManager): """Manager class for manipulating Identity roles assignments.""" resource_class = RoleAssignment collection_key = 'role_assignments' key = 'role_assignment' def _check_not_user_and_group(self, user, group): if user and group: msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) def _check_not_domain_and_project(self, domain, project): if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) def _check_not_system_and_domain(self, system, domain): if system and domain: msg = _('Specify either system or domain, not both') raise exceptions.ValidationError(msg) def _check_not_system_and_project(self, system, project): if system and project: msg = _('Specify either system or project, not both') raise exceptions.ValidationError(msg) def _check_system_value(self, system): if system and system != 'all': msg = _("Only a system scope of 'all' is currently supported") raise exceptions.ValidationError(msg) def list(self, user=None, group=None, project=None, domain=None, system=False, role=None, effective=False, os_inherit_extension_inherited_to=None, include_subtree=False, include_names=False): """List role assignments. If no arguments are provided, all role assignments in the system will be listed. If both user and group are provided, a ValidationError will be raised. If both domain and project are provided, it will also raise a ValidationError. :param user: User to be used as query filter. (optional) :param group: Group to be used as query filter. (optional) :param project: Project to be used as query filter. (optional) :param domain: Domain to be used as query filter. (optional) :param system: Boolean to be used to filter system assignments. (optional) :param role: Role to be used as query filter. (optional) :param boolean effective: return effective role assignments. (optional) :param string os_inherit_extension_inherited_to: return inherited role assignments for either 'projects' or 'domains'. (optional) :param boolean include_subtree: Include subtree (optional) :param boolean include_names: Display names instead of IDs. (optional) """ self._check_not_user_and_group(user, group) self._check_not_domain_and_project(domain, project) self._check_not_system_and_domain(system, domain) self._check_not_system_and_project(system, project) self._check_system_value(system) query_params = {} if user: query_params['user.id'] = base.getid(user) if group: query_params['group.id'] = base.getid(group) if project: query_params['scope.project.id'] = base.getid(project) if domain: query_params['scope.domain.id'] = base.getid(domain) if system: query_params['scope.system'] = system if role: query_params['role.id'] = base.getid(role) if effective: query_params['effective'] = effective if include_names: query_params['include_names'] = include_names if os_inherit_extension_inherited_to: query_params['scope.OS-INHERIT:inherited_to'] = ( os_inherit_extension_inherited_to) if include_subtree: query_params['include_subtree'] = include_subtree return super(RoleAssignmentManager, self).list(**query_params) def create(self, **kwargs): raise exceptions.MethodNotImplemented( _('Create not supported for role assignments')) def update(self, **kwargs): raise exceptions.MethodNotImplemented( _('Update not supported for role assignments')) def get(self, **kwargs): raise exceptions.MethodNotImplemented( _('Get not supported for role assignments')) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for role assignments')) def put(self, **kwargs): raise exceptions.MethodNotImplemented( _('Put not supported for role assignments')) def delete(self, **kwargs): raise exceptions.MethodNotImplemented( _('Delete not supported for role assignments')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/roles.py0000664000175000017500000006036400000000000023305 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from debtcollector import removals from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class Role(base.Resource): """Represents an Identity role. Attributes: * id: a uuid that identifies the role * name: user-facing identifier * domain: optional domain for the role """ pass class InferenceRule(base.Resource): """Represents a rule that states one role implies another. Attributes: * prior_role: this role implies the other * implied_role: this role is implied by the other """ pass class RoleManager(base.CrudManager): """Manager class for manipulating Identity roles.""" resource_class = Role collection_key = 'roles' key = 'role' deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager' def _role_grants_base_url(self, user, group, system, domain, project, use_inherit_extension): # When called, we have already checked that only one of user & group # and one of domain & project have been specified params = {} if project: params['project_id'] = base.getid(project) base_url = '/projects/%(project_id)s' elif domain: params['domain_id'] = base.getid(domain) base_url = '/domains/%(domain_id)s' elif system: if system == 'all': base_url = '/system' else: # NOTE(lbragstad): If we've made it this far, a user is # attempting to do something with system scope that isn't # supported yet (e.g. 'all' is currently the only supported # system scope). In the future that may change but until then # we should fail like we would if a user provided a bogus # project name or domain ID. msg = _("Only a system scope of 'all' is currently supported") raise exceptions.ValidationError(msg) if use_inherit_extension: base_url = '/OS-INHERIT' + base_url if user: params['user_id'] = base.getid(user) base_url += '/users/%(user_id)s' elif group: params['group_id'] = base.getid(group) base_url += '/groups/%(group_id)s' return base_url % params def _enforce_mutually_exclusive_group(self, system, domain, project): if not system: if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) elif not (domain or project): msg = _('Must specify either system, domain, or project') raise exceptions.ValidationError(msg) elif system: if domain and project: msg = _( 'Specify either system, domain, or project, not all three.' ) raise exceptions.ValidationError(msg) if domain: msg = _('Specify either system or a domain, not both') raise exceptions.ValidationError(msg) if project: msg = _('Specify either a system or project, not both') raise exceptions.ValidationError(msg) def _require_user_xor_group(self, user, group): if user and group: msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) elif not (user or group): msg = _('Must specify either a user or group') raise exceptions.ValidationError(msg) def create(self, name, domain=None, **kwargs): """Create a role. :param str name: the name of the role. :param domain: the domain of the role. If a value is passed it is a domain-scoped role, otherwise it's a global role. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param kwargs: any other attribute provided will be passed to the server. :returns: the created role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ domain_id = None if domain: domain_id = base.getid(domain) return super(RoleManager, self).create( name=name, domain_id=domain_id, **kwargs) def get(self, role): """Retrieve a role. :param role: the role to be retrieved from the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ return super(RoleManager, self).get(role_id=base.getid(role)) def list(self, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """List roles and role grants. :param user: filter in role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: filter in role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param domain: filter in role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: filter in role grants on the specified project. Either user or group must be specified. Project, domain and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will filter roles on. :returns: a list of roles. :rtype: list of :class:`keystoneclient.v3.roles.Role` """ if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' if user or group: self._require_user_xor_group(user, group) self._enforce_mutually_exclusive_group(system, domain, project) base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited ) return super(RoleManager, self).list(base_url=base_url, **kwargs) return super(RoleManager, self).list(**kwargs) def update(self, role, name=None, **kwargs): """Update a role. :param role: the role to be updated on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :param str name: the new name of the role. :param kwargs: any other attribute provided will be passed to server. :returns: the updated role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ return super(RoleManager, self).update( role_id=base.getid(role), name=name, **kwargs) def delete(self, role): """Delete a role. When a role is deleted all the role inferences that have deleted role as prior role will be deleted as well. :param role: the role to be deleted on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(RoleManager, self).delete( role_id=base.getid(role)) def grant(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Grant a role to a user or group on a domain or project. :param role: the role to be granted on the server. :type role: str or :class:`keystoneclient.v3.roles.Role` :param user: the specified user to have the role granted on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the specified group to have the role granted on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: system information to grant the role on. Project, domain, and system are mutually exclusive. :type system: str :param domain: the domain in which the role will be granted. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the project in which the role will be granted. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the granted role returned from server. :rtype: :class:`keystoneclient.v3.roles.Role` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).put(base_url=base_url, role_id=base.getid(role), **kwargs) def check(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Check if a user or group has a role on a domain or project. :param user: check for role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: check for role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: check for role grants on the system. Project, domain, and system are mutually exclusive. :type system: str :param domain: check for role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: check for role grants on the specified project. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the specified role returned from server if it exists. :rtype: :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status if specified role doesn't exist. :rtype: :class:`requests.models.Response` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).head( base_url=base_url, role_id=base.getid(role), os_inherit_extension_inherited=os_inherit_extension_inherited, **kwargs) def revoke(self, role, user=None, group=None, system=None, domain=None, project=None, os_inherit_extension_inherited=False, **kwargs): """Revoke a role from a user or group on a domain or project. :param user: revoke role grants for the specified user on a resource. Domain or project must be specified. User and group are mutually exclusive. :type user: str or :class:`keystoneclient.v3.users.User` :param group: revoke role grants for the specified group on a resource. Domain or project must be specified. User and group are mutually exclusive. :type group: str or :class:`keystoneclient.v3.groups.Group` :param system: revoke role grants on the system. Project, domain, and system are mutually exclusive. :type system: str :param domain: revoke role grants on the specified domain. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: revoke role grants on the specified project. Either user or group must be specified. Project, domain, and system are mutually exclusive. :type project: str or :class:`keystoneclient.v3.projects.Project` :param bool os_inherit_extension_inherited: OS-INHERIT will be used. It provides the ability for projects to inherit role assignments from their domains or from parent projects in the hierarchy. :param kwargs: any other attribute provided will be passed to server. :returns: the revoked role returned from server. :rtype: list of :class:`keystoneclient.v3.roles.Role` """ self._enforce_mutually_exclusive_group(system, domain, project) self._require_user_xor_group(user, group) if os_inherit_extension_inherited: kwargs['tail'] = '/inherited_to_projects' base_url = self._role_grants_base_url( user, group, system, domain, project, os_inherit_extension_inherited) return super(RoleManager, self).delete( base_url=base_url, role_id=base.getid(role), os_inherit_extension_inherited=os_inherit_extension_inherited, **kwargs) @removals.remove(message='Use %s.create instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def create_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).create(prior_role, implied_role) @removals.remove(message='Use %s.delete instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def delete_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).delete(prior_role, implied_role) @removals.remove(message='Use %s.get instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def get_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).get(prior_role, implied_role) @removals.remove(message='Use %s.check instead.' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def check_implied(self, prior_role, implied_role, **kwargs): return InferenceRuleManager(self.client).check(prior_role, implied_role) @removals.remove(message='Use %s.list_inference_roles' % deprecation_msg, version='3.9.0', removal_version='4.0.0') def list_role_inferences(self, **kwargs): return InferenceRuleManager(self.client).list_inference_roles() class InferenceRuleManager(base.CrudManager): """Manager class for manipulating Identity inference rules.""" resource_class = InferenceRule collection_key = 'role_inferences' key = 'role_inference' def _implied_role_url_tail(self, prior_role, implied_role): base_url = ('/%(prior_role_id)s/implies/%(implied_role_id)s' % {'prior_role_id': base.getid(prior_role), 'implied_role_id': base.getid(implied_role)}) return base_url def create(self, prior_role, implied_role): """Create an inference rule. An inference rule is comprised of two roles, a prior role and an implied role. The prior role will imply the implied role. Valid HTTP return codes: * 201: Resource is created successfully * 404: A role cannot be found * 409: The inference rule already exists :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: a newly created role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) _resp, body = self.client.put("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def delete(self, prior_role, implied_role): """Delete an inference rule. When deleting an inference rule, both roles are required. Note that neither role is deleted, only the inference relationship is dissolved. Valid HTTP return codes: * 204: Delete request is accepted * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) return self._delete("/roles" + url_tail) def get(self, prior_role, implied_role): """Retrieve an inference rule. Valid HTTP return codes: * 200: Inference rule is returned * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) _resp, body = self.client.get("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def list(self, prior_role): """List all roles that a role may imply. Valid HTTP return codes: * 200: List of inference rules are returned * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: the specified role inference returned from server. :rtype: :class:`keystoneclient.v3.roles.InferenceRule` """ url_tail = ('/%s/implies' % base.getid(prior_role)) _resp, body = self.client.get("/roles" + url_tail) return self._prepare_return_value( _resp, self.resource_class(self, body['role_inference'])) def check(self, prior_role, implied_role): """Check if an inference rule exists. Valid HTTP return codes: * 204: The rule inference exists * 404: A role cannot be found :param prior_role: the role which implies ``implied_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :param implied_role: the role which is implied by ``prior_role``. :type role: str or :class:`keystoneclient.v3.roles.Role` :returns: response object with 204 status returned from server. :rtype: :class:`requests.models.Response` """ url_tail = self._implied_role_url_tail(prior_role, implied_role) return self._head("/roles" + url_tail) def list_inference_roles(self): """List all rule inferences. Valid HTTP return codes: * 200: All inference rules are returned :param kwargs: attributes provided will be passed to the server. :returns: a list of inference rules. :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule` """ return super(InferenceRuleManager, self).list() def update(self, **kwargs): raise exceptions.MethodNotImplemented( _('Update not supported for rule inferences')) def find(self, **kwargs): raise exceptions.MethodNotImplemented( _('Find not supported for rule inferences')) def put(self, **kwargs): raise exceptions.MethodNotImplemented( _('Put not supported for rule inferences')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/services.py0000664000175000017500000001120100000000000023766 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneclient import base class Service(base.Resource): """Represents an Identity service. Attributes: * id: a uuid that identifies the service * name: the user-facing name of the service (e.g. Keystone) * description: a description of the service * type: the type of the service (e.g. 'compute', 'identity') * enabled: determines whether the service appears in the catalog """ pass class ServiceManager(base.CrudManager): """Manager class for manipulating Identity services.""" resource_class = Service collection_key = 'services' key = 'service' def create(self, name, type=None, enabled=True, description=None, **kwargs): """Create a service. :param str name: the name of the service. :param str type: the type of the service. :param bool enabled: whether the service appears in the catalog. :param str description: the description of the service. :param kwargs: any other attribute provided will be passed to the server. :returns: the created service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).create( name=name, type=type_arg, description=description, enabled=enabled, **kwargs) def get(self, service): """Retrieve a service. :param service: the service to be retrieved from the server. :type service: str or :class:`keystoneclient.v3.services.Service` :returns: the specified service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ return super(ServiceManager, self).get( service_id=base.getid(service)) def list(self, name=None, type=None, **kwargs): """List services. :param str name: the name of the services to be filtered on. :param str type: the type of the services to be filtered on. :param kwargs: any other attribute provided will filter services on. :returns: a list of services. :rtype: list of :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).list( name=name, type=type_arg, **kwargs) def update(self, service, name=None, type=None, enabled=None, description=None, **kwargs): """Update a service. :param service: the service to be updated on the server. :type service: str or :class:`keystoneclient.v3.services.Service` :param str name: the new name of the service. :param str type: the new type of the service. :param bool enabled: whether the service appears in the catalog. :param str description: the new description of the service. :param kwargs: any other attribute provided will be passed to server. :returns: the updated service returned from server. :rtype: :class:`keystoneclient.v3.services.Service` """ type_arg = type or kwargs.pop('service_type', None) return super(ServiceManager, self).update( service_id=base.getid(service), name=name, type=type_arg, description=description, enabled=enabled, **kwargs) def delete(self, service=None, id=None): """Delete a service. :param service: the service to be deleted on the server. :type service: str or :class:`keystoneclient.v3.services.Service` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ if service: service_id = base.getid(service) else: service_id = id return super(ServiceManager, self).delete( service_id=service_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/system.py0000664000175000017500000000147300000000000023501 0ustar00zuulzuul00000000000000# Copyright 2021 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. from keystoneclient import base class System(base.Resource): """Represents the deployment system, with all the services in it. Attributes: * all: boolean """ pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/tokens.py0000664000175000017500000001125300000000000023455 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 keystoneclient import access from keystoneclient import base def _calc_id(token): if isinstance(token, access.AccessInfo): return token.auth_token return base.getid(token) class TokenManager(object): """Manager class for manipulating Identity tokens.""" def __init__(self, client): self._client = client def revoke_token(self, token): """Revoke a token. :param token: The token to be revoked. :type token: str or :class:`keystoneclient.access.AccessInfo` """ token_id = _calc_id(token) headers = {'X-Subject-Token': token_id} return self._client.delete('/auth/tokens', headers=headers) def get_revoked(self, audit_id_only=False): """Get revoked tokens list. :param bool audit_id_only: If true, the server is requested to not send token IDs, but only audit IDs instead. **New in version 2.2.0.** :returns: A dict containing ``signed`` which is a CMS formatted string if the server signed the response. If `audit_id_only` is true then the response may be a dict containing ``revoked`` which is the list of token audit IDs and expiration times. :rtype: dict """ path = '/auth/tokens/OS-PKI/revoked' if audit_id_only: path += '?audit_id_only' resp, body = self._client.get(path) return body def get_token_data(self, token, include_catalog=True, allow_expired=False, access_rules_support=None): """Fetch the data about a token from the identity server. :param str token: The ID of the token to be fetched. :param bool include_catalog: Whether the service catalog should be included in the response. :param allow_expired: If True the token will be validated and returned if it has already expired. :param access_rules_support: Version number indicating that the client is capable of enforcing keystone access rules, if unset this client does not support access rules. :type access_rules_support: float :rtype: dict """ headers = {'X-Subject-Token': token} if access_rules_support: headers['OpenStack-Identity-Access-Rules'] = access_rules_support flags = [] url = '/auth/tokens' if not include_catalog: flags.append('nocatalog') if allow_expired: flags.append('allow_expired=1') if flags: url = '%s?%s' % (url, '&'.join(flags)) resp, body = self._client.get(url, headers=headers) return body def validate(self, token, include_catalog=True, allow_expired=False, access_rules_support=None): """Validate a token. :param token: The token to be validated. :type token: str or :class:`keystoneclient.access.AccessInfo` :param include_catalog: If False, the response is requested to not include the catalog. :param allow_expired: If True the token will be validated and returned if it has already expired. :type allow_expired: bool :param access_rules_support: Version number indicating that the client is capable of enforcing keystone access rules, if unset this client does not support access rules. :type access_rules_support: float :rtype: :class:`keystoneclient.access.AccessInfoV3` """ token_id = _calc_id(token) body = self.get_token_data(token_id, include_catalog=include_catalog, allow_expired=allow_expired, access_rules_support=access_rules_support) return access.AccessInfo.factory(auth_token=token_id, body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/keystoneclient/v3/users.py0000664000175000017500000002662400000000000023323 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2011 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from debtcollector import renames from keystoneclient import base from keystoneclient import exceptions from keystoneclient.i18n import _ class User(base.Resource): """Represents an Identity user. Attributes: * id: a uuid that identifies the user """ pass class UserManager(base.CrudManager): """Manager class for manipulating Identity users.""" resource_class = User collection_key = 'users' key = 'user' def _require_user_and_group(self, user, group): if not (user and group): msg = _('Specify both a user and a group') raise exceptions.ValidationError(msg) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def create(self, name, domain=None, project=None, password=None, email=None, description=None, enabled=True, default_project=None, **kwargs): """Create a user. :param str name: the name of the user. :param domain: the domain of the user. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the default project of the user. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param str password: the password for the user. :param str email: the email address of the user. :param str description: a description of the user. :param bool enabled: whether the user is enabled. :param default_project: the default project of the user. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to the server. :returns: the created user returned from server. :rtype: :class:`keystoneclient.v3.users.User` .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), default_project_id=default_project_id, password=password, email=email, description=description, enabled=enabled, **kwargs) return self._post('/users', {'user': user_data}, 'user', log=not bool(password)) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def list(self, project=None, domain=None, group=None, default_project=None, **kwargs): """List users. :param project: the default project of the users to be filtered on. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param domain: the domain of the users to be filtered on. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param group: the group in which the users are member of. :type group: str or :class:`keystoneclient.v3.groups.Group` :param default_project: the default project of the users to be filtered on. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will filter users on. :returns: a list of users. :rtype: list of :class:`keystoneclient.v3.users.User`. .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) if group: base_url = '/groups/%s' % base.getid(group) else: base_url = None return super(UserManager, self).list( base_url=base_url, domain_id=base.getid(domain), default_project_id=default_project_id, **kwargs) def get(self, user): """Retrieve a user. :param user: the user to be retrieved from the server. :type user: str or :class:`keystoneclient.v3.users.User` :returns: the specified user returned from server. :rtype: :class:`keystoneclient.v3.users.User` """ return super(UserManager, self).get( user_id=base.getid(user)) @renames.renamed_kwarg('project', 'default_project', version='1.7.0', removal_version='2.0.0') def update(self, user, name=None, domain=None, project=None, password=None, email=None, description=None, enabled=None, default_project=None, **kwargs): """Update a user. :param user: the user to be updated on the server. :type user: str or :class:`keystoneclient.v3.users.User` :param str name: the new name of the user. :param domain: the new domain of the user. :type domain: str or :class:`keystoneclient.v3.domains.Domain` :param project: the new default project of the user. (deprecated, see warning below) :type project: str or :class:`keystoneclient.v3.projects.Project` :param str password: the new password of the user. :param str email: the new email of the user. :param str description: the newdescription of the user. :param bool enabled: whether the user is enabled. :param default_project: the new default project of the user. :type default_project: str or :class:`keystoneclient.v3.projects.Project` :param kwargs: any other attribute provided will be passed to server. :returns: the updated user returned from server. :rtype: :class:`keystoneclient.v3.users.User` .. warning:: The project argument is deprecated as of the 1.7.0 release in favor of default_project and may be removed in the 2.0.0 release. If both default_project and project is provided, the default_project will be used. """ default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), default_project_id=default_project_id, password=password, email=email, description=description, enabled=enabled, **kwargs) return self._update('/users/%s' % base.getid(user), {'user': user_data}, 'user', method='PATCH', log=False) def update_password(self, old_password, new_password): """Update the password for the user the token belongs to. :param str old_password: the user's old password :param str new_password: the user's new password :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ if not (old_password and new_password): msg = _('Specify both the current password and a new password') raise exceptions.ValidationError(msg) if old_password == new_password: msg = _('Old password and new password must be different.') raise exceptions.ValidationError(msg) params = {'user': {'password': new_password, 'original_password': old_password}} base_url = '/users/%s/password' % self.client.user_id return self._update(base_url, params, method='POST', log=False) def add_to_group(self, user, group): """Add the specified user as a member of the specified group. :param user: the user to be added to the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to put the user in. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).put( base_url=base_url, user_id=base.getid(user)) def check_in_group(self, user, group): """Check if the specified user is a member of the specified group. :param user: the user to be verified in the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to check the user in. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).head( base_url=base_url, user_id=base.getid(user)) def remove_from_group(self, user, group): """Remove the specified user from the specified group. :param user: the user to be removed from the group. :type user: str or :class:`keystoneclient.v3.users.User` :param group: the group to remove the user from. :type group: str or :class:`keystoneclient.v3.groups.Group` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ self._require_user_and_group(user, group) base_url = '/groups/%s' % base.getid(group) return super(UserManager, self).delete( base_url=base_url, user_id=base.getid(user)) def delete(self, user): """Delete a user. :param user: the user to be deleted on the server. :type user: str or :class:`keystoneclient.v3.users.User` :returns: Response object with 204 status. :rtype: :class:`requests.models.Response` """ return super(UserManager, self).delete( user_id=base.getid(user)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2590036 python_keystoneclient-5.6.0/playbooks/0000775000175000017500000000000000000000000020211 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/playbooks/run-ds-tox.yaml0000664000175000017500000000010400000000000023110 0ustar00zuulzuul00000000000000- hosts: all roles: - run-devstack - ensure-tox - tox ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/playbooks/tox-post.yaml0000664000175000017500000000011000000000000022662 0ustar00zuulzuul00000000000000- hosts: all roles: - fetch-tox-output - fetch-subunit-output ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/0000775000175000017500000000000000000000000024341 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/PKG-INFO0000644000175000017500000000705500000000000025443 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: python-keystoneclient Version: 5.6.0 Summary: Client Library for OpenStack Identity Home-page: https://docs.openstack.org/python-keystoneclient/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 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: pbr>=2.0.0 Requires-Dist: debtcollector>=1.2.0 Requires-Dist: keystoneauth1>=3.4.0 Requires-Dist: oslo.config>=5.2.0 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.serialization>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: requests>=2.14.2 Requires-Dist: stevedore>=1.20.0 Requires-Dist: packaging>=20.4 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-keystoneclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the OpenStack Identity API (Keystone) ======================================================== .. image:: https://img.shields.io/pypi/v/python-keystoneclient.svg :target: https://pypi.org/project/python-keystoneclient/ :alt: Latest Version This is a client for the OpenStack Identity API, implemented by the Keystone team; it contains a Python API (the ``keystoneclient`` module) for OpenStack's Identity Service. For command line interface support, use `OpenStackClient`_. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-keystoneclient .. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/ .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://opendev.org/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.org/project/python-openstackclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient .. contents:: Contents: :local: Python API ---------- By way of a quick-start:: >>> from keystoneauth1.identity import v3 >>> from keystoneauth1 import session >>> from keystoneclient.v3 import client >>> auth = v3.Password(auth_url="http://example.com:5000/v3", username="admin", ... password="password", project_name="admin", ... user_domain_id="default", project_domain_id="default") >>> sess = session.Session(auth=auth) >>> keystone = client.Client(session=sess) >>> keystone.projects.list() [...] >>> project = keystone.projects.create(name="test", description="My new Project!", domain="default", enabled=True) >>> project.delete() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/SOURCES.txt0000664000175000017500000003236700000000000026240 0ustar00zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/.gitignore doc/Makefile doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/using-api-v2.rst doc/source/using-api-v3.rst doc/source/using-sessions.rst examples/pki/gen_cmsz.py examples/pki/gen_pki.sh examples/pki/run_all.sh examples/pki/certs/cacert.pem examples/pki/certs/signing_cert.pem examples/pki/certs/ssl_cert.pem examples/pki/cms/auth_token_revoked.json examples/pki/cms/auth_token_revoked.pem examples/pki/cms/auth_token_revoked.pkiz examples/pki/cms/auth_token_scoped.json examples/pki/cms/auth_token_scoped.pem examples/pki/cms/auth_token_scoped.pkiz examples/pki/cms/auth_token_scoped_expired.json examples/pki/cms/auth_token_scoped_expired.pem examples/pki/cms/auth_token_scoped_expired.pkiz examples/pki/cms/auth_token_unscoped.json examples/pki/cms/auth_token_unscoped.pem examples/pki/cms/auth_token_unscoped.pkiz examples/pki/cms/auth_v3_token_revoked.json examples/pki/cms/auth_v3_token_revoked.pem examples/pki/cms/auth_v3_token_revoked.pkiz examples/pki/cms/auth_v3_token_scoped.json examples/pki/cms/auth_v3_token_scoped.pem examples/pki/cms/auth_v3_token_scoped.pkiz examples/pki/cms/revocation_list.der examples/pki/cms/revocation_list.json examples/pki/cms/revocation_list.pem examples/pki/cms/revocation_list.pkiz examples/pki/private/cakey.pem examples/pki/private/signing_key.pem examples/pki/private/ssl_key.pem keystoneclient/__init__.py keystoneclient/_discover.py keystoneclient/access.py keystoneclient/adapter.py keystoneclient/base.py keystoneclient/baseclient.py keystoneclient/client.py keystoneclient/discover.py keystoneclient/exceptions.py keystoneclient/httpclient.py keystoneclient/i18n.py keystoneclient/service_catalog.py keystoneclient/session.py keystoneclient/utils.py keystoneclient/auth/__init__.py keystoneclient/auth/base.py keystoneclient/auth/cli.py keystoneclient/auth/conf.py keystoneclient/auth/token_endpoint.py keystoneclient/auth/identity/__init__.py keystoneclient/auth/identity/access.py keystoneclient/auth/identity/base.py keystoneclient/auth/identity/v2.py keystoneclient/auth/identity/generic/__init__.py keystoneclient/auth/identity/generic/base.py keystoneclient/auth/identity/generic/cli.py keystoneclient/auth/identity/generic/password.py keystoneclient/auth/identity/generic/token.py keystoneclient/auth/identity/v3/__init__.py keystoneclient/auth/identity/v3/base.py keystoneclient/auth/identity/v3/federated.py keystoneclient/auth/identity/v3/password.py keystoneclient/auth/identity/v3/token.py keystoneclient/common/__init__.py keystoneclient/common/cms.py keystoneclient/contrib/__init__.py keystoneclient/contrib/auth/__init__.py keystoneclient/contrib/auth/v3/__init__.py keystoneclient/contrib/auth/v3/oidc.py keystoneclient/contrib/auth/v3/saml2.py keystoneclient/contrib/ec2/__init__.py keystoneclient/contrib/ec2/utils.py keystoneclient/fixture/__init__.py keystoneclient/fixture/discovery.py keystoneclient/fixture/exception.py keystoneclient/fixture/v2.py keystoneclient/fixture/v3.py keystoneclient/generic/__init__.py keystoneclient/generic/client.py keystoneclient/tests/__init__.py keystoneclient/tests/functional/__init__.py keystoneclient/tests/functional/base.py keystoneclient/tests/functional/test_base.py keystoneclient/tests/functional/v3/__init__.py keystoneclient/tests/functional/v3/client_fixtures.py keystoneclient/tests/functional/v3/test_credentials.py keystoneclient/tests/functional/v3/test_domain_configs.py keystoneclient/tests/functional/v3/test_domains.py keystoneclient/tests/functional/v3/test_ec2.py keystoneclient/tests/functional/v3/test_endpoint_filters.py keystoneclient/tests/functional/v3/test_endpoint_groups.py keystoneclient/tests/functional/v3/test_endpoints.py keystoneclient/tests/functional/v3/test_federation.py keystoneclient/tests/functional/v3/test_groups.py keystoneclient/tests/functional/v3/test_implied_roles.py keystoneclient/tests/functional/v3/test_policies.py keystoneclient/tests/functional/v3/test_projects.py keystoneclient/tests/functional/v3/test_regions.py keystoneclient/tests/functional/v3/test_roles.py keystoneclient/tests/functional/v3/test_services.py keystoneclient/tests/functional/v3/test_users.py keystoneclient/tests/unit/__init__.py keystoneclient/tests/unit/client_fixtures.py keystoneclient/tests/unit/test_base.py keystoneclient/tests/unit/test_cms.py keystoneclient/tests/unit/test_discovery.py keystoneclient/tests/unit/test_ec2utils.py keystoneclient/tests/unit/test_fixtures.py keystoneclient/tests/unit/test_http.py keystoneclient/tests/unit/test_https.py keystoneclient/tests/unit/test_keyring.py keystoneclient/tests/unit/test_session.py keystoneclient/tests/unit/test_utils.py keystoneclient/tests/unit/utils.py keystoneclient/tests/unit/apiclient/__init__.py keystoneclient/tests/unit/apiclient/test_exceptions.py keystoneclient/tests/unit/auth/__init__.py keystoneclient/tests/unit/auth/test_access.py keystoneclient/tests/unit/auth/test_auth.py keystoneclient/tests/unit/auth/test_cli.py keystoneclient/tests/unit/auth/test_conf.py keystoneclient/tests/unit/auth/test_default_cli.py keystoneclient/tests/unit/auth/test_identity_common.py keystoneclient/tests/unit/auth/test_identity_v2.py keystoneclient/tests/unit/auth/test_identity_v3.py keystoneclient/tests/unit/auth/test_identity_v3_federated.py keystoneclient/tests/unit/auth/test_loading.py keystoneclient/tests/unit/auth/test_password.py keystoneclient/tests/unit/auth/test_token.py keystoneclient/tests/unit/auth/test_token_endpoint.py keystoneclient/tests/unit/auth/utils.py keystoneclient/tests/unit/generic/__init__.py keystoneclient/tests/unit/generic/test_client.py keystoneclient/tests/unit/v2_0/__init__.py keystoneclient/tests/unit/v2_0/client_fixtures.py keystoneclient/tests/unit/v2_0/test_access.py keystoneclient/tests/unit/v2_0/test_auth.py keystoneclient/tests/unit/v2_0/test_certificates.py keystoneclient/tests/unit/v2_0/test_client.py keystoneclient/tests/unit/v2_0/test_discovery.py keystoneclient/tests/unit/v2_0/test_ec2.py keystoneclient/tests/unit/v2_0/test_endpoints.py keystoneclient/tests/unit/v2_0/test_extensions.py keystoneclient/tests/unit/v2_0/test_roles.py keystoneclient/tests/unit/v2_0/test_service_catalog.py keystoneclient/tests/unit/v2_0/test_services.py keystoneclient/tests/unit/v2_0/test_tenants.py keystoneclient/tests/unit/v2_0/test_tokens.py keystoneclient/tests/unit/v2_0/test_users.py keystoneclient/tests/unit/v2_0/utils.py keystoneclient/tests/unit/v3/__init__.py keystoneclient/tests/unit/v3/client_fixtures.py keystoneclient/tests/unit/v3/saml2_fixtures.py keystoneclient/tests/unit/v3/test_access.py keystoneclient/tests/unit/v3/test_access_rules.py keystoneclient/tests/unit/v3/test_application_credentials.py keystoneclient/tests/unit/v3/test_auth.py keystoneclient/tests/unit/v3/test_auth_manager.py keystoneclient/tests/unit/v3/test_auth_oidc.py keystoneclient/tests/unit/v3/test_auth_saml2.py keystoneclient/tests/unit/v3/test_client.py keystoneclient/tests/unit/v3/test_credentials.py keystoneclient/tests/unit/v3/test_discover.py keystoneclient/tests/unit/v3/test_domain_configs.py keystoneclient/tests/unit/v3/test_domains.py keystoneclient/tests/unit/v3/test_ec2.py keystoneclient/tests/unit/v3/test_endpoint_filter.py keystoneclient/tests/unit/v3/test_endpoint_groups.py keystoneclient/tests/unit/v3/test_endpoint_policy.py keystoneclient/tests/unit/v3/test_endpoints.py keystoneclient/tests/unit/v3/test_federation.py keystoneclient/tests/unit/v3/test_groups.py keystoneclient/tests/unit/v3/test_limits.py keystoneclient/tests/unit/v3/test_oauth1.py keystoneclient/tests/unit/v3/test_policies.py keystoneclient/tests/unit/v3/test_projects.py keystoneclient/tests/unit/v3/test_regions.py keystoneclient/tests/unit/v3/test_registered_limits.py keystoneclient/tests/unit/v3/test_role_assignments.py keystoneclient/tests/unit/v3/test_roles.py keystoneclient/tests/unit/v3/test_service_catalog.py keystoneclient/tests/unit/v3/test_services.py keystoneclient/tests/unit/v3/test_simple_cert.py keystoneclient/tests/unit/v3/test_tokens.py keystoneclient/tests/unit/v3/test_trusts.py keystoneclient/tests/unit/v3/test_users.py keystoneclient/tests/unit/v3/utils.py keystoneclient/tests/unit/v3/examples/xml/ADFS_RequestSecurityTokenResponse.xml keystoneclient/tests/unit/v3/examples/xml/ADFS_fault.xml keystoneclient/v2_0/__init__.py keystoneclient/v2_0/certificates.py keystoneclient/v2_0/client.py keystoneclient/v2_0/ec2.py keystoneclient/v2_0/endpoints.py keystoneclient/v2_0/extensions.py keystoneclient/v2_0/roles.py keystoneclient/v2_0/services.py keystoneclient/v2_0/tenants.py keystoneclient/v2_0/tokens.py keystoneclient/v2_0/users.py keystoneclient/v3/__init__.py keystoneclient/v3/access_rules.py keystoneclient/v3/application_credentials.py keystoneclient/v3/auth.py keystoneclient/v3/client.py keystoneclient/v3/credentials.py keystoneclient/v3/domain_configs.py keystoneclient/v3/domains.py keystoneclient/v3/ec2.py keystoneclient/v3/endpoint_groups.py keystoneclient/v3/endpoints.py keystoneclient/v3/groups.py keystoneclient/v3/limits.py keystoneclient/v3/policies.py keystoneclient/v3/projects.py keystoneclient/v3/regions.py keystoneclient/v3/registered_limits.py keystoneclient/v3/role_assignments.py keystoneclient/v3/roles.py keystoneclient/v3/services.py keystoneclient/v3/system.py keystoneclient/v3/tokens.py keystoneclient/v3/users.py keystoneclient/v3/contrib/__init__.py keystoneclient/v3/contrib/endpoint_filter.py keystoneclient/v3/contrib/endpoint_policy.py keystoneclient/v3/contrib/simple_cert.py keystoneclient/v3/contrib/trusts.py keystoneclient/v3/contrib/federation/__init__.py keystoneclient/v3/contrib/federation/base.py keystoneclient/v3/contrib/federation/core.py keystoneclient/v3/contrib/federation/domains.py keystoneclient/v3/contrib/federation/identity_providers.py keystoneclient/v3/contrib/federation/mappings.py keystoneclient/v3/contrib/federation/projects.py keystoneclient/v3/contrib/federation/protocols.py keystoneclient/v3/contrib/federation/saml.py keystoneclient/v3/contrib/federation/service_providers.py keystoneclient/v3/contrib/oauth1/__init__.py keystoneclient/v3/contrib/oauth1/access_tokens.py keystoneclient/v3/contrib/oauth1/auth.py keystoneclient/v3/contrib/oauth1/consumers.py keystoneclient/v3/contrib/oauth1/core.py keystoneclient/v3/contrib/oauth1/request_tokens.py keystoneclient/v3/contrib/oauth1/utils.py playbooks/run-ds-tox.yaml playbooks/tox-post.yaml python_keystoneclient.egg-info/PKG-INFO python_keystoneclient.egg-info/SOURCES.txt python_keystoneclient.egg-info/dependency_links.txt python_keystoneclient.egg-info/entry_points.txt python_keystoneclient.egg-info/not-zip-safe python_keystoneclient.egg-info/pbr.json python_keystoneclient.egg-info/requires.txt python_keystoneclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml releasenotes/notes/bug-1641674-4862454115265e76.yaml releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml releasenotes/notes/drop-python-3-6-and-3-7-ef1e107897dde8f4.yaml releasenotes/notes/implied_roles-ea39d3c3d998d482.yaml releasenotes/notes/ksc_2.1.0-739ded9c4c3f8aaa.yaml releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml releasenotes/notes/list_role_assignment_names-7e1b7eb8c2d22d7c.yaml releasenotes/notes/project-tags-1f8a32d389951e7a.yaml releasenotes/notes/remove-credentials-data-46ab3c3c248047cf.yaml releasenotes/notes/remove-middleware-eef8c40117b465aa.yaml releasenotes/notes/remove-py38-2e39854190447827.yaml releasenotes/notes/remove_apiclient_exceptions-0cd5c8d16aa09a22.yaml releasenotes/notes/remove_apiclient_exceptions-6580003a885db286.yaml releasenotes/notes/remove_cli-d2c4435ba6a09b79.yaml releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml releasenotes/notes/switch-default-interface-v3-dcd7167196ace531.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/fr/LC_MESSAGES/releasenotes.po././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/dependency_links.txt0000664000175000017500000000000100000000000030407 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/entry_points.txt0000664000175000017500000000124400000000000027640 0ustar00zuulzuul00000000000000[keystoneclient.auth.plugin] admin_token = keystoneclient.auth.token_endpoint:Token password = keystoneclient.auth.identity.generic:Password token = keystoneclient.auth.identity.generic:Token v2password = keystoneclient.auth.identity.v2:Password v2token = keystoneclient.auth.identity.v2:Token v3oidcpassword = keystoneclient.contrib.auth.v3.oidc:OidcPassword v3password = keystoneclient.auth.identity.v3:Password v3scopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2ScopedToken v3token = keystoneclient.auth.identity.v3:Token v3unscopedadfs = keystoneclient.contrib.auth.v3.saml2:ADFSUnscopedToken v3unscopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2UnscopedToken ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/not-zip-safe0000664000175000017500000000000100000000000026567 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/pbr.json0000664000175000017500000000005600000000000026020 0ustar00zuulzuul00000000000000{"git_version": "0d43c46", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/requires.txt0000664000175000017500000000027300000000000026743 0ustar00zuulzuul00000000000000pbr>=2.0.0 debtcollector>=1.2.0 keystoneauth1>=3.4.0 oslo.config>=5.2.0 oslo.i18n>=3.15.3 oslo.serialization>=2.18.0 oslo.utils>=3.33.0 requests>=2.14.2 stevedore>=1.20.0 packaging>=20.4 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753785.0 python_keystoneclient-5.6.0/python_keystoneclient.egg-info/top_level.txt0000664000175000017500000000001700000000000027071 0ustar00zuulzuul00000000000000keystoneclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1910036 python_keystoneclient-5.6.0/releasenotes/0000775000175000017500000000000000000000000020677 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2670035 python_keystoneclient-5.6.0/releasenotes/notes/0000775000175000017500000000000000000000000022027 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000024300 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=python_keystoneclient-5.6.0/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.yaml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/Add-allow-expired-flag-to-validate-25b8914f4deb359b.y0000664000175000017500000000033200000000000033023 0ustar00zuulzuul00000000000000--- features: - Added a ``allow_expired`` argument to ``validate`` and ``get_token_data`` in `keystoneclient.v3.tokens`. Setting this to ``True``, allos for a token validation query to fetch expired tokens. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/add-support-for-limits-6f883d6d3054a500.yaml0000664000175000017500000000037600000000000031322 0ustar00zuulzuul00000000000000--- features: - | Added support for managing project-specific limits. The ``POST`` API for limits in keystone supports batch creation, but the client implementation does not. Creation for limits using the client must be done one at a time. ././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=python_keystoneclient-5.6.0/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.yaml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/add-support-for-registered-limits-d83b888ea65a614b.ya0000664000175000017500000000042100000000000033264 0ustar00zuulzuul00000000000000--- features: - | Added support for managing registered limits. The ``POST`` API for registered limits in keystone supports batch creation, but the client implementation does not. Creation of registered limits using the client must be done one at a time. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml0000664000175000017500000000056100000000000032350 0ustar00zuulzuul00000000000000--- features: - | Adds support for creating, reading, and deleting application credentials. With application credentials, a user can grant their applications limited access to their cloud resources. Applications can use keystoneauth with the `v3applicationcredential` auth plugin to authenticate with keystone without needing the user's password. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bp-domain-config-9566e672a98f4e7f.yaml0000664000175000017500000000044600000000000030221 0ustar00zuulzuul00000000000000--- features: - Added support for ``domain configs``. A user can now upload domain specific configurations to keytone using the client. See ``client.domain_configs.create``, ``client.domain_configs.delete``, ``client.domain_configs.get`` and ``client.domain_configs.update``. ././@PaxHeader0000000000000000000000000000021400000000000011452 xustar0000000000000000118 path=python_keystoneclient-5.6.0/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf330000664000175000017500000000037500000000000033620 0ustar00zuulzuul00000000000000--- features: - | Added ability to filter on multiple values with the same parameter key. For example, we can now filter on user names that contain both ``test`` and ``user`` using ``keystone.users.list(name__contains=['test', 'user'])``. ././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=python_keystoneclient-5.6.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce0000664000175000017500000000023300000000000033406 0ustar00zuulzuul00000000000000--- features: - | Adds support for creating access rules as an attribute of application credentials as well as for retrieving and deleting them. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bug-1615076-26962c85aeaf288c.yaml0000664000175000017500000000026500000000000026552 0ustar00zuulzuul00000000000000--- deprecations: - | The region resource in Keystone never support or contain "enabled" property. Thus the property is deprecated and will be removed in future versions. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml0000664000175000017500000000055500000000000026630 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1616105 `_] Only log the response body when the ``Content-Type`` header is set to ``application/json``. This avoids logging large binary objects (such as images). Other ``Content-Type`` will not be logged. Additional ``Content-Type`` strings can be added as required. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bug-1641674-4862454115265e76.yaml0000664000175000017500000000045600000000000026173 0ustar00zuulzuul00000000000000--- prelude: > Keystone Client now supports endpoint group filtering. features: - | Support for handling the relationship between endpoint groups and projects has been added. It is now possible to list, associate, check and disassociate endpoint groups that have access to a project. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml0000664000175000017500000000020400000000000026645 0ustar00zuulzuul00000000000000--- fixes: - | The ``X-Service-Token`` header value is now properly masked, and is displayed as a hash value, in the log. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml0000664000175000017500000000134700000000000030415 0ustar00zuulzuul00000000000000--- deprecations: - > [`blueprint deprecate-to-ksa `_] Several modules related to authentication in keystoneclient have been deprecated in favor of [`keystoneauth `_] These modules include: ``keystoneclient.session``, ``keystoneclient.adapter``, ``keystoneclient.httpclient``, ``keystoneclient.auth.base``, ``keystoneclient.auth.cli``, ``keystoneclient.auth.conf``, ``keystoneclient.auth.identity.base``, and ``keystoneclient.auth.token_endpoint``. Tips for migrating to `keystoneauth` have been [`documented `_]. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/drop-py-2-7-5ac18e82de83fcfa.yaml0000664000175000017500000000031600000000000027252 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of python-keystoneclient to support python 2.7 is OpenStack Train. The minimum version of Python now supported is Python 3.6.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/drop-python-3-6-and-3-7-ef1e107897dde8f4.yaml0000664000175000017500000000020100000000000031053 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=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/implied_roles-ea39d3c3d998d482.yaml0000664000175000017500000000006700000000000030001 0ustar00zuulzuul00000000000000--- features: - support for implied roles in v3 API. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/ksc_2.1.0-739ded9c4c3f8aaa.yaml0000664000175000017500000000124200000000000026654 0ustar00zuulzuul00000000000000--- fixes: - > [`bug 1462694 `_] Add support for `include_subtree` in list_role_assignments. - > [`bug 1526686 `_] Replace textwrap with faster code in cms functions. - > [`bug 1457702 `_] Change default endpoint to public for keystone v3. - > [`bug 1520244 `_] Support `truncated` flag returned from server. other: - > Support v2 parameters for the v3 service create method. ././@PaxHeader0000000000000000000000000000022100000000000011450 xustar0000000000000000123 path=python_keystoneclient-5.6.0/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f197c1e37.yaml 22 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/list_projects_filtered_by_the_parent_project-a873974f0000664000175000017500000000016500000000000034241 0ustar00zuulzuul00000000000000--- features: - | Now keystone client supports to list projects which belongs to the given parent project. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/list_role_assignment_names-7e1b7eb8c2d22d7c.yaml0000664000175000017500000000042300000000000032676 0ustar00zuulzuul00000000000000--- features: - > [`bug 1479569 `_] With the ``include_names`` parameter set to True the names of the role assignments are returned with the entities IDs. (GET /role_assignments?include_names=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/project-tags-1f8a32d389951e7a.yaml0000664000175000017500000000060100000000000027460 0ustar00zuulzuul00000000000000--- features: - | [`blueprint project-tags `_] The keystoneclient now supports project tags feature in keystone. This allows operators to use the client to associate tags to a project, retrieve tags associated with a project, delete tags associated with a project, and filter projects based on tags. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove-credentials-data-46ab3c3c248047cf.yaml0000664000175000017500000000041000000000000031615 0ustar00zuulzuul00000000000000--- prelude: > The ``data`` argument for creating and updating credentials has been removed. other: - The ``data`` argument for creating and updating credentials was deprecated in the 1.7.0 release. It has been replaced by the ``blob`` argument. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove-middleware-eef8c40117b465aa.yaml0000664000175000017500000000063000000000000030612 0ustar00zuulzuul00000000000000--- prelude: > keystoneclient.middleware has been removed. critical: - > [`bug 1449066 `_] The `keystoneclient.middleware` module has been removed in favor of the keystonemiddleware library. The aforementioned module has been deprecated since keystoneclient v0.10.0 which was included in the Juno release of OpenStack. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove-py38-2e39854190447827.yaml0000664000175000017500000000016600000000000026667 0ustar00zuulzuul00000000000000--- upgrade: - | Support for Python 3.8 has been removed. Now the minimum python version supported is 3.9 . ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove_apiclient_exceptions-0cd5c8d16aa09a22.yaml0000664000175000017500000000024500000000000032761 0ustar00zuulzuul00000000000000--- other: - > Removed `keystoneclient.apiclient.exceptions`. This file was deprecated in v0.7.1 and has now been replaced by `keystoneclient.exceptions`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove_apiclient_exceptions-6580003a885db286.yaml0000664000175000017500000000062100000000000032477 0ustar00zuulzuul00000000000000--- prelude: > keystoneclient.apiclient has been removed. critical: - > [`bug 1526651 `_] The `keystoneclient.apiclient` module has been removed in favor of `keystoneclient.exceptions`. The aforementioned module has been deprecated since keystoneclient v0.7.1 which was inclued in the Juno release of OpenStack. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/remove_cli-d2c4435ba6a09b79.yaml0000664000175000017500000000035000000000000027250 0ustar00zuulzuul00000000000000--- prelude: > The ``keystone`` CLI has been removed. other: - The ``keystone`` CLI has been removed, using the ``openstack`` CLI is recommended. This feature has been deprecated since the Liberty release of Keystone. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml0000664000175000017500000000045100000000000031437 0ustar00zuulzuul00000000000000--- deprecations: - Deprecate the `keystoneclient.generic` client. This client used to be able to determine available API versions and some basics around installed extensions however the APIs were never upgraded for the v3 API. It doesn't seem to be used in the openstack ecosystem. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/return-request-id-to-caller-97fa269ad626f8c1.yaml0000664000175000017500000000125100000000000032423 0ustar00zuulzuul00000000000000--- features: - > [`blueprint return-request-id-to-caller `_] Instantiating client with ``include_metadata=True`` will cause manager response to return data along with request_ids for better tracing. Refer [`using-api-v3 `_] Added support to return "x-openstack-request-id" header in request_ids attribute if ``include_metadata=True``. Also, for APIs which return response as None, client will return request_ids as well if ``include_metadata`` is True.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/notes/switch-default-interface-v3-dcd7167196ace531.yaml0000664000175000017500000000040700000000000032342 0ustar00zuulzuul00000000000000--- features: - | For sessions using the v3 Identity API, the default interface has been switched from ``admin`` to ``public``. This allows deployments to get rid of the admin endpoint, which functionally is no longer necessary with the v3 API. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/releasenotes/source/0000775000175000017500000000000000000000000022177 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000023450 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000023451 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000023451 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000023452 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/releasenotes/source/_static/0000775000175000017500000000000000000000000023625 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000026076 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000024334 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000026605 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/conf.py0000664000175000017500000002106600000000000023503 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. # keystoneclient 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 = '2015, Keystone Developers' # Release notes are version independent. # 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 = 'KeystoneClientReleaseNotesdoc' # -- 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', 'keystoneclientReleaseNotes.tex', 'keystoneclient Release Notes Documentation', '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', 'keystoneclientreleasenotes', 'keystoneclient Release Notes Documentation', ['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', 'keystoneclientReleaseNotes', 'keystoneclient Release Notes Documentation', 'Keystone Developers', 'keystoneclientReleaseNotes', 'Python bindings 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/python-keystoneclient' openstackdocs_bug_project = 'python-keystoneclient' openstackdocs_bug_tag = '' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/index.rst0000664000175000017500000000046300000000000024043 0ustar00zuulzuul00000000000000============================== keystoneclient 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=1740753785.1910036 python_keystoneclient-5.6.0/releasenotes/source/locale/0000775000175000017500000000000000000000000023436 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.1910036 python_keystoneclient-5.6.0/releasenotes/source/locale/fr/0000775000175000017500000000000000000000000024045 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2710035 python_keystoneclient-5.6.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000000000000000025632 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000232700000000000030667 0ustar00zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: keystoneclient Release Notes 3.12.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-07-24 15:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:08+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 "2.1.0" msgstr "2.1.0" msgid "2.2.0" msgstr "2.2.0" msgid "2.3.0" msgstr "2.3.0" msgid "3.0.0" msgstr "3.0.0" msgid "3.6.0" msgstr "3.6.0" msgid "Bug Fixes" msgstr "Corrections de bugs" msgid "Critical Issues" msgstr "Erreurs critiques" 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 "Other Notes" msgstr "Autres notes" msgid "keystoneclient Release Notes" msgstr "Note de release pour keystoneclient" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/mitaka.rst0000664000175000017500000000023200000000000024174 0ustar00zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/newton.rst0000664000175000017500000000021600000000000024242 0ustar00zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/ocata.rst0000664000175000017500000000021200000000000024013 0ustar00zuulzuul00000000000000============================ Ocata Series Release Notes ============================ .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000023661 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000024226 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000024053 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000024046 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/train.rst0000664000175000017500000000022100000000000024041 0ustar00zuulzuul00000000000000=================================== Train Series Release Notes =================================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000025055 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000024255 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000024543 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000024361 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000023654 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023660 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000023515 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/requirements.txt0000664000175000017500000000103100000000000021465 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. pbr>=2.0.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 packaging>=20.4 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740753785.2750037 python_keystoneclient-5.6.0/setup.cfg0000664000175000017500000000274600000000000020040 0ustar00zuulzuul00000000000000[metadata] name = python-keystoneclient summary = Client Library for OpenStack Identity description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-keystoneclient/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 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 [files] packages = keystoneclient [entry_points] keystoneclient.auth.plugin = password = keystoneclient.auth.identity.generic:Password token = keystoneclient.auth.identity.generic:Token admin_token = keystoneclient.auth.token_endpoint:Token v2password = keystoneclient.auth.identity.v2:Password v2token = keystoneclient.auth.identity.v2:Token v3password = keystoneclient.auth.identity.v3:Password v3token = keystoneclient.auth.identity.v3:Token v3oidcpassword = keystoneclient.contrib.auth.v3.oidc:OidcPassword v3unscopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2UnscopedToken v3scopedsaml = keystoneclient.contrib.auth.v3.saml2:Saml2ScopedToken v3unscopedadfs = keystoneclient.contrib.auth.v3.saml2:ADFSUnscopedToken [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/setup.py0000664000175000017500000000127100000000000017721 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. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/test-requirements.txt0000664000175000017500000000077100000000000022454 0ustar00zuulzuul00000000000000hacking>=6.1.0,<6.2.0 # Apache-2.0 flake8-docstrings==1.7.0 # MIT coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD keyring>=5.5.1 # MIT/PSF lxml>=4.5.0 # BSD oauthlib>=0.6.2 # BSD os-client-config>=1.28.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740753711.0 python_keystoneclient-5.6.0/tox.ini0000664000175000017500000000525100000000000017524 0ustar00zuulzuul00000000000000[tox] minversion = 3.18.0 skipsdist = True envlist = py38,pep8,releasenotes ignore_basepython_conflict = True [testenv] usedevelop = True setenv = 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 = find . -type f -name "*.pyc" -delete stestr run --slowest {posargs} allowlist_externals = find basepython = python3 [testenv:pep8] commands = flake8 bandit -r keystoneclient -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 keystoneclient -x tests -n5 [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON=coverage run --source keystoneclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:debug] commands = oslo_debug_helper -t keystoneclient/tests {posargs} [testenv:functional] setenv = {[testenv]setenv} OS_TEST_PATH=./keystoneclient/tests/functional passenv = OS_* [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__ # D203: 1 blank line required before class docstring (deprecated in pep257) # D401 First line should be in imperative mood; try rephrasing # W504 line break after binary operator ignore = D100,D101,D102,D103,D104,D107,D203,D401,W504 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [testenv:docs] commands = sphinx-build -W -b html doc/source doc/build/html deps = -r{toxinidir}/doc/requirements.txt -r{toxinidir}/requirements.txt [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] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html deps = -r{toxinidir}/doc/requirements.txt [hacking] import_exceptions = keystoneclient.i18n [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files. deps = bindep commands = bindep test