From 1068cb6f7fc964458891bd873a96300d9fefa3e1 Mon Sep 17 00:00:00 2001 From: Tony Breeds Date: Tue, 12 Sep 2017 16:00:55 -0600 Subject: [PATCH] Retire Packaging Deb project repos This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: I3ca69746ffe73b7c01c3066476589fac8e16a5e9 --- .coveragerc | 7 - .gitignore | 25 - .gitreview | 4 - .mailmap | 6 - .testr.conf | 4 - CONTRIBUTING.rst | 18 - HACKING.rst | 24 - LICENSE | 209 -- README | 14 + README.rst | 35 - bindep.txt | 8 - doc/.gitignore | 1 - doc/Makefile | 90 - doc/ext/__init__.py | 0 doc/ext/list_plugins.py | 93 - doc/source/authentication-plugins.rst | 303 --- doc/source/conf.py | 237 --- doc/source/extras.rst | 73 - doc/source/history.rst | 1 - doc/source/images/graphs_authComp.svg | 48 - doc/source/images/graphs_authCompDelegate.svg | 53 - doc/source/index.rst | 49 - doc/source/migrating.rst | 102 - doc/source/plugin-options.rst | 92 - doc/source/using-sessions.rst | 440 ----- keystoneauth1/__init__.py | 16 - keystoneauth1/_utils.py | 83 - keystoneauth1/access/__init__.py | 19 - keystoneauth1/access/access.py | 755 -------- keystoneauth1/access/service_catalog.py | 512 ----- keystoneauth1/access/service_providers.py | 44 - keystoneauth1/adapter.py | 466 ----- keystoneauth1/discover.py | 1195 ------------ keystoneauth1/exceptions/__init__.py | 23 - keystoneauth1/exceptions/auth.py | 17 - keystoneauth1/exceptions/auth_plugins.py | 93 - keystoneauth1/exceptions/base.py | 24 - keystoneauth1/exceptions/catalog.py | 30 - keystoneauth1/exceptions/connection.py | 51 - keystoneauth1/exceptions/discovery.py | 25 - keystoneauth1/exceptions/http.py | 428 ----- keystoneauth1/exceptions/oidc.py | 44 - keystoneauth1/exceptions/response.py | 25 - keystoneauth1/exceptions/service_providers.py | 24 - keystoneauth1/extras/__init__.py | 20 - keystoneauth1/extras/_saml2/__init__.py | 22 - keystoneauth1/extras/_saml2/_loading.py | 66 - keystoneauth1/extras/_saml2/v3/__init__.py | 23 - keystoneauth1/extras/_saml2/v3/adfs.py | 431 ----- keystoneauth1/extras/_saml2/v3/base.py | 98 - keystoneauth1/extras/_saml2/v3/saml2.py | 301 --- keystoneauth1/extras/kerberos/__init__.py | 89 - keystoneauth1/extras/kerberos/_loading.py | 36 - keystoneauth1/extras/oauth1/__init__.py | 19 - keystoneauth1/extras/oauth1/_loading.py | 47 - keystoneauth1/extras/oauth1/v3.py | 80 - keystoneauth1/fixture/__init__.py | 41 - keystoneauth1/fixture/discovery.py | 373 ---- keystoneauth1/fixture/exception.py | 20 - keystoneauth1/fixture/hooks.py | 60 - keystoneauth1/fixture/keystoneauth_betamax.py | 136 -- keystoneauth1/fixture/serializer.py | 98 - keystoneauth1/fixture/v2.py | 247 --- keystoneauth1/fixture/v3.py | 466 ----- keystoneauth1/hacking/__init__.py | 0 keystoneauth1/hacking/checks.py | 37 - keystoneauth1/identity/__init__.py | 69 - keystoneauth1/identity/access.py | 48 - keystoneauth1/identity/base.py | 507 ----- keystoneauth1/identity/generic/__init__.py | 21 - keystoneauth1/identity/generic/base.py | 215 --- keystoneauth1/identity/generic/password.py | 90 - keystoneauth1/identity/generic/token.py | 39 - keystoneauth1/identity/v2.py | 178 -- keystoneauth1/identity/v3/__init__.py | 46 - keystoneauth1/identity/v3/base.py | 282 --- keystoneauth1/identity/v3/federation.py | 115 -- keystoneauth1/identity/v3/k2k.py | 173 -- keystoneauth1/identity/v3/oidc.py | 480 ----- keystoneauth1/identity/v3/password.py | 75 - keystoneauth1/identity/v3/token.py | 54 - keystoneauth1/identity/v3/tokenless_auth.py | 115 -- keystoneauth1/identity/v3/totp.py | 81 - keystoneauth1/loading/__init__.py | 85 - keystoneauth1/loading/_plugins/__init__.py | 0 keystoneauth1/loading/_plugins/admin_token.py | 46 - .../loading/_plugins/identity/__init__.py | 0 .../loading/_plugins/identity/generic.py | 75 - keystoneauth1/loading/_plugins/identity/v2.py | 53 - keystoneauth1/loading/_plugins/identity/v3.py | 256 --- keystoneauth1/loading/_plugins/noauth.py | 33 - keystoneauth1/loading/_utils.py | 40 - keystoneauth1/loading/adapter.py | 209 -- keystoneauth1/loading/base.py | 187 -- keystoneauth1/loading/cli.py | 105 - keystoneauth1/loading/conf.py | 135 -- keystoneauth1/loading/identity.py | 162 -- keystoneauth1/loading/opts.py | 151 -- keystoneauth1/loading/session.py | 254 --- keystoneauth1/noauth.py | 24 - keystoneauth1/plugin.py | 252 --- keystoneauth1/service_token.py | 73 - keystoneauth1/session.py | 1114 ----------- keystoneauth1/tests/__init__.py | 0 keystoneauth1/tests/unit/__init__.py | 0 keystoneauth1/tests/unit/access/__init__.py | 0 .../tests/unit/access/test_v2_access.py | 218 --- .../unit/access/test_v2_service_catalog.py | 269 --- .../tests/unit/access/test_v3_access.py | 264 --- .../unit/access/test_v3_service_catalog.py | 392 ---- keystoneauth1/tests/unit/client_fixtures.py | 122 -- keystoneauth1/tests/unit/data/README | 7 - .../unit/data/keystone_v2_sample_request.json | 1 - .../data/keystone_v2_sample_response.json | 49 - .../unit/data/keystone_v3_sample_request.json | 13 - .../data/keystone_v3_sample_response.json | 15 - .../unit/data/ksa_betamax_test_cassette.yaml | 92 - .../tests/unit/data/ksa_serializer_data.json | 1 - .../tests/unit/data/test_pre_record_hook.json | 0 .../tests/unit/exceptions/__init__.py | 0 .../tests/unit/exceptions/test_exceptions.py | 32 - keystoneauth1/tests/unit/extras/__init__.py | 0 .../tests/unit/extras/kerberos/__init__.py | 0 .../tests/unit/extras/kerberos/base.py | 44 - .../extras/kerberos/test_fedkerb_loading.py | 48 - .../extras/kerberos/test_kerberos_loading.py | 33 - .../tests/unit/extras/kerberos/test_mapped.py | 77 - .../tests/unit/extras/kerberos/test_v3.py | 38 - .../tests/unit/extras/kerberos/utils.py | 83 - .../tests/unit/extras/oauth1/__init__.py | 0 .../tests/unit/extras/oauth1/test_oauth1.py | 117 -- .../unit/extras/oauth1/test_oauth1_loading.py | 57 - .../tests/unit/extras/saml2/__init__.py | 0 .../xml/ADFS_RequestSecurityTokenResponse.xml | 132 -- .../extras/saml2/examples/xml/ADFS_fault.xml | 19 - .../unit/extras/saml2/fixtures/__init__.py | 120 -- .../fixtures/templates/authn_request.xml | 22 - .../fixtures/templates/saml_assertion.xml | 69 - .../fixtures/templates/soap_response.xml | 45 - .../tests/unit/extras/saml2/test_auth_adfs.py | 261 --- .../unit/extras/saml2/test_auth_saml2.py | 312 --- .../tests/unit/extras/saml2/utils.py | 39 - keystoneauth1/tests/unit/identity/__init__.py | 0 .../tests/unit/identity/test_access.py | 76 - .../unit/identity/test_identity_common.py | 1681 ----------------- .../tests/unit/identity/test_identity_v2.py | 369 ---- .../tests/unit/identity/test_identity_v3.py | 632 ------- .../identity/test_identity_v3_federation.py | 292 --- .../unit/identity/test_identity_v3_oidc.py | 402 ---- .../tests/unit/identity/test_password.py | 106 -- .../tests/unit/identity/test_token.py | 63 - .../unit/identity/test_tokenless_auth.py | 105 - keystoneauth1/tests/unit/identity/utils.py | 180 -- keystoneauth1/tests/unit/k2k_fixtures.py | 148 -- .../tests/unit/keystoneauth_fixtures.py | 75 - keystoneauth1/tests/unit/loading/__init__.py | 0 .../tests/unit/loading/test_adapter.py | 172 -- keystoneauth1/tests/unit/loading/test_cli.py | 217 --- keystoneauth1/tests/unit/loading/test_conf.py | 207 -- .../tests/unit/loading/test_entry_points.py | 35 - .../tests/unit/loading/test_generic.py | 86 - .../tests/unit/loading/test_loading.py | 145 -- .../tests/unit/loading/test_session.py | 114 -- keystoneauth1/tests/unit/loading/test_v3.py | 365 ---- keystoneauth1/tests/unit/loading/utils.py | 123 -- keystoneauth1/tests/unit/matchers.py | 89 - keystoneauth1/tests/unit/oidc_fixtures.py | 98 - .../tests/unit/test_betamax_fixture.py | 125 -- .../tests/unit/test_betamax_hooks.py | 198 -- .../tests/unit/test_betamax_serializer.py | 53 - keystoneauth1/tests/unit/test_discovery.py | 951 ---------- keystoneauth1/tests/unit/test_fixtures.py | 342 ---- .../tests/unit/test_hacking_checks.py | 47 - keystoneauth1/tests/unit/test_matchers.py | 93 - keystoneauth1/tests/unit/test_noauth.py | 37 - .../tests/unit/test_service_token.py | 116 -- keystoneauth1/tests/unit/test_session.py | 1540 --------------- .../tests/unit/test_token_endpoint.py | 76 - keystoneauth1/tests/unit/test_utils.py | 22 - keystoneauth1/tests/unit/utils.py | 153 -- keystoneauth1/token_endpoint.py | 48 - releasenotes/notes/.placeholder | 0 .../notes/1583780-700f99713e06324e.yaml | 9 - ...c-client-credentials-2be065926ba4b849.yaml | 4 - ...ery-document-support-b07fe54f83286d62.yaml | 12 - .../add-prompt-to-opt-d083acc357a7f07b.yaml | 10 - ...add-totp-auth-plugin-0650d220899c25b7.yaml | 16 - .../additional-headers-f2d16f85f5abe942.yaml | 10 - ...ow_version_hack-flag-9b53b72d9b084c04.yaml | 9 - .../notes/bug-1582774-49af731b6dfc6f2f.yaml | 4 - .../notes/bug-1614688-c4a1bd54f4ba5644.yaml | 9 - .../notes/bug-1616105-cc8b85eb056e99e2.yaml | 8 - .../notes/bug-1654847-acdf9543158329ec.yaml | 5 - ...rd-endpointreference-f186d84a54007b09.yaml | 13 - .../notes/ksa_2.2.0-81145229d4b43043.yaml | 8 - ...rsion-header-support-901acd820a21d788.yaml | 6 - ...ort-api-wg-discovery-2cb4b0186619e124.yaml | 10 - ...ser-agent-generation-b069100508c06177.yaml | 17 - releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 286 --- releasenotes/source/index.rst | 11 - releasenotes/source/mitaka.rst | 6 - releasenotes/source/newton.rst | 6 - releasenotes/source/ocata.rst | 6 - releasenotes/source/unreleased.rst | 5 - requirements.txt | 19 - setup.cfg | 74 - setup.py | 29 - test-requirements.txt | 24 - tools/tox_install.sh | 30 - tox.ini | 65 - 212 files changed, 14 insertions(+), 27631 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .gitignore delete mode 100644 .gitreview delete mode 100644 .mailmap delete mode 100644 .testr.conf delete mode 100644 CONTRIBUTING.rst delete mode 100644 HACKING.rst delete mode 100644 LICENSE create mode 100644 README delete mode 100644 README.rst delete mode 100644 bindep.txt delete mode 100644 doc/.gitignore delete mode 100644 doc/Makefile delete mode 100644 doc/ext/__init__.py delete mode 100644 doc/ext/list_plugins.py delete mode 100644 doc/source/authentication-plugins.rst delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/extras.rst delete mode 100644 doc/source/history.rst delete mode 100644 doc/source/images/graphs_authComp.svg delete mode 100644 doc/source/images/graphs_authCompDelegate.svg delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/migrating.rst delete mode 100644 doc/source/plugin-options.rst delete mode 100644 doc/source/using-sessions.rst delete mode 100644 keystoneauth1/__init__.py delete mode 100644 keystoneauth1/_utils.py delete mode 100644 keystoneauth1/access/__init__.py delete mode 100644 keystoneauth1/access/access.py delete mode 100644 keystoneauth1/access/service_catalog.py delete mode 100644 keystoneauth1/access/service_providers.py delete mode 100644 keystoneauth1/adapter.py delete mode 100644 keystoneauth1/discover.py delete mode 100644 keystoneauth1/exceptions/__init__.py delete mode 100644 keystoneauth1/exceptions/auth.py delete mode 100644 keystoneauth1/exceptions/auth_plugins.py delete mode 100644 keystoneauth1/exceptions/base.py delete mode 100644 keystoneauth1/exceptions/catalog.py delete mode 100644 keystoneauth1/exceptions/connection.py delete mode 100644 keystoneauth1/exceptions/discovery.py delete mode 100644 keystoneauth1/exceptions/http.py delete mode 100644 keystoneauth1/exceptions/oidc.py delete mode 100644 keystoneauth1/exceptions/response.py delete mode 100644 keystoneauth1/exceptions/service_providers.py delete mode 100644 keystoneauth1/extras/__init__.py delete mode 100644 keystoneauth1/extras/_saml2/__init__.py delete mode 100644 keystoneauth1/extras/_saml2/_loading.py delete mode 100644 keystoneauth1/extras/_saml2/v3/__init__.py delete mode 100644 keystoneauth1/extras/_saml2/v3/adfs.py delete mode 100644 keystoneauth1/extras/_saml2/v3/base.py delete mode 100644 keystoneauth1/extras/_saml2/v3/saml2.py delete mode 100644 keystoneauth1/extras/kerberos/__init__.py delete mode 100644 keystoneauth1/extras/kerberos/_loading.py delete mode 100644 keystoneauth1/extras/oauth1/__init__.py delete mode 100644 keystoneauth1/extras/oauth1/_loading.py delete mode 100644 keystoneauth1/extras/oauth1/v3.py delete mode 100644 keystoneauth1/fixture/__init__.py delete mode 100644 keystoneauth1/fixture/discovery.py delete mode 100644 keystoneauth1/fixture/exception.py delete mode 100644 keystoneauth1/fixture/hooks.py delete mode 100644 keystoneauth1/fixture/keystoneauth_betamax.py delete mode 100644 keystoneauth1/fixture/serializer.py delete mode 100644 keystoneauth1/fixture/v2.py delete mode 100644 keystoneauth1/fixture/v3.py delete mode 100644 keystoneauth1/hacking/__init__.py delete mode 100644 keystoneauth1/hacking/checks.py delete mode 100644 keystoneauth1/identity/__init__.py delete mode 100644 keystoneauth1/identity/access.py delete mode 100644 keystoneauth1/identity/base.py delete mode 100644 keystoneauth1/identity/generic/__init__.py delete mode 100644 keystoneauth1/identity/generic/base.py delete mode 100644 keystoneauth1/identity/generic/password.py delete mode 100644 keystoneauth1/identity/generic/token.py delete mode 100644 keystoneauth1/identity/v2.py delete mode 100644 keystoneauth1/identity/v3/__init__.py delete mode 100644 keystoneauth1/identity/v3/base.py delete mode 100644 keystoneauth1/identity/v3/federation.py delete mode 100644 keystoneauth1/identity/v3/k2k.py delete mode 100644 keystoneauth1/identity/v3/oidc.py delete mode 100644 keystoneauth1/identity/v3/password.py delete mode 100644 keystoneauth1/identity/v3/token.py delete mode 100644 keystoneauth1/identity/v3/tokenless_auth.py delete mode 100644 keystoneauth1/identity/v3/totp.py delete mode 100644 keystoneauth1/loading/__init__.py delete mode 100644 keystoneauth1/loading/_plugins/__init__.py delete mode 100644 keystoneauth1/loading/_plugins/admin_token.py delete mode 100644 keystoneauth1/loading/_plugins/identity/__init__.py delete mode 100644 keystoneauth1/loading/_plugins/identity/generic.py delete mode 100644 keystoneauth1/loading/_plugins/identity/v2.py delete mode 100644 keystoneauth1/loading/_plugins/identity/v3.py delete mode 100644 keystoneauth1/loading/_plugins/noauth.py delete mode 100644 keystoneauth1/loading/_utils.py delete mode 100644 keystoneauth1/loading/adapter.py delete mode 100644 keystoneauth1/loading/base.py delete mode 100644 keystoneauth1/loading/cli.py delete mode 100644 keystoneauth1/loading/conf.py delete mode 100644 keystoneauth1/loading/identity.py delete mode 100644 keystoneauth1/loading/opts.py delete mode 100644 keystoneauth1/loading/session.py delete mode 100644 keystoneauth1/noauth.py delete mode 100644 keystoneauth1/plugin.py delete mode 100644 keystoneauth1/service_token.py delete mode 100644 keystoneauth1/session.py delete mode 100644 keystoneauth1/tests/__init__.py delete mode 100644 keystoneauth1/tests/unit/__init__.py delete mode 100644 keystoneauth1/tests/unit/access/__init__.py delete mode 100644 keystoneauth1/tests/unit/access/test_v2_access.py delete mode 100644 keystoneauth1/tests/unit/access/test_v2_service_catalog.py delete mode 100644 keystoneauth1/tests/unit/access/test_v3_access.py delete mode 100644 keystoneauth1/tests/unit/access/test_v3_service_catalog.py delete mode 100644 keystoneauth1/tests/unit/client_fixtures.py delete mode 100644 keystoneauth1/tests/unit/data/README delete mode 100644 keystoneauth1/tests/unit/data/keystone_v2_sample_request.json delete mode 100644 keystoneauth1/tests/unit/data/keystone_v2_sample_response.json delete mode 100644 keystoneauth1/tests/unit/data/keystone_v3_sample_request.json delete mode 100644 keystoneauth1/tests/unit/data/keystone_v3_sample_response.json delete mode 100644 keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml delete mode 100644 keystoneauth1/tests/unit/data/ksa_serializer_data.json delete mode 100644 keystoneauth1/tests/unit/data/test_pre_record_hook.json delete mode 100644 keystoneauth1/tests/unit/exceptions/__init__.py delete mode 100644 keystoneauth1/tests/unit/exceptions/test_exceptions.py delete mode 100644 keystoneauth1/tests/unit/extras/__init__.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/__init__.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/base.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/test_mapped.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/test_v3.py delete mode 100644 keystoneauth1/tests/unit/extras/kerberos/utils.py delete mode 100644 keystoneauth1/tests/unit/extras/oauth1/__init__.py delete mode 100644 keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py delete mode 100644 keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py delete mode 100644 keystoneauth1/tests/unit/extras/saml2/__init__.py delete mode 100644 keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml delete mode 100644 keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml delete mode 100644 keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py delete mode 100644 keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml delete mode 100644 keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml delete mode 100644 keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml delete mode 100644 keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py delete mode 100644 keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py delete mode 100644 keystoneauth1/tests/unit/extras/saml2/utils.py delete mode 100644 keystoneauth1/tests/unit/identity/__init__.py delete mode 100644 keystoneauth1/tests/unit/identity/test_access.py delete mode 100644 keystoneauth1/tests/unit/identity/test_identity_common.py delete mode 100644 keystoneauth1/tests/unit/identity/test_identity_v2.py delete mode 100644 keystoneauth1/tests/unit/identity/test_identity_v3.py delete mode 100644 keystoneauth1/tests/unit/identity/test_identity_v3_federation.py delete mode 100644 keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py delete mode 100644 keystoneauth1/tests/unit/identity/test_password.py delete mode 100644 keystoneauth1/tests/unit/identity/test_token.py delete mode 100644 keystoneauth1/tests/unit/identity/test_tokenless_auth.py delete mode 100644 keystoneauth1/tests/unit/identity/utils.py delete mode 100644 keystoneauth1/tests/unit/k2k_fixtures.py delete mode 100644 keystoneauth1/tests/unit/keystoneauth_fixtures.py delete mode 100644 keystoneauth1/tests/unit/loading/__init__.py delete mode 100644 keystoneauth1/tests/unit/loading/test_adapter.py delete mode 100644 keystoneauth1/tests/unit/loading/test_cli.py delete mode 100644 keystoneauth1/tests/unit/loading/test_conf.py delete mode 100644 keystoneauth1/tests/unit/loading/test_entry_points.py delete mode 100644 keystoneauth1/tests/unit/loading/test_generic.py delete mode 100644 keystoneauth1/tests/unit/loading/test_loading.py delete mode 100644 keystoneauth1/tests/unit/loading/test_session.py delete mode 100644 keystoneauth1/tests/unit/loading/test_v3.py delete mode 100644 keystoneauth1/tests/unit/loading/utils.py delete mode 100644 keystoneauth1/tests/unit/matchers.py delete mode 100644 keystoneauth1/tests/unit/oidc_fixtures.py delete mode 100644 keystoneauth1/tests/unit/test_betamax_fixture.py delete mode 100644 keystoneauth1/tests/unit/test_betamax_hooks.py delete mode 100644 keystoneauth1/tests/unit/test_betamax_serializer.py delete mode 100644 keystoneauth1/tests/unit/test_discovery.py delete mode 100644 keystoneauth1/tests/unit/test_fixtures.py delete mode 100644 keystoneauth1/tests/unit/test_hacking_checks.py delete mode 100644 keystoneauth1/tests/unit/test_matchers.py delete mode 100644 keystoneauth1/tests/unit/test_noauth.py delete mode 100644 keystoneauth1/tests/unit/test_service_token.py delete mode 100644 keystoneauth1/tests/unit/test_session.py delete mode 100644 keystoneauth1/tests/unit/test_token_endpoint.py delete mode 100644 keystoneauth1/tests/unit/test_utils.py delete mode 100644 keystoneauth1/tests/unit/utils.py delete mode 100644 keystoneauth1/token_endpoint.py delete mode 100644 releasenotes/notes/.placeholder delete mode 100644 releasenotes/notes/1583780-700f99713e06324e.yaml delete mode 100644 releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml delete mode 100644 releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml delete mode 100644 releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml delete mode 100644 releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml delete mode 100644 releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml delete mode 100644 releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml delete mode 100644 releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml delete mode 100644 releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml delete mode 100644 releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml delete mode 100644 releasenotes/notes/bug-1654847-acdf9543158329ec.yaml delete mode 100644 releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml delete mode 100644 releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml delete mode 100644 releasenotes/notes/microversion-header-support-901acd820a21d788.yaml delete mode 100644 releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml delete mode 100644 releasenotes/notes/user-agent-generation-b069100508c06177.yaml delete mode 100644 releasenotes/source/_static/.placeholder delete mode 100644 releasenotes/source/_templates/.placeholder delete mode 100644 releasenotes/source/conf.py delete mode 100644 releasenotes/source/index.rst delete mode 100644 releasenotes/source/mitaka.rst delete mode 100644 releasenotes/source/newton.rst delete mode 100644 releasenotes/source/ocata.rst delete mode 100644 releasenotes/source/unreleased.rst delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/tox_install.sh delete mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 2ee9e0d..0000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -branch = True -source = keystoneauth1 -omit = keystoneauth1/tests/* - -[report] -ignore_errors = True diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5395e5c..0000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -.coverage -.testrepository -subunit.log -.venv -*,cover -cover -*.pyc -.idea -*.sw? -*.egg -*~ -.tox -AUTHORS -ChangeLog -build -dist -keystoneauth1.egg-info -doc/source/api -# Development environment files -.project -.pydevproject -# Temporary files created during test, but not removed -examples/pki/certs/tmp* -# Files created by releasenotes build -releasenotes/build diff --git a/.gitreview b/.gitreview deleted file mode 100644 index 96e3cc0..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/keystoneauth.git diff --git a/.mailmap b/.mailmap deleted file mode 100644 index c804401..0000000 --- a/.mailmap +++ /dev/null @@ -1,6 +0,0 @@ -# Format is: -# -# - - - diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6595c1a..0000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystoneauth1/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 798f7eb..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,18 +0,0 @@ -If 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/keystoneauth diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index e56cfff..0000000 --- a/HACKING.rst +++ /dev/null @@ -1,24 +0,0 @@ -Keystone 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 -======= - -keystoneauth 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/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index af613b1..0000000 --- a/LICENSE +++ /dev/null @@ -1,209 +0,0 @@ -Copyright (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 keystoneauth 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. diff --git a/README b/README new file mode 100644 index 0000000..8fcd2b2 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For ongoing work on maintaining OpenStack packages in the Debian +distribution, please see the Debian OpenStack packaging team at +https://wiki.debian.org/OpenStack/. + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index 0bb1d4a..0000000 --- a/README.rst +++ /dev/null @@ -1,35 +0,0 @@ -======================== -Team and repository tags -======================== - -.. image:: https://governance.openstack.org/badges/keystoneauth.svg - :target: https://governance.openstack.org/reference/tags/index.html - -.. Change things from this point on - -============ -keystoneauth -============ - -.. image:: https://img.shields.io/pypi/v/keystoneauth1.svg - :target: https://pypi.python.org/pypi/keystoneauth1/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/dm/keystoneauth1.svg - :target: https://pypi.python.org/pypi/keystoneauth1/ - :alt: Downloads - -This package contains tools for authenticating to an OpenStack-based cloud. -These tools include: - -* Authentication plugins (password, token, and federation based) -* Discovery mechanisms to determine API version support -* A session that is used to maintain client settings across requests (based on - the requests Python library) - -Further information: - -* Free software: Apache license -* Documentation: https://docs.openstack.org/keystoneauth/latest/ -* Source: https://git.openstack.org/cgit/openstack/keystoneauth -* Bugs: https://bugs.launchpad.net/keystoneauth diff --git a/bindep.txt b/bindep.txt deleted file mode 100644 index 2a783e6..0000000 --- a/bindep.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This is a cross-platform list tracking distribution packages needed for install and tests; -# see https://docs.openstack.org/infra/bindep/ for additional information. - -build-essential [platform:dpkg test] -python-dev [platform:dpkg test] -python-devel [platform:rpm test] -libkrb5-dev [platform:dpkg test] -krb5-devel [platform:rpm test] diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 567609b..0000000 --- a/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 31ab3ab..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,90 +0,0 @@ -# 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/keystoneauth.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystoneauth.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." diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/doc/ext/list_plugins.py b/doc/ext/list_plugins.py deleted file mode 100644 index 9626880..0000000 --- a/doc/ext/list_plugins.py +++ /dev/null @@ -1,93 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 inspect - -from docutils import nodes -from docutils.parsers import rst -from docutils.parsers.rst import directives -from docutils.statemachine import ViewList -from sphinx.util.nodes import nested_parse_with_titles - -from stevedore import extension - - -class ListAuthPluginsDirective(rst.Directive): - """Present a simple list of the plugins in a namespace.""" - - option_spec = { - 'class': directives.class_option, - 'overline-style': directives.single_char_or_unicode, - 'underline-style': directives.single_char_or_unicode, - } - - has_content = True - - @property - def app(self): - return self.state.document.settings.env.app - - def report_load_failure(mgr, ep, err): - self.app.warn(u'Failed to load %s: %s' % (ep.module_name, err)) - - def display_plugin(self, ext): - overline_style = self.options.get('overline-style', '') - underline_style = self.options.get('underline-style', '=') - - if overline_style: - yield overline_style * len(ext.name) - - yield ext.name - - if underline_style: - yield underline_style * len(ext.name) - - yield "\n" - - doc = inspect.getdoc(ext.obj) - if doc: - yield doc - yield "\n" - yield "------" - - yield "\n" - - for opt in ext.obj.get_options(): - yield ":%s: %s" % (opt.name, opt.help) - - yield "\n" - - def run(self): - mgr = extension.ExtensionManager( - 'keystoneauth1.plugin', - on_load_failure_callback=self.report_load_failure, - invoke_on_load=True, - ) - - result = ViewList() - - for name in sorted(mgr.names()): - for line in self.display_plugin(mgr[name]): - for l in line.splitlines(): - result.append(l, mgr[name].entry_point.module_name) - - # Parse what we have into a new section. - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, result, node) - - return node.children - - -def setup(app): - app.info('loading keystoneauth1 plugins') - app.add_directive('list-auth-plugins', ListAuthPluginsDirective) diff --git a/doc/source/authentication-plugins.rst b/doc/source/authentication-plugins.rst deleted file mode 100644 index cba778f..0000000 --- a/doc/source/authentication-plugins.rst +++ /dev/null @@ -1,303 +0,0 @@ -====================== -Authentication Plugins -====================== - -Introduction -============ - -Authentication plugins provide a generic means by which to extend the -authentication mechanisms known to OpenStack clients. - -In the vast majority of cases the authentication plugins used will be those -written for use with the OpenStack Identity Service (Keystone), however this is -not the only possible case, and the mechanisms by which authentication plugins -are used and implemented should be generic enough to cover completely -customized authentication solutions. - -The subset of authentication plugins intended for use with an OpenStack -Identity server (such as Keystone) are called Identity Plugins. - - -Available Plugins -================= - -Keystoneauth ships with a number of plugins and particularly Identity -Plugins. - -V2 Identity Plugins -------------------- - -Standard V2 identity plugins are defined in the module: -:py:mod:`keystoneauth1.identity.v2` - -They include: - -- :py:class:`~keystoneauth1.identity.v2.Password`: Authenticate against - a V2 identity service using a username and password. -- :py:class:`~keystoneauth1.identity.v2.Token`: Authenticate against a - V2 identity service using an existing token. - -V2 identity plugins must use an `auth_url` that points to the root of a V2 -identity server URL, i.e.: ``http://hostname:5000/v2.0``. - -V3 Identity Plugins -------------------- - -Standard V3 identity plugins are defined in the module -:py:mod:`keystoneauth1.identity.v3`. - -V3 Identity plugins are slightly different from their V2 counterparts as a V3 -authentication request can contain multiple authentication methods. To handle -this V3 defines a number of different -:py:class:`~keystoneauth1.identity.v3.AuthMethod` classes: - -- :py:class:`~keystoneauth1.identity.v3.PasswordMethod`: Authenticate - against a V3 identity service using a username and password. -- :py:class:`~keystoneauth1.identity.v3.TokenMethod`: Authenticate against - a V3 identity service using an existing token. -- :py:class:`~keystoneauth1.identity.v3.TOTPMethod`: Authenticate against - a V3 identity service using Time-Based One-Time Password (TOTP). -- :py:class:`~keystoneauth1.identity.v3.TokenlessAuth`: Authenticate against - a V3 identity service using tokenless authentication. -- :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`: Authenticate - against a V3 identity service using Kerberos. - -The :py:class:`~keystoneauth1.identity.v3.AuthMethod` objects are then -passed to the :py:class:`~keystoneauth1.identity.v3.Auth` plugin:: - - >>> from keystoneauth1 import session - >>> from keystoneauth1.identity import v3 - >>> password = v3.PasswordMethod(username='user', - ... password='password', - ... user_domain_name='default') - >>> auth = v3.Auth(auth_url='http://my.keystone.com:5000/v3', - ... auth_methods=[password], - ... project_id='projectid') - >>> sess = session.Session(auth=auth) - -As in the majority of cases you will only want to use one -:py:class:`~keystoneauth1.identity.v3.AuthMethod` there are also helper -authentication plugins for the various -:py:class:`~keystoneauth1.identity.v3.AuthMethod` which can be used more -like the V2 plugins: - -- :py:class:`~keystoneauth1.identity.v3.Password`: Authenticate using - only a :py:class:`~keystoneauth1.identity.v3.PasswordMethod`. -- :py:class:`~keystoneauth1.identity.v3.Token`: Authenticate using only a - :py:class:`~keystoneauth1.identity.v3.TokenMethod`. -- :py:class:`~keystoneauth1.identity.v3.TOTP`: Authenticate using - only a :py:class:`~keystoneauth1.identity.v3.TOTPMethod`. -- :py:class:`~keystoneauth1.extras.kerberos.Kerberos`: Authenticate using - only a :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`. - -:: - - >>> auth = v3.Password(auth_url='http://my.keystone.com:5000/v3', - ... username='username', - ... password='password', - ... project_id='projectid', - ... user_domain_name='default') - >>> sess = session.Session(auth=auth) - -This will have exactly the same effect as using the single -:py:class:`~keystoneauth1.identity.v3.PasswordMethod` above. - -V3 identity plugins must use an `auth_url` that points to the root of a V3 -identity server URL, i.e.: ``http://hostname:5000/v3``. - -Federation -========== - -The following V3 plugins are provided to support federation: - -- :py:class:`~keystoneauth1.extras.kerberos.MappedKerberos`: Federated (mapped) - Kerberos. -- :py:class:`~keystoneauth1.extras._saml2.v3.Password`: SAML2 password - authentication. -- :py:class:`~keystoneauth1.identity.v3.Keystone2Keystone`: Keystone to - Keystone Federation. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAccessToken`: Plugin to - reuse an existing OpenID Connect access token. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode`: OpenID - Connect Authorization Code grant type. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectClientCredentials`: OpenID - Connect Client Credentials grant type. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectPassword`: OpenID Connect - Resource Owner Password Credentials grant type. - - -Version Independent Identity Plugins ------------------------------------- - -Standard version independent identity plugins are defined in the module -:py:mod:`keystoneauth1.identity.generic`. - -For the cases of plugins that exist under both the identity V2 and V3 APIs -there is an abstraction to allow the plugin to determine which of the V2 and V3 -APIs are supported by the server and use the most appropriate API. - -These plugins are: - -- :py:class:`~keystoneauth1.identity.generic.Password`: Authenticate - using a user/password against either v2 or v3 API. -- :py:class:`~keystoneauth1.identity.generic.Token`: Authenticate using - an existing token against either v2 or v3 API. - -These plugins work by first querying the identity server to determine available -versions and so the `auth_url` used with the plugins should point to the base -URL of the identity server to use. If the `auth_url` points to either a V2 or -V3 endpoint it will restrict the plugin to only working with that version of -the API. - -Simple Plugins --------------- - -In addition to the Identity plugins a simple plugin that will always use the -same provided token and endpoint is available. This is useful in situations -where you have an token or in testing when you specifically know the endpoint -you want to communicate with. - -It can be found at :py:class:`keystoneauth1.token_endpoint.Token`. - - -V3 OAuth 1.0a Plugins ---------------------- - -There also exists a plugin for OAuth 1.0a authentication. We provide a helper -authentication plugin at: -:py:class:`~keystoneauth1.extras.oauth1.V3OAuth1`. -The plugin requires the OAuth consumer's key and secret, as well as the OAuth -access token's key and secret. For example:: - - >>> from keystoneauth1.extras import oauth1 - >>> from keystoneauth1 import session - >>> a = oauth1.V3OAuth1('http://my.keystone.com:5000/v3', - ... consumer_key=consumer_id, - ... consumer_secret=consumer_secret, - ... access_key=access_token_key, - ... access_secret=access_token_secret) - >>> s = session.Session(auth=a) - - -Tokenless Auth -============== - -A plugin for tokenless authentication also exists. It provides a means to -authorize client operations within the Identity server by using an X.509 -TLS client certificate without having to issue a token. We provide a -tokenless authentication plugin at: - -- :class:`~keystoneauth1.identity.v3.TokenlessAuth` - -It is mostly used by service clients for token validation and here is -an example of how this plugin would be used in practice:: - - >>> from keystoneauth1 import session - >>> from keystoneauth1.identity import v3 - >>> auth = v3.TokenlessAuth(auth_url='https://keystone:5000/v3', - ... domain_name='my_service_domain') - >>> sess = session.Session( - ... auth=auth, - ... cert=('/opt/service_client.crt', - ... '/opt/service_client.key'), - ... verify='/opt/ca.crt') - - -Loading Plugins by Name -======================= - -In auth_token middleware and for some service to service communication it is -possible to specify a plugin to load via name. The authentication options that -are available are then specific to the plugin that you specified. Currently the -authentication plugins that are available in `keystoneauth` are: - -- password: :py:class:`keystoneauth1.identity.generic.Password` -- token: :py:class:`keystoneauth1.identity.generic.Token` -- v2password: :py:class:`keystoneauth1.identity.v2.Password` -- v2token: :py:class:`keystoneauth1.identity.v2.Token` -- v3password: :py:class:`keystoneauth1.identity.v3.Password` -- v3token: :py:class:`keystoneauth1.identity.v3.Token` -- v3fedkerb: :py:class:`keystoneauth1.extras.kerberos.MappedKerberos` -- v3kerberos: :py:class:`keystoneauth1.extras.kerberos.Kerberos` -- v3oauth1: :py:class:`keystoneauth1.extras.oauth1.v3.OAuth1` -- v3oidcaccesstoken: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAccessToken` -- v3oidcauthcode: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode` -- v3oidcclientcredentials: :py:class:`keystoneauth1.identity.v3:OpenIDConnectClientCredentials` -- v3oidcpassword: :py:class:`keystoneauth1.identity.v3:OpenIDConnectPassword` -- v3samlpassword: :py:class:`keystoneauth1.extras._saml2.v3.Password` -- v3tokenlessauth: :py:class:`keystoneauth1.identity.v3.TokenlessAuth` -- v3totp: :py:class:`keystoneauth1.identity.v3.TOTP` - - -Creating Authentication Plugins -=============================== - -Creating an Identity Plugin ---------------------------- - -If you have implemented a new authentication mechanism into the Identity -service then you will be able to reuse a lot of the infrastructure available -for the existing Identity mechanisms. As the V2 identity API is essentially -frozen, it is expected that new plugins are for the V3 API. - -To implement a new V3 plugin that can be combined with others you should -implement the base :py:class:`keystoneauth1.identity.v3.AuthMethod` class -and implement the -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` function. -If your Plugin cannot be used in conjunction with existing -:py:class:`keystoneauth1.identity.v3.AuthMethod` then you should just -override :py:class:`keystoneauth1.identity.v3.Auth` directly. - -The new :py:class:`~keystoneauth1.identity.v3.AuthMethod` should take all -the required parameters via -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.__init__` and return from -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` a tuple -with the unique identifier of this plugin (e.g. *password*) and a dictionary -containing the payload of values to send to the authentication server. The -session, calling auth object and request headers are also passed to this -function so that the plugin may use or manipulate them. - -You should also provide a class that inherits from -:py:class:`keystoneauth1.identity.v3.Auth` with an instance of your new -:py:class:`~keystoneauth1.identity.v3.AuthMethod` as the `auth_methods` -parameter to :py:class:`keystoneauth1.identity.v3.Auth`. - -By convention (and like above) these are named `PluginType` and -`PluginTypeMethod` (for example -:py:class:`~keystoneauth1.identity.v3.Password` and -:py:class:`~keystoneauth1.identity.v3.PasswordMethod`). - - -Creating a Custom Plugin ------------------------- - -To implement an entirely new plugin you should implement the base class -:py:class:`keystoneauth1.plugin.BaseAuthPlugin` and provide the -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint`, -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` and -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` methods. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` is called to retrieve -the string token from a plugin. It is intended that a plugin will cache a -received token and so if the token is still valid then it should be re-used -rather than fetching a new one. A session object is provided with which the -plugin can contact it's server. (Note: use `authenticated=False` when making -those requests or it will end up being called recursively). The return value -should be the token as a string. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint` is called to -determine a base URL for a particular service's requests. The keyword arguments -provided to the function are those that are given by the `endpoint_filter` -variable in :py:meth:`keystoneauth1.session.Session.request`. A session object -is also provided so that the plugin may contact an external source to determine -the endpoint. Again this will be generally be called once per request and so -it is up to the plugin to cache these responses if appropriate. The return -value should be the base URL to communicate with. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` should also be -implemented to clear the current user credentials so that on the next -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` call a new token can -be retrieved. - -The most simple example of a plugin is the -:py:class:`keystoneauth1.token_endpoint.Token` plugin. diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 2a41a1a..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- -# -# keystoneauth1 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. - -from __future__ import unicode_literals - -import os -import sys - -import pbr.version - - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), - '..', '..'))) -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), - '..'))) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ---------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.intersphinx', - 'openstackdocstheme', - 'ext.list_plugins', - ] - -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. -project = 'keystoneauth1' -copyright = 'OpenStack Contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -version_info = pbr.version.VersionInfo('keystoneauth1') -# The short X.Y version. -version = version_info.version_string() -# The full version, including alpha/beta/rc tags. -release = version_info.release_string() - -# 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 = 'sphinx' - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['keystoneauth1.'] - -# 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 not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%Y-%m-%d %H:%M' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_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 = 'keystoneauthdoc' - - -# -- 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', 'keystoneauth1.tex', - 'keystoneauth1 Documentation', - 'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss', - '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 - -ksc = 'https://docs.openstack.org/python-keystoneclient/latest/' - -intersphinx_mapping = { - 'python': ('http://docs.python.org/', None), - 'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None), - 'keystoneclient': (ksc, None), -} - -# -- Options for openstackdocstheme ------------------------------------------- -repository_name = 'openstack/keystoneauth' -bug_project = 'keystoneauth' -bug_tag = 'doc' diff --git a/doc/source/extras.rst b/doc/source/extras.rst deleted file mode 100644 index 49f542d..0000000 --- a/doc/source/extras.rst +++ /dev/null @@ -1,73 +0,0 @@ -====== -Extras -====== - -The extensibility of keystoneauth plugins is purposefully designed to allow a -range of different authentication mechanisms that don't have to reside in the -upstream packages. There are however a number of plugins that upstream supports -that involve additional dependencies that the keystoneauth package cannot -depend upon directly. - -To get around this we utilize setuptools `extras dependencies `_ for additional -plugins. To use a plugin like the kerberos plugin that has additional -dependencies you must install the additional dependencies like:: - - pip install keystoneauth1[kerberos] - -By convention (not a requirement) extra plugins have a module located in the -keystoneauth1.extras module with the same name as the dependency. eg:: - - from keystoneauth1.extras import kerberos - -There is no keystoneauth specific check that the correct dependencies are -installed for accessing a module. You would expect to see standard python -ImportError when the required dependencies are not found. - -Examples -======== - -All extras plugins follow the pattern: - -1. import plugin module -2. instantiate the plugin -3. call get_token method of the plugin passing it a session object - to get a token - -Kerberos --------- - -Get domain-scoped token using -:py:class:`~keystoneauth1.extras.kerberos.Kerberos`:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.Kerberos('http://example.com:5000/v3') - sess = session.Session(plugin) - token = plugin.get_token(sess) - -Get unscoped federated token:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.MappedKerberos( - auth_url='http://example.com:5000/v3', protocol='example_protocol', - identity_provider='example_identity_provider') - - sess = session.Session() - token = plugin.get_token(sess) - -Get project scoped federated token:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.MappedKerberos( - auth_url='http://example.com:5000/v3', protocol='example_protocol', - identity_provider='example_identity_provider', - project_id='example_project_id') - - sess = session.Session() - token = plugin.get_token(sess) - project_id = plugin.get_project_id(sess) diff --git a/doc/source/history.rst b/doc/source/history.rst deleted file mode 100644 index 69ed4fe..0000000 --- a/doc/source/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../ChangeLog diff --git a/doc/source/images/graphs_authComp.svg b/doc/source/images/graphs_authComp.svg deleted file mode 100644 index 6be629c..0000000 --- a/doc/source/images/graphs_authComp.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - -AuthComp - - -AuthComp - -Auth -Component - - - -AuthComp->Reject - - -Reject -Unauthenticated -Requests - - -Service - -OpenStack -Service - - -AuthComp->Service - - -Forward -Authenticated -Requests - - - -Start->AuthComp - - - - - diff --git a/doc/source/images/graphs_authCompDelegate.svg b/doc/source/images/graphs_authCompDelegate.svg deleted file mode 100644 index 4788829..0000000 --- a/doc/source/images/graphs_authCompDelegate.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - -AuthCompDelegate - - -AuthComp - -Auth -Component - - - -AuthComp->Reject - - -Reject Requests -Indicated by the Service - - -Service - -OpenStack -Service - - -AuthComp->Service - - -Forward Requests -with Identiy Status - - -Service->AuthComp - - -Send Response OR -Reject Message - - - -Start->AuthComp - - - - - diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 579d7c5..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,49 +0,0 @@ -Common Authentication Library for OpenStack Clients -=================================================== - -Keystoneauth provides a standard way to do authentication and service requests -within the OpenStack ecosystem. It is designed for use in conjunction with the -existing OpenStack clients and for simplifying the process of writing new -clients. - -Contents: - -.. toctree:: - :maxdepth: 1 - - using-sessions - authentication-plugins - plugin-options - - extras - migrating - - api/modules - -Release Notes -============= - -.. toctree:: - :maxdepth: 1 - - history - -Contributing -============ - -Code is hosted `on GitHub`_. Submit bugs to the Keystone project on -`Launchpad`_. Submit code to the ``openstack/keystoneauth`` project -using `Gerrit`_. - -.. _on GitHub: https://github.com/openstack/keystoneauth -.. _Launchpad: https://launchpad.net/keystoneauth -.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow - -Run tests with ``tox``. - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/source/migrating.rst b/doc/source/migrating.rst deleted file mode 100644 index a787358..0000000 --- a/doc/source/migrating.rst +++ /dev/null @@ -1,102 +0,0 @@ -============================= -Migrating from keystoneclient -============================= - -When keystoneauth was extracted from keystoneclient the basic usage of the -session, adapter and auth plugins purposefully did not change. If you are using -them in a supported fashion from keystoneclient then the transition should be -fairly simple. - -Authentication Plugins -====================== - -The authentication plugins themselves changed very little however there were -changes to the way plugins are loaded and some of the supporting classes. - -Plugin Loading --------------- - -In keystoneclient auth plugin loading is managed by the class itself. This -method proved useful in allowing the plugin to control the way it was loaded -however it linked the authentication logic with the config and CLI loading. - -In keystoneauth this has been severed and the auth plugin is handled separately -from the mechanism that loads it. - -Authentication plugins still implement the base authentication class -:py:class:`~keystoneauth1.plugin.BaseAuthPlugin`. To make the plugins capable -of being loaded from CLI or CONF file you should implement the base -:py:class:`~keystoneauth1.loading.BaseLoader` class which is loaded when -`--os-auth-type` is used. This class handles the options that are -presented, and then constructs the authentication plugin for use by the -application. - -Largely the options that are returned will be the same as what was used in -keystoneclient however in keystoneclient the options used -:py:class:`oslo_config.cfg.Opt` objects. Due to trying to keep minimal -dependencies there is no direct dependency from keystoneauth on oslo.config and -instead options should be specified as :py:class:`~keystoneauth1.loading.Opt` -objects. - -To ensure distinction between the plugins, the setuptools entrypoints that -plugins register at has been updated to reflect keystoneauth1 and should now -be: keystoneauth1.plugin - -AccessInfo Objects ------------------- - -AccessInfo objects are a representation of the information stored within a -token. In keystoneclient these objects were dictionaries of the token data with -property accessors. In keystoneauth the dictionary interface has been removed -and just the property accessors are available. - -The creation function has also changed. The -:py:meth:`keystoneclient.access.AccessInfo.factory` method has been removed -and replaced with the :py:func:`keystoneauth1.access.create`. - -Step-by-step migration example ------------------------------- - -Add ``keystoneauth1`` to requirements.txt - -In the code do the following change:: - - -from keystoneclient import auth - +from keystoneauth1 import plugin - -consequently:: - - -auth.BaseAuthPlugin - +plugin.BaseAuthPlugin - -To import service catalog:: - - -from keystoneclient import service_catalog - +from keystoneauth1.access import service_catalog - -To get url using service catalog *endpoint_type* parameter was changed to -*interface*:: - - -service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., endpoint_type=interface) - +service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., interface=interface) - -Obtaining the session:: - - -from keystoneclient import session - +from keystoneauth1 import loading as ks_loading - - -_SESSION = session.Session.load_from_conf_options( - -auth_plugin = auth.load_from_conf_options(conf, NEUTRON_GROUP) - +_SESSION = ks_loading.load_session_from_conf_options( - +auth_plugin = ks_loading.load_auth_from_conf_options(conf, NEUTRON_GROUP) - -Mocking session for test purposes:: - - -@mock.patch('keystoneclient.session.Session') - +@mock.patch('keystoneauth1.session.Session') - -Token fixture imports haven't change much:: - - -from keystoneclient.fixture import V2Token - +from keystoneauth1.fixture import V2Token - diff --git a/doc/source/plugin-options.rst b/doc/source/plugin-options.rst deleted file mode 100644 index 4417f81..0000000 --- a/doc/source/plugin-options.rst +++ /dev/null @@ -1,92 +0,0 @@ -============== -Plugin Options -============== - -Using plugins via config file ------------------------------ - -When using the plugins via config file you define the plugin name as -``auth_type``. The options of the plugin are then specified while replacing -``-`` with ``_`` to be valid in configuration. - -For example to use the password_ plugin in a config file you would specify: - -.. code-block:: ini - - [section] - auth_url = http://keystone.example.com:5000/ - auth_type = password - username = myuser - password = mypassword - project_name = myproject - default_domain_name = mydomain - - -Using plugins via CLI ---------------------- - -When using auth plugins via CLI via ``os-client-config`` or ``shade`` you can -specify parameters via environment configuration by using the pattern ``OS_`` -followed by the uppercase parameter name replacing ``-`` with ``_``. - -For example to use the password_ plugin via environment variable you specify: - -.. code-block:: bash - - export OS_AUTH_TYPE=password - export OS_AUTH_URL=http://keystone.example.com:5000/ - export OS_USERNAME=myuser - export OS_PASSWORD=mypassword - export OS_PROJECT_NAME=myproject - export OS_DEFAULT_DOMAIN_NAME=mydomain - -Specifying operations via CLI parameter will override the environment -parameter. These are specified with the pattern ``--os-`` and the parameter -name. Using the password_ example again: - -.. code-block:: bash - - openstack --os-auth-type password \ - --os-auth-url http://keystone.example.com:5000/ \ - --os-username myuser \ - --os-password mypassword \ - --os-project-name myproject \ - --os-default-domain-name mydomain \ - operation - -Additional loaders ------------------- - -The configuration and CLI loaders are quite commonly used however similar -concepts are found in other situations such as ``os-client-config`` in which -you specify authentication and other cloud parameters in a ``clouds.yaml`` -file. - -Loaders such as these use the same plugin options listed below, but via their -own mechanism. In ``os-client-config`` the password_ plugin looks like: - -.. code-block:: yaml - - clouds: - mycloud: - auth_type: password - auth: - auth_url: http://keystone.example.com:5000/ - auth_type: password - username: myuser - password: mypassword - project_name: myproject - default_domain_name: mydomain - -However different services may implement loaders in their own way and you -should consult their relevant documentation. The same auth options will be -available. - - -Available Plugins ------------------ - -This is a listing of all included plugins and the options that they accept. -Plugins are listed alphabetically and not in any order of priority. - -.. list-auth-plugins:: diff --git a/doc/source/using-sessions.rst b/doc/source/using-sessions.rst deleted file mode 100644 index ffd140f..0000000 --- a/doc/source/using-sessions.rst +++ /dev/null @@ -1,440 +0,0 @@ -============== -Using Sessions -============== - -Introduction -============ - -The :py:class:`keystoneauth1.session.Session` class was introduced into -keystoneauth1 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 service and version discovery - - Clients are not expected to have any knowledge of an identity token or any - other form of identification credential. Service, endpoint, major version - discovery and microversion support discovery are handled by the Session and - plugins. Discovery information is automatically cached in memory, so the user - need not worry about excessive use of discovery metadata. - - -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 using keystoneclient to wrap a Session:: - - >>> 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_name='proj', - ... user_domain_id='default', - ... project_domain_id='default') - >>> sess = session.Session(auth=auth, - ... verify='/path/to/ca.cert') - >>> ks = client.Client(session=sess) - >>> users = ks.users.list() - -As other OpenStack client libraries adopt this means of operating they will be -created in a similar fashion by passing the Session object to the client's -constructor. - - -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. - -Major Version Discovery and Microversion Support ------------------------------------------------- - -In OpenStack the root URLs of available services are distributed to the user -in an object called the Service Catalog, which is part of the token they -receive. Clients are expected to use the URLs from the Service Catalog rather -than have them provided. The root URL of a given service is referred to as the -`endpoint` of the service. The URL of a specific version of a service is -referred to as a `versioned endpoint`. REST requests for a service are made -against a given `versioned endpoint`. - -The topic of Major API versions and microversions can be confusing. As -`keystoneauth` provides facilities for discovery of versioned endpoints -associated with a Major API Version and for fetching information about -the microversions that versioned endpoint supports, it is important to be aware -of the distinction between the two. - -Conceptually the most important thing to understand is that a Major API Version -describes the URL of a discrete versioned endpoint, while a given versioned -endpoint might have properties that express that it supports a range of -microversions. - -When a user wants to make a REST request against a service, the user expresses -the Major API version and the type of service so that the appropriate versioned -endpoint can be found and used. For example, a user might request -version 2 of the compute service from cloud.example.com and end up with a -versioned endpoint of ``https://compute.example.com/v2``. - -Each service provides a discovery document at the root of each versioned -endpoint that contains information about that versioned endpoint. Each service -also provides a document at the root of the unversioned endpoint that contains -a list of the discovery documents for all of the available versioned endpoints. -By examining these documents, it is possible to find the versioned endpoint -that corresponds with the user's desired Major API version. - -Each of those documents may also indicate that the given versioned endpoint -supports microversions by listing a minimum and maximum microversion that it -understands. As a result of having found the versioned endpoint for the -requested Major API version, the user will also know which microversions, -if any, may be used in requests to that versioned endpoint. - -When a client makes REST requests to the Major API version's endpoint, the -client can, optionally, on a request-by-request basis, include a header -specifying that the individual request use the behavior defined by the given -microversion. If a client does not request a microversion, the service will -behave as if the minimum supported microversion was specified. - -.. note: The changes that each microversion reflects are documented elsewhere - and are not information provided by the discovery process. - -The overall transaction then has three parts: - -* What is the endpoint for a given Major API version of a given service? -* What are the minimum and maximum microversions supported at that endpoint? -* Which one of that range of microversions, if any, does the user want to use - for a given request? - -`keystoneauth` provides facilities for discovering the endpoint for a given -Major API of a given service, as well as reporting the available microversion -ranges that endpoint supports, if any. - -More information is available in the `API-WG Specs`_ on the topics of -`Microversions`_ and `Consuming the Catalog`_. - -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 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('/users', - endpoint_filter={'service_type': 'identity', - 'interface': 'admin', - 'region_name': 'myregion', - 'min_version': '2.0', - 'max_version': '3.4', - 'discover_versions': False}) - -.. note:: The min_version and max_version arguments in this example indicate - acceptable range for finding the endpoint for the given Major API - versions. They are in the endpoint_filter, they are not requesting - the call to ``/users`` be made at a specific microversion. - -`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. Can also be a list, in which case the - first matching interface will be used. Valid values are: - - - ``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. - -version - the minimum version, restricted to a given Major API. For instance, a - `version` of ``2.2`` will match ``2.2`` and ``2.3`` but not ``2.1`` or - ``3.0``. Mutually exclusive with `min_version` and `max_version`. - -min_version - the minimum version of a given API, intended to be used as the lower bound of - a range with `max_version`. See `max_version` for examples. Mutually - exclusive with `version`. - -max_version - the maximum version of a given API, intended to be used as the upper bound of - a range with `min_version`. For example:: - - 'min_version': '2.2', - 'max_version': '3.3' - - will match ``2.2``, ``2.10``, ``3.0``, and ``3.3``, but not ``1.42``, - ``2.1``, or ``3.20``. Mutually exclusive with `version`. - -.. note:: version, min_version and max_version are all used to help determine - the endpoint for a given Major API version of a service. - -discover_versions - whether or not version discovery should be run, even if not strictly - necessary. It is often possible to fulfill an endpoint request purely - from the catalog, meaning the version discovery API is a potentially - wasted additional call. However, it's possible that running discovery - instead of inference is desired. Defaults to ``True``. - -All version arguments (`version`, `min_version` and `max_version`) can -be given as: - -* string: ``'2.0'`` -* int: ``2`` -* float: ``2.0`` -* tuple of ints: ``(2, 0)`` - -`version` and `max_version` can also be given the string ``latest``, which -indicates that the highest available version should be used. - -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. - -If you want to further limit your service discovery by allowing experimental -APIs or disallowing deprecated APIs, you can use the ``allow`` parameter:: - - >>> resp = session.get('//volumes', - endpoint_filter={'service_type': 'volume', - 'interface': 'public', - 'version': 1}, - allow={'allow_deprecated': False}) - -The discoverable types of endpoints that `allow` can recognize are: - -- `allow_deprecated`: Allow deprecated version endpoints. - -- `allow_experimental`: Allow experimental version endpoints. - -- `allow_unknown`: Allow endpoints with an unrecognised status. - -The Session object creates a valid request by determining the URL matching the -filters and appending it to the provided path. 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. A specific mechanism may not know how to interpret -certain arguments in which case it may ignore them. For example the -:class:`keystoneauth1.token_endpoint.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. - -Using Adapters --------------- - -If the developer would prefer not to provide `endpoint_filter` with every API -call, a :class:`keystoneauth1.adapter.Adapter` can be created. The `Adapter` -constructor takes the same arguments as `endpoint_filter`, as well as a -`Session`. An `Adapter` behaves much like a `Session`, with the same REST -methods, but is "mounted" on the endpoint that would be found by -`endpoint_filter`. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='volume', - interface='public', - version=1) - response = adapter.get('/volumes') - -As with ``endpoint_filter`` on a Session, the ``version``, ``min_version`` -and ``max_version`` parameters exist to help determine the appropriate -endpoint for a Major API of a service. - -Endpoint Metadata ------------------ - -Both :class:`keystoneauth1.adapter.Adapter` and -:class:`keystoneauth1.session.Session` have a method for getting metadata about -the endpoint found for a given service: ``get_endpoint_data``. - -On the :class:`keystoneauth1.session.Session` it takes the same arguments as -`endpoint_filter`. - -On the :class:`keystoneauth1.adapter.Adapter` it does not take arguments, as -it returns the information for the Endpoint the Adapter is mounted on. - -``get_endpoint_data`` returns an :class:`keystoneauth1.discovery.EndpointData` -object. This object can be used to find information about the Endpoint, -including which major `api_version` was found, or which `interface` in case -of ranges, lists of input values or ``latest`` version. - -It can also be used to determine the `min_microversion` and `max_microversion` -supported by the API. If an API does not support microversions, the values for -both will be ``None``. It will also contain values for `next_min_version` and -`not_before` if they exist for the endpoint, or ``None`` if they do not. The -:class:`keystoneauth1.discovery.EndpointData` object will always contain -microversion related attributes regardless of whether the REST document does -or not. - -``get_endpoint_data`` makes use of the same cache as the rest of the discovery -process, so calling it should incur no undue expense. By default it will make -at least one version discovery call so that it can fetch microversion metadata. -If the user knows a service does not support microversions and is merely -curious as to which major version was discovered, `discover_versions` can be -set to `False` to prevent fetching microversion metadata. - -Requesting a Microversion -------------------------- - -A user who wants to specify a microversion for a given request can pass it to -the ``microversion`` parameter of the `request` method on the -:class:`keystoneauth1.session.Session` object, or the -:class:`keystoneauth1.adapter.Adapter` object. This will cause `keystoneauth` -to pass the appropriate header to the service informing the service of the -microversion the user wants. - -.. code-block:: python - - resp = session.get('/volumes', - microversion='3.15', - endpoint_filter={'service_type': 'volume', - 'interface': 'public', - 'min_version': '3', - 'max_version': 'latest'}) - -If the user is using a :class:`keystoneauth1.adapter.Adapter`, the -`service_type`, which is a part of the data sent in the microversion header, -will be taken from the Adapter's `service_type`. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='compute', - interface='public', - min_version='2.1') - response = adapter.get('/servers', microversion='2.38') - -The user can also provide a ``default_microversion`` parameter to the Adapter -constructor which will be used on all requests where an explicit microversion -is not requested. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='compute', - interface='public', - min_version='2.1', - default_microversion='2.38') - response = adapter.get('/servers') - -If the user is using a :class:`keystoneauth1.session.Session`, the -`service_type` will be taken from the `service_type` in `endpoint_filter`. - -If the `service_type` is the incorrect value to use for the microversion header -for the service in question, the parameter `microversion_service_type` can be -given. For instance, although keystoneauth already knows about Cinder, the -`service_type` for Cinder is ``block-storage`` but the microversion header -expects ``volume``. - -.. code-block:: python - - # Interactions with cinder do not need to explicitly override the - # microversion_service_type - it is only being used as an example for the - # use of the parameter. - resp = session.get('/volumes', - microversion='3.15', - microversion_service_type='volume', - endpoint_filter={'service_type': 'block-storage', - 'interface': 'public', - 'min_version': '3', - 'max_version': 'latest'}) - - -.. _API-WG Specs: http://specs.openstack.org/openstack/api-wg/ -.. _Consuming the Catalog: http://specs.openstack.org/openstack/api-wg/guidelines/consuming-catalog.html -.. _Microversions: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html#version-discovery - diff --git a/keystoneauth1/__init__.py b/keystoneauth1/__init__.py deleted file mode 100644 index 312aa58..0000000 --- a/keystoneauth1/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import pbr.version - - -__version__ = pbr.version.VersionInfo('keystoneauth1').version_string() diff --git a/keystoneauth1/_utils.py b/keystoneauth1/_utils.py deleted file mode 100644 index 2d6f0ae..0000000 --- a/keystoneauth1/_utils.py +++ /dev/null @@ -1,83 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 logging - -import iso8601 -import six - - -def get_logger(name): - name = name.replace(__name__.split('.')[0], 'keystoneauth') - return logging.getLogger(name) - - -logger = get_logger(__name__) - - -def normalize_time(timestamp): - """Normalize time in arbitrary timezone to UTC naive object.""" - offset = timestamp.utcoffset() - if offset is None: - return timestamp - return timestamp.replace(tzinfo=None) - offset - - -def parse_isotime(timestr): - """Parse time from ISO 8601 format.""" - try: - return iso8601.parse_date(timestr) - except iso8601.ParseError as e: - raise ValueError(six.text_type(e)) - except TypeError as e: - raise ValueError(six.text_type(e)) - - -def from_utcnow(**timedelta_kwargs): - r"""Calculate the time in the future from utcnow. - - :param \*\*timedelta_kwargs: - Passed directly to :class:`datetime.timedelta` to add to the current - time in UTC. - :returns: - The time in the future based on ``timedelta_kwargs``. - :rtype: - datetime.datetime - """ - now = datetime.datetime.utcnow() - delta = datetime.timedelta(**timedelta_kwargs) - return now + delta - - -def before_utcnow(**timedelta_kwargs): - r"""Calculate the time in the past from utcnow. - - :param \*\*timedelta_kwargs: - Passed directly to :class:`datetime.timedelta` to subtract from the - current time in UTC. - :returns: - The time in the past based on ``timedelta_kwargs``. - :rtype: - datetime.datetime - """ - now = datetime.datetime.utcnow() - delta = datetime.timedelta(**timedelta_kwargs) - return now - delta - - -# Detect if running on the Windows Subsystem for Linux -try: - with open('/proc/version', 'r') as f: - is_windows_linux_subsystem = 'microsoft' in f.read().lower() -except IOError: - is_windows_linux_subsystem = False diff --git a/keystoneauth1/access/__init__.py b/keystoneauth1/access/__init__.py deleted file mode 100644 index 273d9cd..0000000 --- a/keystoneauth1/access/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.access.access import * # noqa - - -__all__ = ('AccessInfo', - 'AccessInfoV2', - 'AccessInfoV3', - 'create') diff --git a/keystoneauth1/access/access.py b/keystoneauth1/access/access.py deleted file mode 100644 index 69ca1b5..0000000 --- a/keystoneauth1/access/access.py +++ /dev/null @@ -1,755 +0,0 @@ -# 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 functools - -from positional import positional - -from keystoneauth1 import _utils as utils -from keystoneauth1.access import service_catalog -from keystoneauth1.access import service_providers - - -# gap, in seconds, to determine whether the given token is about to expire -STALE_TOKEN_DURATION = 30 - - -__all__ = ('AccessInfo', - 'AccessInfoV2', - 'AccessInfoV3', - 'create') - - -@positional() -def create(resp=None, body=None, auth_token=None): - if resp and not body: - body = resp.json() - - if 'token' in body: - if resp and not auth_token: - auth_token = resp.headers.get('X-Subject-Token') - - return AccessInfoV3(body, auth_token) - elif 'access' in body: - return AccessInfoV2(body, auth_token) - - raise ValueError('Unrecognized auth response') - - -def _missingproperty(f): - - @functools.wraps(f) - def inner(self): - try: - return f(self) - except KeyError: - return None - - return property(inner) - - -class AccessInfo(object): - """Encapsulates a raw authentication token from keystone. - - Provides helper methods for extracting useful values from that token. - - """ - - _service_catalog_class = None - - def __init__(self, body, auth_token=None): - self._data = body - self._auth_token = auth_token - self._service_catalog = None - self._service_providers = None - - @property - def service_catalog(self): - if not self._service_catalog: - self._service_catalog = self._service_catalog_class.from_token( - self._data) - - return self._service_catalog - - def will_expire_soon(self, stale_duration=STALE_TOKEN_DURATION): - """Determine if expiration is about to occur. - - :returns: true if expiration is within the given duration - :rtype: boolean - - """ - norm_expires = utils.normalize_time(self.expires) - # (gyee) should we move auth_token.will_expire_soon() to timeutils - # instead of duplicating code here? - soon = utils.from_utcnow(seconds=stale_duration) - return norm_expires < soon - - def has_service_catalog(self): - """Return true if the auth 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 - - @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. - - :returns: str - """ - raise NotImplementedError() - - @property - def user_domain_name(self): - """Return the user's domain name associated with the auth request. - - :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. - - Returns true if scoped to a tenant(project) or domain, - and contains a populated service catalog. - - This is deprecated, use project_scoped instead. - - :returns: bool - """ - return self.project_scoped or self.domain_scoped - - @property - def project_scoped(self): - """Return true if the auth token was scoped to a tenant (project). - - :returns: bool - """ - return bool(self.project_id) - - @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. - - :returns: str - """ - raise NotImplementedError() - - @property - def project_domain_name(self): - """Return the project's domain name associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @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 is_admin_project(self): - """Return true if the current project scope is the admin project. - - For backwards compatibility purposes if there is nothing specified in - the token we always assume we are in the admin project, so this will - default to True. - - :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 - - @property - def service_providers(self): - """Return an object representing the list of trusted service providers. - - Used for Keystone2Keystone federating-out. - - :returns: :py:class:`keystoneauth1.service_providers.ServiceProviders` - or None - """ - raise NotImplementedError() - - @property - def bind(self): - """Information about external mechanisms the token is bound to. - - If a token is bound to an external authentication mechanism it can only - be used in conjunction with that mechanism. For example if bound to a - kerberos principal it may only be accepted if there is also kerberos - authentication performed on the request. - - :returns: A dictionary or None. The key will be the bind type the value - is a dictionary that is specific to the format of the bind - type. Returns None if there is no bind information in the - token. - """ - raise NotImplementedError() - - @property - def project_is_domain(self): - """Return if a project act as a domain. - - :returns: bool - """ - raise NotImplementedError() - - -class AccessInfoV2(AccessInfo): - """An object for encapsulating raw v2 auth token from identity service.""" - - version = 'v2.0' - _service_catalog_class = service_catalog.ServiceCatalogV2 - - def has_service_catalog(self): - return 'serviceCatalog' in self._data.get('access', {}) - - @_missingproperty - def auth_token(self): - set_token = super(AccessInfoV2, self).auth_token - return set_token or self._data['access']['token']['id'] - - @property - def _token(self): - return self._data['access']['token'] - - @_missingproperty - def expires(self): - return utils.parse_isotime(self._token.get('expires')) - - @_missingproperty - def issued(self): - return utils.parse_isotime(self._token['issued_at']) - - @property - def _user(self): - return self._data['access']['user'] - - @_missingproperty - def username(self): - return self._user.get('name') or self._user.get('username') - - @_missingproperty - def user_id(self): - return self._user['id'] - - @property - def user_domain_id(self): - return None - - @property - def user_domain_name(self): - return None - - @_missingproperty - def role_ids(self): - metadata = self._data.get('access', {}).get('metadata', {}) - return metadata.get('roles', []) - - @_missingproperty - 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: - pass - else: - return tenant_dict.get('name') - - # pre grizzly - try: - return self._user['tenantName'] - except KeyError: - pass - - # pre diablo, keystone only provided a tenantId - try: - return self._token['tenantId'] - except KeyError: - pass - - @property - def domain_scoped(self): - return False - - @property - def _trust(self): - return self._data['access']['trust'] - - @_missingproperty - def trust_id(self): - return self._trust['id'] - - @_missingproperty - def trust_scoped(self): - return bool(self._trust) - - @_missingproperty - def trustee_user_id(self): - return self._trust['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: - pass - else: - return tenant_dict.get('id') - - # pre grizzly - try: - return self._user['tenantId'] - except KeyError: - pass - - # pre diablo - try: - return self._token['tenantId'] - except KeyError: - pass - - @property - def project_is_domain(self): - return False - - @property - def project_domain_id(self): - return None - - @property - def project_domain_name(self): - 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 is_admin_project(self): - return True - - @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 - - @property - def service_providers(self): - return None - - @_missingproperty - def bind(self): - return self._token['bind'] - - -class AccessInfoV3(AccessInfo): - """An object encapsulating raw v3 auth token from identity service.""" - - version = 'v3' - _service_catalog_class = service_catalog.ServiceCatalogV3 - - def has_service_catalog(self): - return 'catalog' in self._data['token'] - - @property - def _user(self): - return self._data['token']['user'] - - @property - def is_federated(self): - return 'OS-FEDERATION' in self._user - - @property - def is_admin_project(self): - return self._data.get('token', {}).get('is_admin_project', True) - - @_missingproperty - def expires(self): - return utils.parse_isotime(self._data['token']['expires_at']) - - @_missingproperty - def issued(self): - return utils.parse_isotime(self._data['token']['issued_at']) - - @_missingproperty - 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 - - @_missingproperty - def role_ids(self): - return [r['id'] for r in self._data['token'].get('roles', [])] - - @_missingproperty - def role_names(self): - return [r['name'] for r in self._data['token'].get('roles', [])] - - @_missingproperty - def username(self): - return self._user['name'] - - @property - def _domain(self): - return self._data['token']['domain'] - - @_missingproperty - def domain_name(self): - return self._domain['name'] - - @_missingproperty - def domain_id(self): - return self._domain['id'] - - @property - def _project(self): - return self._data['token']['project'] - - @_missingproperty - def project_id(self): - return self._project['id'] - - @_missingproperty - def project_is_domain(self): - return self._data['token']['is_domain'] - - @_missingproperty - def project_domain_id(self): - return self._project['domain']['id'] - - @_missingproperty - def project_domain_name(self): - return self._project['domain']['name'] - - @_missingproperty - def project_name(self): - return self._project['name'] - - @property - def domain_scoped(self): - try: - return bool(self._domain) - except KeyError: - return False - - @property - def _trust(self): - return self._data['token']['OS-TRUST:trust'] - - @_missingproperty - def trust_id(self): - return self._trust['id'] - - @property - def trust_scoped(self): - try: - return bool(self._trust) - except KeyError: - return False - - @_missingproperty - def trustee_user_id(self): - return self._trust['trustee_user']['id'] - - @_missingproperty - def trustor_user_id(self): - return self._trust['trustor_user']['id'] - - @property - def _oauth(self): - return self._data['token']['OS-OAUTH1'] - - @_missingproperty - def oauth_access_token_id(self): - return self._oauth['access_token_id'] - - @_missingproperty - def oauth_consumer_id(self): - return self._oauth['consumer_id'] - - @_missingproperty - def audit_id(self): - try: - return self._data['token']['audit_ids'][0] - except IndexError: - return None - - @_missingproperty - def audit_chain_id(self): - try: - return self._data['token']['audit_ids'][1] - except IndexError: - return None - - @property - def service_providers(self): - if not self._service_providers: - self._service_providers = ( - service_providers.ServiceProviders.from_token(self._data)) - - return self._service_providers - - @_missingproperty - def bind(self): - return self._data['token']['bind'] diff --git a/keystoneauth1/access/service_catalog.py b/keystoneauth1/access/service_catalog.py deleted file mode 100644 index dd51ded..0000000 --- a/keystoneauth1/access/service_catalog.py +++ /dev/null @@ -1,512 +0,0 @@ -# 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 copy - -from positional import positional -import six - -from keystoneauth1 import discover -from keystoneauth1 import exceptions - - -@six.add_metaclass(abc.ABCMeta) -class ServiceCatalog(object): - """Helper methods for dealing with a Keystone Service Catalog.""" - - def __init__(self, catalog): - self._catalog = catalog - - def _get_endpoint_region(self, endpoint): - return endpoint.get('region_id') or endpoint.get('region') - - @property - def catalog(self): - """Return the raw service catalog content, mostly useful for debugging. - - Applications should avoid this and use accessor methods instead. - However, there are times when inspecting the raw catalog can be useful - for analysis and other reasons. - """ - return self._catalog - - @abc.abstractmethod - def is_interface_match(self, endpoint, interface): - """Helper function to normalize endpoint matching across v2 and v3. - - :returns: True if the provided endpoint matches the required - interface otherwise False. - """ - - @staticmethod - def normalize_interface(self, interface): - """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' interface and - v3 must be able to handle a 'publicURL' interface. - - :returns: the endpoint string in the format appropriate for this - service catalog. - """ - return interface - - def _normalize_endpoints(self, endpoints): - """Translate endpoint description dicts into v3 form. - - Takes a raw endpoint description from the catalog and changes - it to be in v3 format. It also saves a copy of the data in - raw_endpoint so that it can be returned by methods that expect the - actual original data. - - :param list endpoints: List of endpoint description dicts - - :returns: List of endpoint description dicts in v3 format - """ - new_endpoints = [] - for endpoint in endpoints: - raw_endpoint = endpoint.copy() - new_endpoint = endpoint.copy() - new_endpoint['raw_endpoint'] = raw_endpoint - new_endpoints.append(new_endpoint) - return new_endpoints - - def _denormalize_endpoints(self, endpoints): - """Return original endpoint description dicts. - - Takes a list of EndpointData objects and returns the original - dict that was returned from the catalog. - - :param list endpoints: List of `keystoneauth1.discover.EndpointData` - - :returns: List of endpoint description dicts in original catalog format - """ - return [endpoint.raw_endpoint for endpoint in endpoints] - - def normalize_catalog(self): - """Return the catalog normalized into v3 format.""" - catalog = [] - for service in copy.deepcopy(self._catalog): - if 'type' not in service: - 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. Set to None so - # that checks will naturally work. - service.setdefault('name', None) - - # NOTE(jamielennox): there is no such thing as a service_id in v2 - # similarly to service_name. - service.setdefault('id', None) - - service['endpoints'] = self._normalize_endpoints( - service.get('endpoints', [])) - - for endpoint in service['endpoints']: - endpoint['region_name'] = self._get_endpoint_region(endpoint) - endpoint.setdefault('id', None) - catalog.append(service) - return catalog - - def _get_interface_list(self, interface): - if not interface: - return [] - if not isinstance(interface, list): - interface = [interface] - return [self.normalize_interface(i) for i in interface] - - @positional() - def get_endpoints_data(self, service_type=None, interface=None, - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch and filter endpoint data 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. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: a list of matching EndpointData objects - :rtype: list(`keystoneauth1.discover.EndpointData`) - - :returns: a dict, keyed by service_type, of lists of EndpointData - """ - interfaces = self._get_interface_list(interface) - - matching_endpoints = {} - - for service in self.normalize_catalog(): - - if service_type and service_type != service['type']: - continue - - if (service_name and service['name'] and - service_name != service['name']): - continue - - if (service_id and service['id'] and - service_id != service['id']): - continue - - matching_endpoints.setdefault(service['type'], []) - - for endpoint in service.get('endpoints', []): - if interfaces and endpoint['interface'] not in interfaces: - continue - if region_name and region_name != endpoint['region_name']: - continue - if endpoint_id and endpoint_id != endpoint['id']: - continue - if not endpoint['url']: - continue - - matching_endpoints[service['type']].append( - discover.EndpointData( - catalog_url=endpoint['url'], - service_type=service['type'], - service_name=service['name'], - service_id=service['id'], - interface=endpoint['interface'], - region_name=endpoint['region_name'], - endpoint_id=endpoint['id'], - raw_endpoint=endpoint['raw_endpoint'])) - - if not interfaces: - return matching_endpoints - - ret = {} - for service_type, endpoints in matching_endpoints.items(): - if not endpoints: - ret[service_type] = [] - continue - matches_by_interface = {} - for endpoint in endpoints: - matches_by_interface.setdefault(endpoint.interface, []) - matches_by_interface[endpoint.interface].append(endpoint) - best_interface = [i for i in interfaces - if i in matches_by_interface.keys()][0] - ret[service_type] = matches_by_interface[best_interface] - - return ret - - @positional() - def get_endpoints(self, service_type=None, interface=None, - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch and filter endpoint data 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. - - Returns a dict keyed by service_type with a list of endpoint dicts - """ - endpoints_data = self.get_endpoints_data( - service_type=service_type, interface=interface, - region_name=region_name, service_name=service_name, - service_id=service_id, endpoint_id=endpoint_id) - endpoints = {} - for service_type, data in endpoints_data.items(): - endpoints[service_type] = self._denormalize_endpoints(data) - return endpoints - - @positional() - def get_endpoint_data_list(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch a flat list of matching EndpointData objects. - - 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. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: a list of matching EndpointData objects - :rtype: list(`keystoneauth1.discover.EndpointData`) - """ - endpoints = self.get_endpoints_data(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - return [endpoint for data in endpoints.values() for endpoint in data] - - @positional() - def get_urls(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch endpoint urls from the service catalog. - - Fetch the urls of endpoints from the service catalog for a particular - endpoint attribute. If no attribute is given, return the url of the - first endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: tuple of urls - """ - endpoints = self.get_endpoint_data_list(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - return tuple([endpoint.url for endpoint in endpoints]) - - @positional() - def url_for(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=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 interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - """ - return self.endpoint_data_for(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id).url - - @positional() - def endpoint_data_for(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch endpoint data from the service catalog. - - Fetch the specified endpoint data from the service catalog for - a particular endpoint attribute. If no attribute is given, return - the first endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - """ - if not self._catalog: - raise exceptions.EmptyCatalog('The service catalog is empty.') - - endpoint_data_list = self.get_endpoint_data_list( - service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - - if endpoint_data_list: - return endpoint_data_list[0] - - if service_name and region_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'named %(service_name)s in %(region_name)s region not ' - 'found' % - {'interface': interface, - 'service_type': service_type, 'service_name': service_name, - 'region_name': region_name}) - elif service_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'named %(service_name)s not found' % - {'interface': interface, - 'service_type': service_type, - 'service_name': service_name}) - elif region_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'in %(region_name)s region not found' % - {'interface': interface, - 'service_type': service_type, 'region_name': region_name}) - else: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'not found' % - {'interface': interface, - 'service_type': service_type}) - - raise exceptions.EndpointNotFound(msg) - - -class ServiceCatalogV2(ServiceCatalog): - """An object for encapsulating the v2 service catalog. - - The object is created using raw v2 auth token from Keystone. - """ - - @classmethod - def from_token(cls, token): - if 'access' not in token: - raise ValueError('Invalid token format for fetching catalog') - - return cls(token['access'].get('serviceCatalog', {})) - - @staticmethod - def normalize_interface(interface): - if interface and 'URL' not in interface: - interface += 'URL' - - return interface - - def is_interface_match(self, endpoint, interface): - return interface in endpoint - - def _normalize_endpoints(self, endpoints): - """Translate endpoint description dicts into v3 form. - - Takes a raw endpoint description from the catalog and changes - it to be in v3 format. It also saves a copy of the data in - raw_endpoint so that it can be returned by methods that expect the - actual original data. - - :param list endpoints: List of endpoint description dicts - - :returns: List of endpoint description dicts in v3 format - """ - new_endpoints = [] - for endpoint in endpoints: - raw_endpoint = endpoint.copy() - interface_urls = {} - interface_keys = [key for key in endpoint.keys() - if key.endswith('URL')] - for key in interface_keys: - interface = self.normalize_interface(key) - interface_urls[interface] = endpoint.pop(key) - for interface, url in interface_urls.items(): - new_endpoint = endpoint.copy() - new_endpoint['interface'] = interface - new_endpoint['url'] = url - # Save the actual endpoint for ease of later reconstruction - new_endpoint['raw_endpoint'] = raw_endpoint - new_endpoints.append(new_endpoint) - return new_endpoints - - def _denormalize_endpoints(self, endpoints): - """Return original endpoint description dicts. - - Takes a list of EndpointData objects and returns the original - dict that was returned from the catalog. - - :param list endpoints: List of `keystoneauth1.discover.EndpointData` - - :returns: List of endpoint description dicts in original catalog format - """ - raw_endpoints = super(ServiceCatalogV2, self)._denormalize_endpoints( - endpoints) - # The same raw endpoint content will be in the list once for each - # v2 endpoint_type entry. We only need one of them in the resulting - # list. So keep a list of the string versions. - seen = {} - endpoints = [] - for endpoint in raw_endpoints: - if str(endpoint) in seen: - continue - seen[str(endpoint)] = True - endpoints.append(endpoint) - return endpoints - - -class ServiceCatalogV3(ServiceCatalog): - """An object for encapsulating the v3 service catalog. - - The object is created using raw v3 auth token from Keystone. - """ - - @classmethod - def from_token(cls, token): - if 'token' not in token: - raise ValueError('Invalid token format for fetching catalog') - - return cls(token['token'].get('catalog', {})) - - @staticmethod - def normalize_interface(interface): - if interface: - interface = interface.rstrip('URL') - - return interface - - def is_interface_match(self, endpoint, interface): - try: - return interface == endpoint['interface'] - except KeyError: - return False diff --git a/keystoneauth1/access/service_providers.py b/keystoneauth1/access/service_providers.py deleted file mode 100644 index 83a27cc..0000000 --- a/keystoneauth1/access/service_providers.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT 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 - - -class ServiceProviders(object): - """Helper methods for dealing with Service Providers.""" - - @classmethod - def from_token(cls, token): - if 'token' not in token: - raise ValueError('Token format does not support service' - 'providers.') - - return cls(token['token'].get('service_providers', [])) - - def __init__(self, service_providers): - - def normalize(service_providers_list): - return dict((sp['id'], sp) for sp in service_providers_list - if 'id' in sp) - self._service_providers = normalize(service_providers) - - def _get_service_provider(self, sp_id): - try: - return self._service_providers[sp_id] - except KeyError: - raise exceptions.ServiceProviderNotFound(sp_id) - - def get_sp_url(self, sp_id): - return self._get_service_provider(sp_id).get('sp_url') - - def get_auth_url(self, sp_id): - return self._get_service_provider(sp_id).get('auth_url') diff --git a/keystoneauth1/adapter.py b/keystoneauth1/adapter.py deleted file mode 100644 index 1abc97f..0000000 --- a/keystoneauth1/adapter.py +++ /dev/null @@ -1,466 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 warnings - -from positional import positional - -from keystoneauth1 import session - - -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. - - version, min_version, max_version and default_microversion can all be - given either as a string or a tuple. - - :param session: The session object to wrap. - :type session: keystoneauth1.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 version: The minimum version restricted to a given Major API. - Mutually exclusive with min_version and max_version. - (optional) - :param auth: An auth plugin to use instead of the session one. - :type auth: keystoneauth1.plugin.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 - :param dict allow: Extra filters to pass when discovering API versions. - (optional) - :param dict additional_headers: Additional headers that should be attached - to every request passing through the - adapter. Headers of the same name specified - per request will take priority. - :param str client_name: The name of the client that created the adapter. - This will be used to create the user_agent. - :param str client_version: The version of the client that created the - adapter. This will be used to create the - user_agent. - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param str global_request_id: A global_request_id (in the form of - ``req-$uuid``) that will be passed on all - requests. Enables cross project request id - tracking. - :param min_version: The minimum major version of a given API, intended to - be used as the lower bound of a range with - max_version. Mutually exclusive with version. - If min_version is given with no max_version it is as - if max version is 'latest'. (optional) - :param max_version: The maximum major version of a given API, intended to - be used as the upper bound of a range with min_version. - Mutually exclusive with version. (optional) - :param default_microversion: The default microversion value to send - with API requests. While microversions are - a per-request feature, a user may know they - want to default to sending a specific value. - (optional) - """ - - client_name = None - client_version = None - - @positional() - 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, allow={}, - additional_headers=None, client_name=None, - client_version=None, allow_version_hack=None, - global_request_id=None, - min_version=None, max_version=None, - default_microversion=None): - if version and (min_version or max_version): - raise TypeError( - "version is mutually exclusive with min_version and" - " max_version") - # NOTE(jamielennox): when adding new parameters to adapter please also - # add them to the adapter call in httpclient.HTTPClient.__init__ as - # well as to load_adapter_from_argparse below if the argument is - # intended to be something a user would reasonably expect to set on - # a command line - 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 - self.allow = allow - self.additional_headers = additional_headers or {} - self.allow_version_hack = allow_version_hack - self.min_version = min_version - self.max_version = max_version - self.default_microversion = default_microversion - - self.global_request_id = global_request_id - - if client_name: - self.client_name = client_name - if client_version: - self.client_version = client_version - - 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) - if self.min_version: - kwargs.setdefault('min_version', self.min_version) - if self.max_version: - kwargs.setdefault('max_version', self.max_version) - if self.allow_version_hack is not None: - kwargs.setdefault('allow_version_hack', self.allow_version_hack) - - 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) - if self.allow: - kwargs.setdefault('allow', self.allow) - if self.default_microversion is not None: - kwargs.setdefault('microversion', self.default_microversion) - - if isinstance(self.session, (session.Session, Adapter)): - # these things are unsupported by keystoneclient's session so be - # careful with them until everyone has transitioned to ksa. - # Allowing adapter allows adapter nesting that auth_token does. - if self.client_name: - kwargs.setdefault('client_name', self.client_name) - if self.client_version: - kwargs.setdefault('client_version', self.client_version) - - else: - warnings.warn('Using keystoneclient sessions has been deprecated. ' - 'Please update your software to use keystoneauth1.') - - for k, v in self.additional_headers.items(): - kwargs.setdefault('headers', {}).setdefault(k, v) - - if self.global_request_id is not None: - kwargs.setdefault('headers', {}).setdefault( - "X-OpenStack-Request-ID", self.global_request_id) - - 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: if a new - token fetch fails. - - :returns: A valid token. - :rtype: :class:`str` - """ - 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a - plugin is not available. - - :returns: An endpoint if available or None. - :rtype: :class:`str` - """ - 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 get_endpoint_data(self, auth=None): - """Get the endpoint data for this Adapter's endpoint. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a - plugin is not available. - :raises TypeError: If arguments are invalid - - :returns: Endpoint data if available or None. - :rtype: keystoneauth1.discover.EndpointData - """ - kwargs = {} - self._set_endpoint_filter_kwargs(kwargs) - if self.endpoint_override: - kwargs['endpoint_override'] = self.endpoint_override - - return self.session.get_endpoint_data(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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current `user_id` or None if not supported by plugin. - :rtype: :class:`str` - """ - 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current `project_id` or None if not supported by plugin. - :rtype: :class:`str` - """ - 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) - - # TODO(efried): Move this to loading.adapter.Adapter - @classmethod - def register_argparse_arguments(cls, parser, service_type=None): - """Attach arguments to a given argparse Parser for Adapters. - - :param parser: The argparse parser to attach options to. - :type parser: argparse.ArgumentParser - :param str service_type: Default service_type value. (optional) - """ - adapter_group = parser.add_argument_group( - 'Service Options', - 'Options controlling the specialization of the API' - ' Connection from information found in the catalog') - - adapter_group.add_argument( - '--os-service-type', - metavar='', - default=os.environ.get('OS_SERVICE_TYPE', service_type), - help='Service type to request from the catalog') - - adapter_group.add_argument( - '--os-service-name', - metavar='', - default=os.environ.get('OS_SERVICE_NAME', None), - help='Service name to request from the catalog') - - adapter_group.add_argument( - '--os-interface', - metavar='', - default=os.environ.get('OS_INTERFACE', 'public'), - help='API Interface to use [public, internal, admin]') - - adapter_group.add_argument( - '--os-region-name', - metavar='', - default=os.environ.get('OS_REGION_NAME', None), - help='Region of the cloud to use') - - adapter_group.add_argument( - '--os-endpoint-override', - metavar='', - default=os.environ.get('OS_ENDPOINT_OVERRIDE', None), - help='Endpoint to use instead of the endpoint in the catalog') - - adapter_group.add_argument( - '--os-api-version', - metavar='', - default=os.environ.get('OS_API_VERSION', None), - help='Which version of the service API to use') - - # TODO(efried): Move this to loading.adapter.Adapter - @classmethod - def register_service_argparse_arguments(cls, parser, service_type): - """Attach arguments to a given argparse Parser for Adapters. - - :param parser: The argparse parser to attach options to. - :type parser: argparse.ArgumentParser - :param str service_type: Name of a service to generate additional - arguments for. - """ - service_env = service_type.upper().replace('-', '_') - adapter_group = parser.add_argument_group( - '{service_type} Service Options'.format( - service_type=service_type.title()), - 'Options controlling the specialization of the {service_type}' - ' API Connection from information found in the catalog'.format( - service_type=service_type.title())) - - adapter_group.add_argument( - '--os-{service_type}-service-type'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_SERVICE_TYPE'.format( - service_type=service_env), None), - help=('Service type to request from the catalog for the' - ' {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-service-name'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_SERVICE_NAME'.format( - service_type=service_env), None), - help=('Service name to request from the catalog for the' - ' {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-interface'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_INTERFACE'.format( - service_type=service_env), None), - help=('API Interface to use for the {service_type} service' - ' [public, internal, admin]'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-api-version'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_API_VERSION'.format( - service_type=service_env), None), - help=('Which version of the service API to use for' - ' the {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-endpoint-override'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_ENDPOINT_OVERRIDE'.format( - service_type=service_env), None), - help=('Endpoint to use for the {service_type} service' - ' instead of the endpoint in the catalog'.format( - service_type=service_type))) - - -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: - pass - - resp = super(LegacyJsonAdapter, self).request(*args, **kwargs) - - try: - body = resp.json() - except ValueError: - body = None - - return resp, body - - -# TODO(efried): Deprecate this in favor of -# loading.adapter.register_argparse_arguments -def register_adapter_argparse_arguments(*args, **kwargs): - return Adapter.register_argparse_arguments(*args, **kwargs) - - -# TODO(efried): Deprecate this in favor of -# loading.adapter.register_service_argparse_arguments -def register_service_adapter_argparse_arguments(*args, **kwargs): - return Adapter.register_service_argparse_arguments(*args, **kwargs) diff --git a/keystoneauth1/discover.py b/keystoneauth1/discover.py deleted file mode 100644 index 0b2cba2..0000000 --- a/keystoneauth1/discover.py +++ /dev/null @@ -1,1195 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 keystoneauth1 -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 copy -import re - -from positional import positional -import six -from six.moves import urllib - -from keystoneauth1 import _utils as utils -from keystoneauth1 import exceptions - - -_LOGGER = utils.get_logger(__name__) - -LATEST = float('inf') - - -def _str_or_latest(val): - """Convert val to a string, handling LATEST => 'latest'. - - :param val: An int or the special value LATEST. - :return: A string representation of val. If val was LATEST, the return is - 'latest'. - """ - return 'latest' if val == LATEST else str(val) - - -def _int_or_latest(val): - """Convert val to an int or the special value LATEST. - - :param val: An int()-able, or the string 'latest', or the special value - LATEST. - :return: An int, or the special value LATEST - """ - return LATEST if val == 'latest' or val == LATEST else int(val) - - -@positional() -def get_version_data(session, url, authenticated=None): - """Retrieve raw version data from a url. - - The return is a list of dicts of the form:: - - [{ - 'status': 'STABLE', - 'id': 'v2.3', - 'links': [ - { - 'href': 'http://network.example.com/v2.3', - 'rel': 'self', - }, - { - 'href': 'http://network.example.com/', - 'rel': 'collection', - }, - ], - 'min_version': '2.0', - 'max_version': '2.7', - }, - ..., - ] - - Note: - The maximum microversion may be specified by `max_version` or `version`, - the former superseding the latter. - All `*version` keys are optional. - Other keys and 'links' entries are permitted, but ignored. - - :param session: A Session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param string url: Endpoint or discovery URL from which to retrieve data. - :param bool authenticated: Include a token in the discovery call. - (optional) Defaults to None. - :return: A list of dicts containing version information. - :rtype: list(dict) - """ - headers = {'Accept': 'application/json'} - - resp = session.get(url, headers=headers, authenticated=authenticated) - - try: - body_resp = resp.json() - except ValueError: - 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): - pass - - # Most servers don't have a 'values' element so accept a simple - # versions dict if available. - try: - return body_resp['versions'] - except KeyError: - 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: - pass - - err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text - raise exceptions.DiscoveryFailure('Invalid Response - Bad version data ' - 'returned: %s' % err_text) - - -def normalize_version_number(version): - """Turn a version representation into a tuple. - - Examples: - - The following all produce a return value of (1, 0):: - - 1, '1', 'v1', [1], (1,), ['1'], 1.0, '1.0', 'v1.0', (1, 0) - - The following all produce a return value of (1, 20, 3):: - - 'v1.20.3', '1.20.3', (1, 20, 3), ['1', '20', '3'] - - The following all produce a return value of (LATEST, LATEST):: - - 'latest', 'vlatest', ('latest', 'latest'), (LATEST, LATEST) - - The following all produce a return value of (2, LATEST):: - - '2.latest', 'v2.latest', (2, LATEST), ('2', 'latest') - - :param version: A version specifier in any of the following forms: - String, possibly prefixed with 'v', containing one or more numbers - *or* the string 'latest', separated by periods. Examples: 'v1', - 'v1.2', '1.2.3', '123', 'latest', '1.latest', 'v1.latest'. - Integer. This will be assumed to be the major version, with a minor - version of 0. - Float. The integer part is assumed to be the major version; the - decimal part the minor version. - Non-string iterable comprising integers, integer strings, the string - 'latest', or the special value LATEST. - Examples: (1,), [1, 2], ('12', '34', '56'), (LATEST,), (2, 'latest') - :return: A tuple of len >= 2 comprising integers and/or LATEST. - :raises TypeError: If the input version cannot be interpreted. - """ - # Copy the input var so the error presents the original value - ver = version - - # If it's a non-string iterable, turn it into a string for subsequent - # processing. This ensures at least 1 decimal point if e.g. [1] is given. - if not isinstance(ver, six.string_types): - try: - ver = '.'.join(map(_str_or_latest, ver)) - except TypeError: - # Not an iterable - pass - - # If it's a numeric or an integer as a string then normalize it to a - # float string. This ensures 1 decimal point. - # If it's a float as a string, don't do that, the split/map below will do - # what we want. (Otherwise, we wind up with 3.20 -> (3, 2)) - if isinstance(ver, six.string_types): - # trim the v from a 'v2.0' or similar - ver = ver.lstrip('v') - try: - # If version is a pure int, like '1' or '200' this will produce - # a stringified version with a .0 added. If it's any other number, - # such as '1.1' - int(version) raises an Exception - ver = str(float(int(ver))) - except ValueError: - pass - - # If it's an int or float, turn it into a float string - elif isinstance(ver, (int, float)): - ver = _str_or_latest(float(ver)) - - # At this point, we should either have a string that contains numbers with - # at least one decimal point, or something decidedly else. - - # if it's a string from above break it on . - try: - ver = ver.split('.') - except AttributeError: - # Not a string - pass - - # Handle special case variants of just 'latest' - if ver == 'latest' or tuple(ver) == ('latest',): - return LATEST, LATEST - - # It's either an interable, or something else that makes us sad. - try: - return tuple(map(_int_or_latest, ver)) - except (TypeError, ValueError): - pass - - raise TypeError('Invalid version specified: %s' % version) - - -def _normalize_version_args(version, min_version, max_version): - if version and (min_version or max_version): - raise ValueError( - "version is mutually exclusive with min_version and max_version") - - if version: - # Explode this into min_version and max_version - min_version = normalize_version_number(version) - max_version = (min_version[0], LATEST) - return min_version, max_version - - if min_version == 'latest': - if max_version not in (None, 'latest'): - raise ValueError( - "min_version is 'latest' and max_version is {max_version}" - " but is only allowed to be 'latest' or None".format( - max_version=max_version)) - max_version = 'latest' - - # Normalize e.g. empty string to None - min_version = min_version or None - max_version = max_version or None - - if min_version: - min_version = normalize_version_number(min_version) - # If min_version was specified but max_version was not, max is latest. - max_version = normalize_version_number(max_version or 'latest') - - # NOTE(efried): We should be doing this instead: - # max_version = normalize_version_number(max_version or 'latest') - # However, see first NOTE(jamielennox) in EndpointData._set_version_info. - if max_version: - max_version = normalize_version_number(max_version) - - if None not in (min_version, max_version) and max_version < min_version: - raise ValueError("min_version cannot be greater than max_version") - - return min_version, max_version - - -def version_to_string(version): - """Turn a version tuple into a string. - - :param tuple version: A version represented as a tuple of ints. As a - special case, a tuple member may be LATEST, which - translates to 'latest'. - :return: A version represented as a period-delimited string. - """ - # Special case - if all(ver == LATEST for ver in version): - return 'latest' - - return ".".join(map(_str_or_latest, version)) - - -def _version_between(min_version, max_version, candidate): - """Determine whether a candidate version is within a specified range. - - :param min_version: Normalized lower bound. May be None. May be - (LATEST, LATEST). - :param max_version: Normalized upper bound. May be None. May be - (LATEST, LATEST). - :param candidate: Normalized candidate version to test. May not be None. - :return: True if candidate is between min_version and max_version; False - otherwise. - :raises ValueError: If candidate is None or the input is not properly - normalized. - """ - def is_normalized(ver): - return normalize_version_number(ver) == ver - - # A version can't be between a range that doesn't exist - if not min_version and not max_version: - return False - - if candidate is None: - raise ValueError("candidate cannot be None.") - - if min_version is not None and not is_normalized(min_version): - raise ValueError("min_version is not normalized.") - if max_version is not None and not is_normalized(max_version): - raise ValueError("max_version is not normalized.") - if not is_normalized(candidate): - raise ValueError("candidate is not normalized.") - # This is only possible if args weren't run through _normalize_version_args - if max_version is None and min_version is not None: - raise ValueError("Can't use None as an upper bound.") - - # If the candidate is less than the min_version, it's - # not a match. None works here. - if min_version is not None and candidate < min_version: - return False - - if max_version is not None and candidate > max_version: - return False - - return True - - -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 - - -def _latest_soft_match(required, candidate): - if not required: - return False - - if LATEST not in required: - return False - - if all(part == LATEST for part in required): - return True - - if required[0] == candidate[0] and required[1] == LATEST: - return True - - # TODO(efried): Do we need to handle >2-part version numbers here? - - return False - - -def _combine_relative_url(discovery_url, version_url): - # NOTE(jamielennox): urllib.parse.urljoin allows the url to be relative - # or even protocol-less. The additional trailing '/' makes urljoin respect - # the current path as canonical even if the url doesn't include it. for - # example a "v2" path from http://host/admin should resolve as - # http://host/admin/v2 where it would otherwise be host/v2. This has no - # effect on absolute urls. - url = urllib.parse.urljoin(discovery_url.rstrip('/') + '/', version_url) - - # Parse and recombine the result to squish double //'s from the above - return urllib.parse.urlparse(url).geturl() - - -class Discover(object): - - CURRENT_STATUSES = ('stable', 'current', 'supported') - DEPRECATED_STATUSES = ('deprecated',) - EXPERIMENTAL_STATUSES = ('experimental',) - - @positional() - def __init__(self, session, url, authenticated=None): - self._url = url - 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 - - @positional() - def version_data(self, reverse=False, **kwargs): - """Get normalized version data. - - Return version data in a structured way. - - :param bool reverse: Reverse the list. reverse=true will mean the - returned list is sorted from newest to oldest - version. - :returns: A list of version data dictionaries sorted by version number. - Each data element in the returned list is a dictionary - consisting of: - - :version tuple: The normalized version of the endpoint. - :url str: The url for the endpoint. - :collection: The URL for the discovery document. May be None. - :min_microversion: The minimum microversion supported by the - endpoint. May be None. - :max_microversion: The maximum microversion supported by the - endpoint. May be None. - :raw_status str: The status as provided by the server - :rtype: list(dict) - """ - 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) - - # collect microversion information - # NOTE(efried): Some existing discovery documents (e.g. from nova - # v2.0 in the pike release) include *version keys with "" (empty - # string) values, expecting them to be treated the same as if the - # keys were absent. - min_microversion = v.get('min_version') or None - if min_microversion: - min_microversion = normalize_version_number(min_microversion) - max_microversion = v.get('max_version') - if not max_microversion: - max_microversion = v.get('version') or None - if max_microversion: - max_microversion = normalize_version_number(max_microversion) - next_min_version = v.get('next_min_version') or None - if next_min_version: - next_min_version = normalize_version_number(next_min_version) - not_before = v.get('not_before') or None - - self_url = None - collection_url = None - for link in links: - try: - rel = link['rel'] - url = _combine_relative_url(self._url, link['href']) - except (KeyError, TypeError): - _LOGGER.info('Skipping invalid version link. ' - 'Missing link URL or relationship.') - continue - - if rel.lower() == 'self': - self_url = url - elif rel.lower() == 'collection': - collection_url = url - if not self_url: - _LOGGER.info('Skipping invalid version data. ' - 'Missing link to endpoint.') - continue - - versions.append({'version': version_number, - 'url': self_url, - 'collection': collection_url, - 'min_microversion': min_microversion, - 'max_microversion': max_microversion, - 'next_min_version': next_min_version, - 'not_before': not_before, - 'raw_status': v['status']}) - - versions.sort(key=lambda v: v['version'], reverse=reverse) - return versions - - def data_for(self, version, **kwargs): - """Return endpoint data for a version. - - NOTE: This method raises a TypeError if version is None. It is - kept for backwards compatability. New code should use - versioned_data_for instead. - - :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) - - for data in self.version_data(reverse=True, **kwargs): - # Since the data is reversed, the latest version is first. If - # latest was requested, return it. - if _latest_soft_match(version, data['version']): - return data - if version_match(version, data['version']): - return data - - return None - - def url_for(self, version, **kwargs): - """Get the endpoint url for a version. - - NOTE: This method raises a TypeError if version is None. It is - kept for backwards compatability. New code should use - versioned_url_for instead. - - :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 - - def versioned_data_for(self, url=None, - min_version=None, max_version=None, - **kwargs): - """Return endpoint data for the service at a url. - - min_version and max_version can be given either as strings or tuples. - - :param string url: If url is given, the data will be returned for the - endpoint data that has a self link matching the url. - :param min_version: The minimum endpoint version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. If min_version is 'latest', max_version may only be - 'latest' or None. - :param max_version: The maximum endpoint version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. If min_version is 'latest', max_version may only be - 'latest' or None. - - :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 - """ - min_version, max_version = _normalize_version_args( - None, min_version, max_version) - no_version = not max_version and not min_version - - version_data = self.version_data(reverse=True, **kwargs) - - # If we don't have to check a min_version, we can short - # circuit anything else - if (max_version == (LATEST, LATEST) and - (not min_version or min_version == (LATEST, LATEST))): - # because we reverse we can just take the first entry - return version_data[0] - - if url: - url = url.rstrip('/') + '/' - - if no_version and not url: - # because we reverse we can just take the first entry - return version_data[0] - - # Version data is in order from highest to lowest, so we return - # the first matching entry - for data in version_data: - if url and data['url'] and data['url'].rstrip('/') + '/' == url: - return data - if _latest_soft_match(min_version, data['version']): - return data - if _version_between(min_version, max_version, data['version']): - return data - - # If there is no version requested and we could not find a matching - # url in the discovery doc, that means we've got an unversioned - # endpoint in the catalog and the user is requesting version data - # so that they know what version they got. We can return the first - # entry from version_data, because the user hasn't requested anything - # different. - if no_version and url: - return version_data[0] - - # We couldn't find a match. - return None - - def versioned_url_for(self, min_version=None, max_version=None, **kwargs): - """Get the endpoint url for a version. - - min_version and max_version can be given either as strings or tuples. - - :param min_version: The minimum version that is acceptable. If - min_version is given with no max_version it is as if max version - is 'latest'. - :param max_version: The maximum version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. - - :returns: The url for the specified version or None if no match. - :rtype: str - """ - data = self.versioned_data_for(min_version=min_version, - max_version=max_version, **kwargs) - return data['url'] if data else None - - -class EndpointData(object): - """Normalized information about a discovered endpoint. - - Contains url, version, microversion, interface and region information. - This is essentially the data contained in the catalog and the version - discovery documents about an endpoint that is used to select the endpoint - desired by the user. It is returned so that a user can know which qualities - a discovered endpoint had, in case their request allowed for a range of - possibilities. - """ - - @positional() - def __init__(self, - catalog_url=None, - service_url=None, - service_type=None, - service_name=None, - service_id=None, - region_name=None, - interface=None, - endpoint_id=None, - raw_endpoint=None, - api_version=None, - major_version=None, - min_microversion=None, - max_microversion=None, - next_min_version=None, - not_before=None): - self.catalog_url = catalog_url - self.service_url = service_url - self.service_type = service_type - self.service_name = service_name - self.service_id = service_id - self.interface = interface - self.region_name = region_name - self.endpoint_id = endpoint_id - self.raw_endpoint = raw_endpoint - self.api_version = api_version - self.major_version = major_version - self.min_microversion = min_microversion - self.max_microversion = max_microversion - self.next_min_version = next_min_version - self.not_before = not_before - self._saved_project_id = None - self._catalog_matches_version = False - self._catalog_matches_exactly = False - self._disc = None - - def __copy__(self): - """Return a new EndpointData based on this one.""" - new_data = EndpointData( - catalog_url=self.catalog_url, - service_url=self.service_url, - service_type=self.service_type, - service_name=self.service_name, - service_id=self.service_id, - region_name=self.region_name, - interface=self.interface, - endpoint_id=self.endpoint_id, - raw_endpoint=self.raw_endpoint, - api_version=self.api_version, - major_version=self.major_version, - min_microversion=self.min_microversion, - max_microversion=self.max_microversion, - next_min_version=self.next_min_version, - not_before=self.not_before) - # Save cached discovery object - but we don't want to - # actually provide a constructor argument - new_data._disc = self._disc - new_data._saved_project_id = self._saved_project_id - return new_data - - @property - def url(self): - return self.service_url or self.catalog_url - - @positional(3) - def get_versioned_data(self, session, allow=None, cache=None, - allow_version_hack=True, project_id=None, - discover_versions=True, - min_version=None, max_version=None): - """Run version discovery for the service described. - - Performs Version Discovery and returns a new EndpointData object with - information found. - - min_version and max_version can be given either as strings or tuples. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param dict cache: A dict to be used for caching results in - addition to caching them on the Session. - (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param string project_id: ID of the currently scoped project. Used for - removing project_id components of URLs from - the catalog. (optional) - :param bool discover_versions: Whether to get version metadata from - the version discovery document even - if it's not neccessary to fulfill the - major version request. (optional, - defaults to True) - :param min_version: The minimum version that is acceptable. If - min_version is given with no max_version it is as - if max version is 'latest'. - :param max_version: The maximum version that is acceptable. If - min_version is given with no max_version it is as - if max version is 'latest'. - - :returns: A new EndpointData with the requested versioned data. - :rtype: :py:class:`keystoneauth1.discover.EndpointData` - :raises keystoneauth1.exceptions.discovery.DiscoveryFailure: If the - appropriate versioned data - could not be discovered. - """ - min_version, max_version = _normalize_version_args( - None, min_version, max_version) - - if not allow: - allow = {} - - # This method should always return a new EndpointData - new_data = copy.copy(self) - - new_data._set_version_info( - session=session, allow=allow, cache=cache, - allow_version_hack=allow_version_hack, project_id=project_id, - discover_versions=discover_versions, min_version=min_version, - max_version=max_version) - return new_data - - def _set_version_info(self, session, allow=None, cache=None, - allow_version_hack=True, project_id=None, - discover_versions=False, - min_version=None, max_version=None): - match_url = None - - no_version = not max_version and not min_version - if no_version and not discover_versions: - # 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 - elif no_version and discover_versions: - # We want to run discovery, but we don't want to find different - # endpoints than what's in the catalog - allow_version_hack = False - match_url = self.url - - if project_id: - self.project_id = project_id - discovered_data = None - # Maybe we've run discovery in the past and have a document that can - # satisfy the request without further work - if self._disc: - discovered_data = self._disc.versioned_data_for( - min_version=min_version, max_version=max_version, - url=match_url, **allow) - if not discovered_data: - self._run_discovery( - session=session, cache=cache, - min_version=min_version, max_version=max_version, - project_id=project_id, allow_version_hack=allow_version_hack, - discover_versions=discover_versions) - if not self._disc: - return - discovered_data = self._disc.versioned_data_for( - min_version=min_version, max_version=max_version, - url=match_url, **allow) - - if not discovered_data: - if min_version and not max_version: - raise exceptions.DiscoveryFailure( - "Minimum version {min_version} was not found".format( - min_version=version_to_string(min_version))) - elif max_version and not min_version: - raise exceptions.DiscoveryFailure( - "Maximum version {max_version} was not found".format( - max_version=version_to_string(max_version))) - elif min_version and max_version: - raise exceptions.DiscoveryFailure( - "No version found between {min_version}" - " and {max_version}".format( - min_version=version_to_string(min_version), - max_version=version_to_string(max_version))) - - self.min_microversion = discovered_data['min_microversion'] - self.max_microversion = discovered_data['max_microversion'] - self.next_min_version = discovered_data['next_min_version'] - self.not_before = discovered_data['not_before'] - - # TODO(mordred): these next two things should be done by Discover - # in versioned_data_for. - discovered_url = discovered_data['url'] - - # NOTE(jamielennox): urljoin allows the url to be relative or even - # protocol-less. The additional trailing '/' make urljoin respect - # the current path as canonical even if the url doesn't include it. - # for example a "v2" path from http://host/admin should resolve as - # http://host/admin/v2 where it would otherwise be host/v2. - # This has no effect on absolute urls returned from url_for. - url = urllib.parse.urljoin(self._disc._url.rstrip('/') + '/', - discovered_url) - - # If we had to pop a project_id from the catalog_url, put it back on - if self._saved_project_id: - url = urllib.parse.urljoin(url.rstrip('/') + '/', - self._saved_project_id) - self.service_url = url - - @positional(1) - def _run_discovery(self, session, cache, min_version, max_version, - project_id, allow_version_hack, discover_versions): - tried = set() - - for vers_url in self._get_discovery_url_choices( - project_id=project_id, - allow_version_hack=allow_version_hack, - min_version=min_version, - max_version=max_version): - - if self._catalog_matches_exactly and not discover_versions: - # The version we started with is correct, and we don't want - # new data - return - - if vers_url in tried: - continue - tried.add(vers_url) - - try: - self._disc = get_discovery( - session, vers_url, - cache=cache, - authenticated=False) - break - except (exceptions.DiscoveryFailure, - exceptions.HttpError, - exceptions.ConnectionError): - continue - if not self._disc: - # We couldn't find a version discovery document anywhere. - if self._catalog_matches_version: - # But - the version in the catalog is fine. - self.service_url = self.catalog_url - return - - # NOTE(jamielennox): The logic here is required for backwards - # compatibility. By itself it is not ideal. - if allow_version_hack: - # NOTE(jamielennox): If we can't contact the server we - # fall back to just returning the URL from the catalog. This - # is backwards compatible behaviour and used when there is no - # other choice. Realistically if you have provided a version - # you should be able to rely on that version being returned or - # the request failing. - _LOGGER.warning( - 'Failed to contact the endpoint at %s for ' - 'discovery. Fallback to using that endpoint as ' - 'the base url.', self.url) - return - - else: - # NOTE(jamielennox): If you've said no to allow_version_hack - # and we can't determine the actual URL this is a failure - # because we are specifying that the deployment must be up to - # date enough to properly specify a version and keystoneauth - # can't deliver. - raise exceptions.DiscoveryFailure( - "Version requested but version discovery document was not" - " found and allow_version_hack was False") - - def _get_discovery_url_choices( - self, project_id=None, allow_version_hack=True, - min_version=None, max_version=None): - """Find potential locations for version discovery URLs. - - min_version and max_version are already normalized, so will either be - None or a tuple. - """ - url = urllib.parse.urlparse(self.url) - url_parts = url.path.split('/') - - # First, check to see if the catalog url ends with a project id - # We need to remove it and save it for later if it does - if project_id and url_parts[-1].endswith(project_id): - self._saved_project_id = url_parts.pop() - elif not project_id: - # Peek to see if -2 is a version. If so, -1 is a project_id, - # even if we don't know that at this point in the call stack - try: - normalize_version_number(url_parts[-2]) - self._saved_project_id = url_parts.pop() - except (IndexError, TypeError): - pass - - catalog_discovery = versioned_discovery = None - - # Next, check to see if the url indicates a version and if that - # version either matches our version request or is withing the - # range requested. If so, we can start by trying the given url - # as it has a high potential for success. - try: - url_version = normalize_version_number(url_parts[-1]) - versioned_discovery = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl() - except TypeError: - pass - else: - is_between = _version_between(min_version, max_version, - url_version) - exact_match = (is_between and max_version and - max_version[0] == url_version[0]) - high_match = (is_between and max_version and - max_version[1] != LATEST and - version_match(max_version, url_version)) - if exact_match or is_between: - self._catalog_matches_version = True - self._catalog_matches_exactly = exact_match - # The endpoint from the catalog matches the version request - # We construct a URL minus any project_id, but we don't - # return it just yet. It's a good option, but unless we - # have an exact match or match the max requested, we want - # to try for an unversioned endpoint first. - catalog_discovery = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl().rstrip('/') + '/' - - # If we found a viable catalog endpoint and it's - # an exact match or matches the max, go ahead and give - # it a go. - if catalog_discovery and (high_match or exact_match): - yield catalog_discovery - catalog_discovery = None - - url_parts.pop() - - if allow_version_hack: - # If there were projects or versions in the url they are now gone. - # That means we're left with what should be the unversioned url. - hacked_url = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl() - # Since this is potentially us constructing a base URL from the - # versioned URL - we need to make sure it has a trailing /. But - # we only want to do that if we have built a new URL - not if - # we're using the one from the catalog - if hacked_url != self.catalog_url: - hacked_url = hacked_url.strip('/') + '/' - yield hacked_url - - # If we have a catalog discovery url, it either means we didn't - # return it earlier because it wasn't an exact enough match, or - # that we did and it failed. We don't double-request things when - # consuming this, so it's safe to return it here in case we didn't - # already return it. - if catalog_discovery: - yield catalog_discovery - - # NOTE(mordred): For backwards compatibility people might have - # added version hacks using the version hack system. The logic - # above should handle most cases, so by the time we get here it's - # most likely to be a no-op - yield self._get_catalog_discover_hack() - elif versioned_discovery and self._saved_project_id: - # We popped a project_id but are either avoiding version hacks - # or we didn't request a version. That means we still want to fetch - # the document from the "catalog url" - but the catalog url is has - # a project_id suffix so is likely not going to work for us. Try - # fetching from the project-less versioned endpoint. - yield versioned_discovery - - # As a final fallthrough case, return the actual unmodified url from - # the catalog. - yield self.catalog_url - - def _get_catalog_discover_hack(self): - """Apply the catalog hacks and figure out an unversioned endpoint. - - This function is internal to keystoneauth1. - - :returns: A url that has been transformed by the regex hacks that - match the service_type. - """ - return _VERSION_HACKS.get_discover_hack(self.service_type, self.url) - - -@positional() -def get_discovery(session, url, cache=None, authenticated=False): - """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. - - NOTE: This function is expected to be used by keystoneauth and should not - be needed by users part of normal usage. A normal user should use - get_endpoint or get_endpoint_data on `keystoneauth.session.Session` or - endpoint_filters on `keystoneauth.session.Session` or - `keystoneauth.session.Session`. However, should the user need to perform - direct discovery for some reason, this function should be used so that - the discovery caching is used. - - :param session: A session object to discover with. - :type session: keystoneauth1.session.Session - :param str url: The url to lookup. - :param dict cache: - A dict to be used for caching results, in addition to caching them - on the Session. (optional) Defaults to None. - :param bool authenticated: - Include a token in the discovery call. (optional) Defaults to None, - which will use a token if an auth plugin is installed. - - :raises keystoneauth1.exceptions.discovery.DiscoveryFailure: - if for some reason the lookup fails. - :raises keystoneauth1.exceptions.http.HttpError: - An error from an invalid HTTP response. - - :returns: A discovery object with the results of looking up that URL. - :rtype: :py:class:`keystoneauth1.discover.Discovery` - """ - # There are between one and three different caches. The user may have - # passed one in. There is definitely one on the session, and there is - # one on the auth plugin if the Session has an auth plugin. - caches = [] - - # If a cache was passed in, check it first. - if cache is not None: - caches.append(cache) - - # If the session has a cache, check it second, since it could have been - # provided by the user at Session creation time. - if hasattr(session, '_discovery_cache'): - caches.append(session._discovery_cache) - - # Finally check the auth cache associated with the Session. - if session.auth and hasattr(session.auth, '_discovery_cache'): - caches.append(session.auth._discovery_cache) - - for cache in caches: - disc = cache.get(url) - - if disc: - break - else: - disc = Discover(session, url, authenticated=authenticated) - - # Whether we get one from fetching or from cache, set it in the - # caches. This assures that if we combine sessions and auth plugins - # that we don't make unnecesary calls. - if disc: - for cache in caches: - cache[url] = disc - - return disc - - -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 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. - """ - _VERSION_HACKS.add_discover_hack(service_type, old, new) diff --git a/keystoneauth1/exceptions/__init__.py b/keystoneauth1/exceptions/__init__.py deleted file mode 100644 index e444926..0000000 --- a/keystoneauth1/exceptions/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.auth import * # noqa -from keystoneauth1.exceptions.auth_plugins import * # noqa -from keystoneauth1.exceptions.base import * # noqa -from keystoneauth1.exceptions.catalog import * # noqa -from keystoneauth1.exceptions.connection import * # noqa -from keystoneauth1.exceptions.discovery import * # noqa -from keystoneauth1.exceptions.http import * # noqa -from keystoneauth1.exceptions.oidc import * # noqa -from keystoneauth1.exceptions.response import * # noqa -from keystoneauth1.exceptions.service_providers import * # noqa diff --git a/keystoneauth1/exceptions/auth.py b/keystoneauth1/exceptions/auth.py deleted file mode 100644 index 99bea8d..0000000 --- a/keystoneauth1/exceptions/auth.py +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - - -class AuthorizationFailure(base.ClientException): - message = "Cannot authorize API client." diff --git a/keystoneauth1/exceptions/auth_plugins.py b/keystoneauth1/exceptions/auth_plugins.py deleted file mode 100644 index be3c8c1..0000000 --- a/keystoneauth1/exceptions/auth_plugins.py +++ /dev/null @@ -1,93 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - - -__all__ = ('AuthPluginException', - 'MissingAuthPlugin', - 'NoMatchingPlugin', - 'UnsupportedParameters', - 'OptionError', - 'MissingRequiredOptions') - - -class AuthPluginException(base.ClientException): - message = "Unknown error with authentication plugins." - - -class MissingAuthPlugin(AuthPluginException): - message = "An authenticated request is required but no plugin available." - - -class NoMatchingPlugin(AuthPluginException): - """No auth plugins could be created from the parameters provided. - - :param str name: The name of the plugin that was attempted to load. - - .. py:attribute:: name - - The name of the plugin that was attempted to load. - """ - - def __init__(self, name): - self.name = name - msg = 'The plugin %s could not be found' % name - super(NoMatchingPlugin, self).__init__(msg) - - -class UnsupportedParameters(AuthPluginException): - """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 OptionError(AuthPluginException): - """A requirement of this plugin loader was not met. - - This error can be raised by a specific plugin loader during the - load_from_options stage to indicate a parameter problem that can not be - handled by the generic options loader. - - The intention here is that a plugin can do checks like if a name parameter - is provided then a domain parameter must also be provided, but that Opt - checking doesn't handle. - """ - - -class MissingRequiredOptions(OptionError): - """One or more required options were not provided. - - :param list(keystoneauth1.loading.Opt) options: Missing options. - - .. py:attribute:: options - - List of the missing options. - """ - - def __init__(self, options): - self.options = options - - names = ", ".join(o.dest for o in options) - m = 'Auth plugin requires parameters which were not given: %s' - super(MissingRequiredOptions, self).__init__(m % names) diff --git a/keystoneauth1/exceptions/base.py b/keystoneauth1/exceptions/base.py deleted file mode 100644 index afb889b..0000000 --- a/keystoneauth1/exceptions/base.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -__all__ = ('ClientException',) - - -class ClientException(Exception): - """The base exception for everything to do with clients.""" - - message = "ClientException" - - def __init__(self, message=None): - self.message = message or self.message - super(ClientException, self).__init__(self.message) diff --git a/keystoneauth1/exceptions/catalog.py b/keystoneauth1/exceptions/catalog.py deleted file mode 100644 index 25b6005..0000000 --- a/keystoneauth1/exceptions/catalog.py +++ /dev/null @@ -1,30 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - -__all__ = ('CatalogException', - 'EmptyCatalog', - 'EndpointNotFound') - - -class CatalogException(base.ClientException): - message = "Unknown error with service catalog." - - -class EndpointNotFound(CatalogException): - message = "Could not find requested endpoint in Service Catalog." - - -class EmptyCatalog(EndpointNotFound): - message = "The service catalog is empty." diff --git a/keystoneauth1/exceptions/connection.py b/keystoneauth1/exceptions/connection.py deleted file mode 100644 index e5de679..0000000 --- a/keystoneauth1/exceptions/connection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - - -__all__ = ('ConnectionError', - 'ConnectTimeout', - 'ConnectFailure', - 'SSLError', - 'RetriableConnectionFailure', - 'UnknownConnectionError') - - -class RetriableConnectionFailure(Exception): - """A mixin class that implies you can retry the most recent request.""" - - pass - - -class ConnectionError(base.ClientException): - message = "Cannot connect to API service." - - -class ConnectTimeout(ConnectionError, RetriableConnectionFailure): - message = "Timed out connecting to service." - - -class ConnectFailure(ConnectionError, RetriableConnectionFailure): - message = "Connection failure that may be retried." - - -class SSLError(ConnectionError): - message = "An SSL error occurred." - - -class UnknownConnectionError(ConnectionError): - """An error was encountered but we don't know what it is.""" - - def __init__(self, msg, original): - super(UnknownConnectionError, self).__init__(msg) - self.original = original diff --git a/keystoneauth1/exceptions/discovery.py b/keystoneauth1/exceptions/discovery.py deleted file mode 100644 index 209009b..0000000 --- a/keystoneauth1/exceptions/discovery.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - - -__all__ = ('DiscoveryFailure', - 'VersionNotAvailable') - - -class DiscoveryFailure(base.ClientException): - message = "Discovery of client versions failed." - - -class VersionNotAvailable(DiscoveryFailure): - message = "Discovery failed. Requested version is not available." diff --git a/keystoneauth1/exceptions/http.py b/keystoneauth1/exceptions/http.py deleted file mode 100644 index f76afc2..0000000 --- a/keystoneauth1/exceptions/http.py +++ /dev/null @@ -1,428 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# 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. - -"""HTTP Exceptions used by keystoneauth1.""" - -import inspect -import sys - -from keystoneauth1.exceptions import base - - -__all__ = ('HttpError', - - 'HTTPClientError', - 'BadRequest', - 'Unauthorized', - 'PaymentRequired', - 'Forbidden', - 'NotFound', - 'MethodNotAllowed', - 'NotAcceptable', - 'ProxyAuthenticationRequired', - 'RequestTimeout', - 'Conflict', - 'Gone', - 'LengthRequired', - 'PreconditionFailed', - 'RequestEntityTooLarge', - 'RequestUriTooLong', - 'UnsupportedMediaType', - 'RequestedRangeNotSatisfiable', - 'ExpectationFailed', - 'UnprocessableEntity', - - 'HttpServerError', - 'InternalServerError', - 'HttpNotImplemented', - 'BadGateway', - 'ServiceUnavailable', - 'GatewayTimeout', - 'HttpVersionNotSupported', - - 'from_response') - - -class HttpError(base.ClientException): - """The base exception class for all HTTP exceptions.""" - - http_status = 0 - message = "HTTP Error" - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None, - retry_after=0): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - self.retry_after = retry_after - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - - message = "HTTP Client Error" - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - - message = "HTTP Server Error" - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - - http_status = 400 - message = "Bad Request" - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - - http_status = 401 - message = "Unauthorized" - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - - http_status = 402 - message = "Payment Required" - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - - http_status = 403 - message = "Forbidden" - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - - http_status = 404 - message = "Not Found" - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - - http_status = 405 - message = "Method Not Allowed" - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - - http_status = 406 - message = "Not Acceptable" - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - - http_status = 407 - message = "Proxy Authentication Required" - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - - http_status = 408 - message = "Request Timeout" - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - - http_status = 409 - message = "Conflict" - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - - http_status = 410 - message = "Gone" - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - - http_status = 411 - message = "Length Required" - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - - http_status = 412 - message = "Precondition Failed" - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - - http_status = 413 - message = "Request Entity Too Large" - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - - http_status = 414 - message = "Request-URI Too Long" - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - - http_status = 415 - message = "Unsupported Media Type" - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - - http_status = 416 - message = "Requested Range Not Satisfiable" - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - - http_status = 417 - message = "Expectation Failed" - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - - http_status = 422 - message = "Unprocessable Entity" - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - - http_status = 500 - message = "Internal Server Error" - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - - http_status = 501 - message = "Not Implemented" - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - - http_status = 502 - message = "Bad Gateway" - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - - http_status = 503 - message = "Service Unavailable" - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - - http_status = 504 - message = "Gateway Timeout" - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - - http_status = 505 - message = "HTTP Version Not Supported" - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in vars(sys.modules[__name__]).items() - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Return an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - req_id = response.headers.get("x-openstack-request-id") - - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict) and isinstance(body.get("error"), dict): - error = body["error"] - kwargs["message"] = error.get("message") - kwargs["details"] = error.get("details") - elif content_type.startswith("text/"): - kwargs["details"] = response.text - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff --git a/keystoneauth1/exceptions/oidc.py b/keystoneauth1/exceptions/oidc.py deleted file mode 100644 index 2835017..0000000 --- a/keystoneauth1/exceptions/oidc.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 auth_plugins - -__all__ = ( - 'InvalidDiscoveryEndpoint', 'InvalidOidcDiscoveryDocument', - 'OidcAccessTokenEndpointNotFound', 'OidcAuthorizationEndpointNotFound', - 'OidcGrantTypeMissmatch', 'OidcPluginNotSupported', -) - - -class InvalidDiscoveryEndpoint(auth_plugins.AuthPluginException): - message = "OpenID Connect Discovery Document endpoint not set.""" - - -class InvalidOidcDiscoveryDocument(auth_plugins.AuthPluginException): - message = "OpenID Connect Discovery Document is not valid JSON.""" - - -class OidcAccessTokenEndpointNotFound(auth_plugins.AuthPluginException): - message = "OpenID Connect access token endpoint not provided." - - -class OidcAuthorizationEndpointNotFound(auth_plugins.AuthPluginException): - message = "OpenID Connect authorization endpoint not provided." - - -class OidcGrantTypeMissmatch(auth_plugins.AuthPluginException): - message = "Missmatch between OpenID Connect plugin and grant_type argument" - - -class OidcPluginNotSupported(auth_plugins.AuthPluginException): - message = "OpenID Connect grant type not supported by provider." diff --git a/keystoneauth1/exceptions/response.py b/keystoneauth1/exceptions/response.py deleted file mode 100644 index 0707eda..0000000 --- a/keystoneauth1/exceptions/response.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - - -__all__ = ('InvalidResponse',) - - -class InvalidResponse(base.ClientException): - message = "Invalid response from server." - - def __init__(self, response): - super(InvalidResponse, self).__init__() - self.response = response diff --git a/keystoneauth1/exceptions/service_providers.py b/keystoneauth1/exceptions/service_providers.py deleted file mode 100644 index 959d6eb..0000000 --- a/keystoneauth1/exceptions/service_providers.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base - -__all__ = ('ServiceProviderNotFound',) - - -class ServiceProviderNotFound(base.ClientException): - """A Service Provider cannot be found.""" - - def __init__(self, sp_id): - self.sp_id = sp_id - msg = 'The Service Provider %(sp)s could not be found' % {'sp': sp_id} - super(ServiceProviderNotFound, self).__init__(msg) diff --git a/keystoneauth1/extras/__init__.py b/keystoneauth1/extras/__init__.py deleted file mode 100644 index fbcb352..0000000 --- a/keystoneauth1/extras/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# NOTE(jamielennox): This directory is designed to reflect the dependency -# extras in the setup.cfg file. If you create an additional dependency section -# like 'kerberos' in the setup.cfg it is expected that there be a kerberos -# package here that can be imported. -# -# e.g. from keystoneauth1.extras import kerberos - -pass diff --git a/keystoneauth1/extras/_saml2/__init__.py b/keystoneauth1/extras/_saml2/__init__.py deleted file mode 100644 index a15d1f9..0000000 --- a/keystoneauth1/extras/_saml2/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras._saml2 import v3 - -_V3_SAML2_AVAILABLE = v3._SAML2_AVAILABLE -_V3_ADFS_AVAILABLE = v3._ADFS_AVAILABLE - -V3Saml2Password = v3.Saml2Password -V3ADFSPassword = v3.ADFSPassword - - -__all__ = ('V3Saml2Password', 'V3ADFSPassword') diff --git a/keystoneauth1/extras/_saml2/_loading.py b/keystoneauth1/extras/_saml2/_loading.py deleted file mode 100644 index 845eac0..0000000 --- a/keystoneauth1/extras/_saml2/_loading.py +++ /dev/null @@ -1,66 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras import _saml2 -from keystoneauth1 import loading - - -class Saml2Password(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return _saml2.V3Saml2Password - - @property - def available(self): - return _saml2._V3_SAML2_AVAILABLE - - def get_options(self): - options = super(Saml2Password, self).get_options() - - options.extend([ - loading.Opt('identity-provider-url', - help=('An Identity Provider URL, where the SAML2 ' - 'authentication request will be sent.')), - loading.Opt('username', help='Username'), - loading.Opt('password', secret=True, help='Password') - ]) - - return options - - -class ADFSPassword(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return _saml2.V3ADFSPassword - - @property - def available(self): - return _saml2._V3_ADFS_AVAILABLE - - def get_options(self): - options = super(ADFSPassword, self).get_options() - - options.extend([ - loading.Opt('identity-provider-url', - help=('An Identity Provider URL, where the SAML ' - 'authentication request will be sent.')), - loading.Opt('service-provider-endpoint', - help="Service Provider's Endpoint"), - loading.Opt('service-provider-entity-id', - help="Service Provider's SAML Entity ID"), - loading.Opt('username', help='Username'), - loading.Opt('password', secret=True, help='Password') - ]) - - return options diff --git a/keystoneauth1/extras/_saml2/v3/__init__.py b/keystoneauth1/extras/_saml2/v3/__init__.py deleted file mode 100644 index 4dbbeff..0000000 --- a/keystoneauth1/extras/_saml2/v3/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras._saml2.v3 import adfs -from keystoneauth1.extras._saml2.v3 import base -from keystoneauth1.extras._saml2.v3 import saml2 - -_SAML2_AVAILABLE = base.etree is not None and saml2.etree is not None -_ADFS_AVAILABLE = base.etree is not None and adfs.etree is not None - -Saml2Password = saml2.Password -ADFSPassword = adfs.Password - -__all__ = ('Saml2Password', 'ADFSPassword') diff --git a/keystoneauth1/extras/_saml2/v3/adfs.py b/keystoneauth1/extras/_saml2/v3/adfs.py deleted file mode 100644 index 4d3b87b..0000000 --- a/keystoneauth1/extras/_saml2/v3/adfs.py +++ /dev/null @@ -1,431 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - -try: - from lxml import etree -except ImportError: - etree = None - -from six.moves import urllib - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.extras._saml2.v3 import base - - -class Password(base.BaseSAMLPlugin): - """Authentication plugin for Microsoft ADFS2.0 IdPs.""" - - 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, - protocol, service_provider_entity_id=None, **kwargs): - """Constructor for ``ADFSPassword``. - - :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 service_provider_entity_id: Service Provider SAML Entity ID - :type service_provider_entity_id: string - - :param username: User's login - :type username: string - - :param password: User's password - :type password: string - - """ - super(Password, self).__init__( - auth_url=auth_url, identity_provider=identity_provider, - identity_provider_url=identity_provider_url, - username=username, password=password, protocol=protocol, **kwargs) - - self.service_provider_endpoint = service_provider_endpoint - self.service_provider_entity_id = service_provider_entity_id - - def _cookies(self, session): - """Check if cookie jar is not empty. - - keystoneauth1.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: - 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 ``keystoneauth1`` - doesn't have mechanisms for reusing such tokens (every time ADFS authn - method is called, keystoneauth1 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_entity_id or - 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: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.AuthorizationFailure: when HTTP - response from the ADFS server is not a valid XML ADFS security - token. - :raises keystoneauth1.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: keystoneauth1.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: keystoneauth1.session.Session - - :raises keystoneauth1.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.federated_token_url, - authenticated=False) - - def get_unscoped_auth_ref(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). - - * Send ADFS Security token to the ADFS server. Step handled by - - * 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 ``ADFSPassword._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 - ``ADFSPassword._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 - ``ADFSPassword._access_service_provider()`` method. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: AccessInfo - :rtype: :py:class:`keystoneauth1.access.AccessInfo` - - """ - 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) - - return access.create(resp=self.authenticated_response) diff --git a/keystoneauth1/extras/_saml2/v3/base.py b/keystoneauth1/extras/_saml2/v3/base.py deleted file mode 100644 index d221d15..0000000 --- a/keystoneauth1/extras/_saml2/v3/base.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -try: - from lxml import etree -except ImportError: - etree = None - -from keystoneauth1 import exceptions -from keystoneauth1.identity import v3 - - -class _Saml2TokenAuthMethod(v3.AuthMethod): - _method_parameters = [] - - def get_auth_data(self, session, auth, headers, **kwargs): - raise exceptions.MethodNotImplemented('This method should never ' - 'be called') - - -class BaseSAMLPlugin(v3.FederationBaseAuth): - - HTTP_MOVED_TEMPORARILY = 302 - HTTP_SEE_OTHER = 303 - - _auth_method_class = _Saml2TokenAuthMethod - - def __init__(self, auth_url, - identity_provider, identity_provider_url, - username, password, protocol, - **kwargs): - """Class constructor 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 identity_provider_url: An Identity Provider URL, where the - SAML2 auhentication 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 - - :param protocol: Protocol to be used for the authentication. - The name must be equal to one configured at the - keystone sp side. This value is used for building - dynamic authentication URL. - Typical value would be: saml2 - :type protocol: string - - """ - super(BaseSAMLPlugin, self).__init__( - auth_url=auth_url, identity_provider=identity_provider, - protocol=protocol, - **kwargs) - self.identity_provider_url = identity_provider_url - self.username = username - self.password = password - - @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) diff --git a/keystoneauth1/extras/_saml2/v3/saml2.py b/keystoneauth1/extras/_saml2/v3/saml2.py deleted file mode 100644 index 1cc5864..0000000 --- a/keystoneauth1/extras/_saml2/v3/saml2.py +++ /dev/null @@ -1,301 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - -try: - from lxml import etree -except ImportError: - etree = None - -import requests -import requests.auth - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import v3 - -_PAOS_NAMESPACE = 'urn:liberty:paos:2003-08' -_ECP_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp' -_PAOS_HEADER = 'application/vnd.paos+xml' - -_PAOS_VER = 'ver="%s";"%s"' % (_PAOS_NAMESPACE, _ECP_NAMESPACE) - -_XML_NAMESPACES = { - 'ecp': _ECP_NAMESPACE, - 'S': 'http://schemas.xmlsoap.org/soap/envelope/', - 'paos': _PAOS_NAMESPACE, -} - -_XBASE = '/S:Envelope/S:Header/' - -_XPATH_SP_RELAY_STATE = '//ecp:RelayState' -_XPATH_SP_CONSUMER_URL = _XBASE + 'paos:Request/@responseConsumerURL' -_XPATH_IDP_CONSUMER_URL = _XBASE + 'ecp:Response/@AssertionConsumerServiceURL' - -_SOAP_FAULT = """ - - - - S:Server - responseConsumerURL from SP and - assertionConsumerServiceURL from IdP do not match - - - - -""" - - -class SamlException(Exception): - """Base SAML plugin exception.""" - - -class InvalidResponse(SamlException): - """Invalid Response from SAML authentication.""" - - -class ConsumerMismatch(SamlException): - """The SP and IDP consumers do not match.""" - - -def _response_xml(response, name): - try: - return etree.XML(response.content) - except etree.XMLSyntaxError as e: - msg = 'SAML2: Error parsing XML returned from %s: %s' % (name, e) - raise InvalidResponse(msg) - - -def _str_from_xml(xml, path): - l = xml.xpath(path, namespaces=_XML_NAMESPACES) - if len(l) != 1: - raise IndexError('%s should provide a single element list' % path) - return l[0] - - -class _SamlAuth(requests.auth.AuthBase): - """A generic SAML ECP plugin for requests. - - This is a multi-step process including multiple HTTP requests. - Authentication consists of: - - * HTTP GET request to the Service Provider. - - It's crucial to include HTTP headers indicating we are expecting SOAP - message in return. Service Provider should respond with a SOAP - message. - - * 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. - - * 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. - """ - - def __init__(self, identity_provider_url, requests_auth): - super(_SamlAuth, self).__init__() - self.identity_provider_url = identity_provider_url - self.requests_auth = requests_auth - - def __call__(self, request): - try: - accept = request.headers['Accept'] - except KeyError: - request.headers['Accept'] = _PAOS_HEADER - else: - request.headers['Accept'] = ','.join([accept, _PAOS_HEADER]) - - request.headers['PAOS'] = _PAOS_VER - request.register_hook('response', self._handle_response) - return request - - def _handle_response(self, response, **kwargs): - if (response.status_code == 200 and - response.headers.get('Content-Type') == _PAOS_HEADER): - response = self._ecp_retry(response, **kwargs) - - return response - - def _ecp_retry(self, sp_response, **kwargs): - history = [sp_response] - - def send(*send_args, **send_kwargs): - req = requests.Request(*send_args, **send_kwargs) - return sp_response.connection.send(req.prepare(), **kwargs) - - authn_request = _response_xml(sp_response, 'Service Provider') - relay_state = _str_from_xml(authn_request, _XPATH_SP_RELAY_STATE) - sp_consumer_url = _str_from_xml(authn_request, _XPATH_SP_CONSUMER_URL) - - authn_request.remove(authn_request[0]) - - idp_response = send('POST', - self.identity_provider_url, - headers={'Content-type': 'text/xml'}, - data=etree.tostring(authn_request), - auth=self.requests_auth) - history.append(idp_response) - - authn_response = _response_xml(idp_response, 'Identity Provider') - idp_consumer_url = _str_from_xml(authn_response, - _XPATH_IDP_CONSUMER_URL) - - if sp_consumer_url != idp_consumer_url: - # send fault message to the SP, discard the response - send('POST', - sp_consumer_url, - data=_SOAP_FAULT, - headers={'Content-Type': _PAOS_HEADER}) - - # 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': sp_response.request.url, - 'sp_consumer_url': sp_consumer_url, - 'identity_provider': self.identity_provider_url, - 'idp_consumer_url': idp_consumer_url - } - - raise ConsumerMismatch(msg) - - authn_response[0][0] = relay_state - - # idp_consumer_url is the URL on the SP that handles the ECP body - # returned and creates an authenticated session. - final_resp = send('POST', - idp_consumer_url, - headers={'Content-Type': _PAOS_HEADER}, - cookies=idp_response.cookies, - data=etree.tostring(authn_response)) - - history.append(final_resp) - - # the SP should then redirect us back to the original URL to retry the - # original request. - if final_resp.status_code in (requests.codes.found, - requests.codes.other): - - # Consume content and release the original connection - # to allow our new request to reuse the same one. - sp_response.content - sp_response.raw.release_conn() - - req = sp_response.request.copy() - req.url = final_resp.headers['location'] - req.prepare_cookies(final_resp.cookies) - - final_resp = sp_response.connection.send(req, **kwargs) - history.append(final_resp) - - final_resp.history.extend(history) - return final_resp - - -class _FederatedSaml(v3.FederationBaseAuth): - - def __init__(self, auth_url, identity_provider, protocol, - identity_provider_url, **kwargs): - super(_FederatedSaml, self).__init__(auth_url, - identity_provider, - protocol, - **kwargs) - self.identity_provider_url = identity_provider_url - - @abc.abstractmethod - def get_requests_auth(self): - raise NotImplementedError() - - def get_unscoped_auth_ref(self, session, **kwargs): - method = self.get_requests_auth() - auth = _SamlAuth(self.identity_provider_url, method) - - try: - resp = session.get(self.federated_token_url, - requests_auth=auth, - authenticated=False) - except SamlException as e: - raise exceptions.AuthorizationFailure(str(e)) - - return access.create(resp=resp) - - -class Password(_FederatedSaml): - 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 - - :param protocol: Protocol to be used for the authentication. - The name must be equal to one configured at the - keystone sp side. This value is used for building - dynamic authentication URL. - Typical value would be: saml2 - :type protocol: string - - """ - - def __init__(self, auth_url, identity_provider, protocol, - identity_provider_url, username, password, **kwargs): - super(Password, self).__init__(auth_url, - identity_provider, - protocol, - identity_provider_url, - **kwargs) - self.username = username - self.password = password - - def get_requests_auth(self): - return requests.auth.HTTPBasicAuth(self.username, self.password) diff --git a/keystoneauth1/extras/kerberos/__init__.py b/keystoneauth1/extras/kerberos/__init__.py deleted file mode 100644 index 6ae3ebd..0000000 --- a/keystoneauth1/extras/kerberos/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -"""Kerberos authentication plugins. - -.. warning:: - This module requires installation of an extra package (`requests_kerberos`) - not installed by default. Without the extra package an import error will - occur. The extra package can be installed using:: - - $ pip install keystoneauth1[kerberos] -""" - -try: - import requests_kerberos -except ImportError: - requests_kerberos = None - -from keystoneauth1 import access -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import federation - - -def _requests_auth(): - # NOTE(jamielennox): request_kerberos.OPTIONAL allows the plugin to accept - # unencrypted error messages where we can't verify the origin of the error - # because we aren't authenticated. - return requests_kerberos.HTTPKerberosAuth( - mutual_authentication=requests_kerberos.OPTIONAL) - - -def _dependency_check(): - if requests_kerberos is None: - raise ImportError(""" -Using the kerberos authentication plugin requires installation of additional -packages. These can be installed with:: - - $ pip install keystoneauth1[kerberos] -""") - - -class KerberosMethod(v3.AuthMethod): - - _method_parameters = [] - - def __init__(self, *args, **kwargs): - _dependency_check() - super(KerberosMethod, self).__init__(*args, **kwargs) - - def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs): - # NOTE(jamielennox): request_kwargs is passed as a kwarg however it is - # required and always present when called from keystoneclient. - request_kwargs['requests_auth'] = _requests_auth() - return 'kerberos', {} - - -class Kerberos(v3.AuthConstructor): - _auth_method_class = KerberosMethod - - -class MappedKerberos(federation.FederationBaseAuth): - """Authenticate using Kerberos via the keystone federation mechanisms. - - This uses the OS-FEDERATION extension to gain an unscoped token and then - use the standard keystone auth process to scope that to any given project. - """ - - def __init__(self, auth_url, identity_provider, protocol, **kwargs): - _dependency_check() - - super(MappedKerberos, self).__init__(auth_url, identity_provider, - protocol, **kwargs) - - def get_unscoped_auth_ref(self, session, **kwargs): - resp = session.get(self.federated_token_url, - requests_auth=_requests_auth(), - authenticated=False) - - return access.create(body=resp.json(), resp=resp) diff --git a/keystoneauth1/extras/kerberos/_loading.py b/keystoneauth1/extras/kerberos/_loading.py deleted file mode 100644 index 6925ad8..0000000 --- a/keystoneauth1/extras/kerberos/_loading.py +++ /dev/null @@ -1,36 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras import kerberos -from keystoneauth1 import loading - - -class Kerberos(loading.BaseV3Loader): - - @property - def plugin_class(self): - return kerberos.Kerberos - - @property - def available(self): - return kerberos.requests_kerberos is not None - - -class MappedKerberos(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return kerberos.MappedKerberos - - @property - def available(self): - return kerberos.requests_kerberos is not None diff --git a/keystoneauth1/extras/oauth1/__init__.py b/keystoneauth1/extras/oauth1/__init__.py deleted file mode 100644 index e267446..0000000 --- a/keystoneauth1/extras/oauth1/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras.oauth1 import v3 - -__all__ = ('V3OAuth1Method', 'V3OAuth1') - - -V3OAuth1Method = v3.OAuth1Method -V3OAuth1 = v3.OAuth1 diff --git a/keystoneauth1/extras/oauth1/_loading.py b/keystoneauth1/extras/oauth1/_loading.py deleted file mode 100644 index fa43232..0000000 --- a/keystoneauth1/extras/oauth1/_loading.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras.oauth1 import v3 -from keystoneauth1 import loading - - -# NOTE(jamielennox): This is not a BaseV3Loader because we don't want to -# include the scoping options like project-id in the option list -class V3OAuth1(loading.BaseIdentityLoader): - - @property - def plugin_class(self): - return v3.OAuth1 - - @property - def available(self): - return v3.oauth1 is not None - - def get_options(self): - options = super(V3OAuth1, self).get_options() - - options.extend([ - loading.Opt('consumer-key', - required=True, - help='OAuth Consumer ID/Key'), - loading.Opt('consumer-secret', - required=True, - help='OAuth Consumer Secret'), - loading.Opt('access-key', - required=True, - help='OAuth Access Key'), - loading.Opt('access-secret', - required=True, - help='OAuth Access Secret'), - ]) - - return options diff --git a/keystoneauth1/extras/oauth1/v3.py b/keystoneauth1/extras/oauth1/v3.py deleted file mode 100644 index 98c768d..0000000 --- a/keystoneauth1/extras/oauth1/v3.py +++ /dev/null @@ -1,80 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Oauth authentication plugins. - -.. warning:: - This module requires installation of an extra package (`oauthlib`) - not installed by default. Without the extra package an import error will - occur. The extra package can be installed using:: - - $ pip install keystoneauth['oauth1'] - -""" - -import logging - -try: - from oauthlib import oauth1 -except ImportError: - oauth1 = None - -from keystoneauth1.identity import v3 - -__all__ = ('OAuth1Method', 'OAuth1') - -LOG = logging.getLogger(__name__) - - -class OAuth1Method(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 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', {} - - def get_cache_id_elements(self): - return dict(('oauth1_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -class OAuth1(v3.AuthConstructor): - - _auth_method_class = OAuth1Method - - def __init__(self, *args, **kwargs): - super(OAuth1, self).__init__(*args, **kwargs) - - if self.has_scope_parameters: - LOG.warning('Scoping parameters such as a project were provided ' - 'to the OAuth1 plugin. Because OAuth1 access is ' - 'always scoped to a project these will be ignored by ' - 'the identity server') diff --git a/keystoneauth1/fixture/__init__.py b/keystoneauth1/fixture/__init__.py deleted file mode 100644 index d25a3e4..0000000 --- a/keystoneauth1/fixture/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 use in testing. - -They are 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 (keystoneauth or other). Because of this there may be dependencies -from this module on libraries that are only available in testing. -""" - -from keystoneauth1.fixture.discovery import * # noqa -from keystoneauth1.fixture import exception -from keystoneauth1.fixture import v2 -from keystoneauth1.fixture import v3 - - -FixtureValidationError = exception.FixtureValidationError -V2Token = v2.Token -V3Token = v3.Token -V3FederationToken = v3.V3FederationToken - -__all__ = ('DiscoveryList', - 'FixtureValidationError', - 'V2Discovery', - 'V3Discovery', - 'V2Token', - 'V3Token', - 'V3FederationToken', - 'VersionDiscovery', - ) diff --git a/keystoneauth1/fixture/discovery.py b/keystoneauth1/fixture/discovery.py deleted file mode 100644 index 5c1e2db..0000000 --- a/keystoneauth1/fixture/discovery.py +++ /dev/null @@ -1,373 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional - -from keystoneauth1 import _utils as utils - -__all__ = ('DiscoveryList', - 'V2Discovery', - 'V3Discovery', - 'VersionDiscovery', - ) - -_DEFAULT_DAYS_AGO = 30 - - -class DiscoveryBase(dict): - """The basic version discovery structure. - - All version discovery elements should have access to these values. - - :param string id: The version id for this version entry. - :param string status: The status of this entry. - :param DateTime updated: When the API was last updated. - """ - - @positional() - def __init__(self, id, status=None, updated=None): - super(DiscoveryBase, self).__init__() - - self.id = id - self.status = status or 'stable' - self.updated = updated or utils.before_utcnow(days=_DEFAULT_DAYS_AGO) - - @property - def id(self): - return self.get('id') - - @id.setter - def id(self, value): - self['id'] = value - - @property - def status(self): - return self.get('status') - - @status.setter - def status(self, value): - self['status'] = value - - @property - def links(self): - return self.setdefault('links', []) - - @property - def updated_str(self): - return self.get('updated') - - @updated_str.setter - def updated_str(self, value): - self['updated'] = value - - @property - def updated(self): - return utils.parse_isotime(self.updated_str) - - @updated.setter - def updated(self, value): - self.updated_str = value.isoformat() - - @positional() - def add_link(self, href, rel='self', type=None): - link = {'href': href, 'rel': rel} - if type: - link['type'] = type - self.links.append(link) - return link - - @property - def media_types(self): - return self.setdefault('media-types', []) - - @positional(1) - def add_media_type(self, base, type): - mt = {'base': base, 'type': type} - self.media_types.append(mt) - return mt - - -class VersionDiscovery(DiscoveryBase): - """A Version element for non-keystone services without microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. - """ - - def __init__(self, href, id, **kwargs): - super(VersionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - -class MicroversionDiscovery(DiscoveryBase): - """A Version element that has microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. - :param string min_version: The minimum supported microversion. (optional) - :param string max_version: The maximum supported microversion. (optional) - """ - - @positional() - def __init__(self, href, id, min_version='', max_version='', **kwargs): - super(MicroversionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - self.min_version = min_version - self.max_version = max_version - - @property - def min_version(self): - return self.get('min_version') - - @min_version.setter - def min_version(self, value): - self['min_version'] = value - - @property - def max_version(self): - return self.get('max_version') - - @max_version.setter - def max_version(self, value): - self['max_version'] = value - - -class NovaMicroversionDiscovery(DiscoveryBase): - """A Version element with nova-style microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param href: The url that this entry should point to. - :param string id: The version id that should be reported. - :param string min_version: The minimum microversion supported. (optional) - :param string version: The maximum microversion supported. (optional) - """ - - @positional() - def __init__(self, href, id, min_version=None, version=None, **kwargs): - super(NovaMicroversionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - self.min_version = min_version - self.version = version - - @property - def min_version(self): - return self.get('min_version') - - @min_version.setter - def min_version(self, value): - if value: - self['min_version'] = value - - @property - def version(self): - return self.get('version') - - @version.setter - def version(self, value): - if value: - self['version'] = value - - -class V2Discovery(DiscoveryBase): - """A Version element for a V2 identity service endpoint. - - Provides some default values and helper methods for creating a v2.0 - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. (optional) - Defaults to 'v2.0'. - :param bool html: Add HTML describedby links to the structure. - :param bool pdf: Add PDF describedby links to the structure. - - """ - - _DESC_URL = 'https://developer.openstack.org/api-ref/identity/v2/' - - @positional() - def __init__(self, href, id=None, html=True, pdf=True, **kwargs): - super(V2Discovery, self).__init__(id or 'v2.0', **kwargs) - - self.add_link(href) - - if html: - self.add_html_description() - if pdf: - self.add_pdf_description() - - def add_html_description(self): - """Add the HTML described by links. - - The standard structure includes a link to a HTML document with the - API specification. Add it to this entry. - """ - self.add_link(href=self._DESC_URL + 'content', - rel='describedby', - type='text/html') - - def add_pdf_description(self): - """Add the PDF described by links. - - The standard structure includes a link to a PDF document with the - API specification. Add it to this entry. - """ - self.add_link(href=self._DESC_URL + 'identity-dev-guide-2.0.pdf', - rel='describedby', - type='application/pdf') - - -class V3Discovery(DiscoveryBase): - """A Version element for a V3 identity service endpoint. - - Provides some default values and helper methods for creating a v3 - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param href: The url that this entry should point to. - :param string id: The version id that should be reported. (optional) - Defaults to 'v3.0'. - :param bool json: Add JSON media-type elements to the structure. - :param bool xml: Add XML media-type elements to the structure. - """ - - @positional() - def __init__(self, href, id=None, json=True, xml=True, **kwargs): - super(V3Discovery, self).__init__(id or 'v3.0', **kwargs) - - self.add_link(href) - - if json: - self.add_json_media_type() - if xml: - self.add_xml_media_type() - - def add_json_media_type(self): - """Add the JSON media-type links. - - The standard structure includes a list of media-types that the endpoint - supports. Add JSON to the list. - """ - self.add_media_type(base='application/json', - type='application/vnd.openstack.identity-v3+json') - - def add_xml_media_type(self): - """Add the XML media-type links. - - The standard structure includes a list of media-types that the endpoint - supports. Add XML to the list. - """ - self.add_media_type(base='application/xml', - type='application/vnd.openstack.identity-v3+xml') - - -class DiscoveryList(dict): - """A List of version elements. - - Creates a correctly structured list of identity service endpoints for - use in testing with discovery. - - :param string href: The url that this should be based at. - :param bool v2: Add a v2 element. - :param bool v3: Add a v3 element. - :param string v2_status: The status to use for the v2 element. - :param DateTime v2_updated: The update time to use for the v2 element. - :param bool v2_html: True to add a html link to the v2 element. - :param bool v2_pdf: True to add a pdf link to the v2 element. - :param string v3_status: The status to use for the v3 element. - :param DateTime v3_updated: The update time to use for the v3 element. - :param bool v3_json: True to add a html link to the v2 element. - :param bool v3_xml: True to add a pdf link to the v2 element. - """ - - TEST_URL = 'http://keystone.host:5000/' - - @positional(2) - def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None, - v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True, - v3_status=None, v3_updated=None, v3_json=True, v3_xml=True): - super(DiscoveryList, self).__init__(versions={'values': []}) - - href = href or self.TEST_URL - - if v2: - v2_href = href.rstrip('/') + '/v2.0' - self.add_v2(v2_href, id=v2_id, status=v2_status, - updated=v2_updated, html=v2_html, pdf=v2_pdf) - - if v3: - v3_href = href.rstrip('/') + '/v3' - self.add_v3(v3_href, id=v3_id, status=v3_status, - updated=v3_updated, json=v3_json, xml=v3_xml) - - @property - def versions(self): - return self['versions']['values'] - - def add_version(self, version): - """Add a new version structure to the list. - - :param dict version: A new version structure to add to the list. - """ - self.versions.append(version) - - def add_v2(self, href, **kwargs): - """Add a v2 version to the list. - - The parameters are the same as V2Discovery. - """ - obj = V2Discovery(href, **kwargs) - self.add_version(obj) - return obj - - def add_v3(self, href, **kwargs): - """Add a v3 version to the list. - - The parameters are the same as V3Discovery. - """ - obj = V3Discovery(href, **kwargs) - self.add_version(obj) - return obj - - def add_microversion(self, href, id, **kwargs): - """Add a microversion version to the list. - - The parameters are the same as MicroversionDiscovery. - """ - obj = MicroversionDiscovery(href=href, id=id, **kwargs) - self.add_version(obj) - return obj - - def add_nova_microversion(self, href, id, **kwargs): - """Add a nova microversion version to the list. - - The parameters are the same as NovaMicroversionDiscovery. - """ - obj = NovaMicroversionDiscovery(href=href, id=id, **kwargs) - self.add_version(obj) - return obj diff --git a/keystoneauth1/fixture/exception.py b/keystoneauth1/fixture/exception.py deleted file mode 100644 index 416a3cf..0000000 --- a/keystoneauth1/fixture/exception.py +++ /dev/null @@ -1,20 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 FixtureValidationError(Exception): - """The token you created is not legitimate. - - The data contained in the token that was generated is not valid and would - not have been returned from a keystone server. You should not do testing - with this token. - """ diff --git a/keystoneauth1/fixture/hooks.py b/keystoneauth1/fixture/hooks.py deleted file mode 100644 index 0c51e52..0000000 --- a/keystoneauth1/fixture/hooks.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (c) 2016 Hewlett-Packard Enterprise 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. -"""Custom hooks for betamax and keystoneauth. - - Module providing a set of hooks specially designed for - interacting with clouds and keystone authentication. - -:author: Yolanda Robla -""" - -import json - - -def mask_fixture_values(nested, prev_key): - for key, value in nested.items(): - if isinstance(value, dict): - mask_fixture_values(value, key) - else: - if key in ('tenantName', 'username'): - nested[key] = 'dummy' - elif prev_key in ('user', 'project', 'tenant') and key == 'name': - nested[key] = 'dummy' - elif prev_key == 'domain' and key == 'id': - nested[key] = 'dummy' - elif key == 'password': - nested[key] = '********' - elif prev_key == 'token' and key in ('expires', 'expires_at'): - nested[key] = '9999-12-31T23:59:59Z' - - -def pre_record_hook(interaction, cassette): - """Hook to mask saved data. - - This hook will be triggered before saving the interaction, and - will perform two tasks: - - mask user, project and password in the saved data - - set token expiration time to an inifinite time. - """ - request_body = interaction.data['request']['body'] - if request_body.get('string'): - parsed_content = json.loads(request_body['string']) - mask_fixture_values(parsed_content, None) - request_body['string'] = json.dumps(parsed_content) - - response_body = interaction.data['response']['body'] - if response_body.get('string'): - parsed_content = json.loads(response_body['string']) - mask_fixture_values(parsed_content, None) - response_body['string'] = json.dumps(parsed_content) diff --git a/keystoneauth1/fixture/keystoneauth_betamax.py b/keystoneauth1/fixture/keystoneauth_betamax.py deleted file mode 100644 index a1aea16..0000000 --- a/keystoneauth1/fixture/keystoneauth_betamax.py +++ /dev/null @@ -1,136 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A fixture to wrap the session constructor for use with Betamax.""" - -from functools import partial - -import betamax -import fixtures -import mock -import requests - -from keystoneauth1.fixture import hooks -from keystoneauth1.fixture import serializer as yaml_serializer -from keystoneauth1 import session - - -class BetamaxFixture(fixtures.Fixture): - - def __init__(self, cassette_name, cassette_library_dir=None, - serializer=None, record=False, - pre_record_hook=hooks.pre_record_hook, - serializer_name=None, request_matchers=None): - """Configure Betamax for the test suite. - - :param str cassette_name: - This is simply the name of the cassette without any file extension - or containing directory. For example, to generate - ``keystoneauth1/tests/unit/data/example.yaml``, one would pass - only ``example``. - :param str cassette_library_dir: - This is the directory that will contain all cassette files. In - ``keystoneauth1/tests/unit/data/example.yaml`` you would pass - ``keystoneauth1/tests/unit/data/``. - :param serializer: - A class that implements the Serializer API in Betamax. See also: - https://betamax.readthedocs.io/en/latest/serializers.html - :param record: - The Betamax record mode to use. If ``False`` (the default), then - Betamax will not record anything. For more information about - record modes, see: - https://betamax.readthedocs.io/en/latest/record_modes.html - :param callable pre_record_hook: - Function or callable to use to perform some handling of the - request or response data prior to saving it to disk. - :param str serializer_name: - The name of a serializer already registered with Betamax to use - to handle cassettes. For example, if you want to use the default - Betamax serializer, you would pass ``'json'`` to this parameter. - :param list request_matchers: - The list of request matcher names to use with Betamax. Betamax's - default list is used if none are specified. See also: - https://betamax.readthedocs.io/en/latest/matchers.html - """ - self.cassette_library_dir = cassette_library_dir - self.record = record - self.cassette_name = cassette_name - if not (serializer or serializer_name): - serializer = yaml_serializer.YamlJsonSerializer - serializer_name = serializer.name - if serializer: - betamax.Betamax.register_serializer(serializer) - self.serializer = serializer - self._serializer_name = serializer_name - self.pre_record_hook = pre_record_hook - self.use_cassette_kwargs = {} - if request_matchers is not None: - self.use_cassette_kwargs['match_requests_on'] = request_matchers - - @property - def serializer_name(self): - """Determine the name of the selected serializer. - - If a class was specified, use the name attribute to generate this, - otherwise, use the serializer_name parameter from ``__init__``. - - :returns: - Name of the serializer - :rtype: - str - """ - if self.serializer: - return self.serializer.name - return self._serializer_name - - def setUp(self): - super(BetamaxFixture, self).setUp() - self.mockpatch = mock.patch.object( - session, '_construct_session', - partial(_construct_session_with_betamax, self)) - self.mockpatch.start() - # Unpatch during cleanup - self.addCleanup(self.mockpatch.stop) - - -def _construct_session_with_betamax(fixture, session_obj=None): - # NOTE(morganfainberg): This function should contain the logic of - # keystoneauth1.session._construct_session as it replaces the - # _construct_session function to apply betamax magic to the requests - # session object. - if not session_obj: - session_obj = requests.Session() - # Use TCPKeepAliveAdapter to fix bug 1323862 - for scheme in list(session_obj.adapters.keys()): - session_obj.mount(scheme, session.TCPKeepAliveAdapter()) - - with betamax.Betamax.configure() as config: - config.before_record(callback=fixture.pre_record_hook) - fixture.recorder = betamax.Betamax( - session_obj, cassette_library_dir=fixture.cassette_library_dir) - - record = 'none' - serializer = None - - if fixture.record in ['once', 'all', 'new_episodes']: - record = fixture.record - - serializer = fixture.serializer_name - - fixture.recorder.use_cassette(fixture.cassette_name, - serialize_with=serializer, - record=record, - **fixture.use_cassette_kwargs) - - fixture.recorder.start() - fixture.addCleanup(fixture.recorder.stop) - return session_obj diff --git a/keystoneauth1/fixture/serializer.py b/keystoneauth1/fixture/serializer.py deleted file mode 100644 index 52765e4..0000000 --- a/keystoneauth1/fixture/serializer.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A serializer to emit YAML but with request body in nicely formatted JSON.""" - -import json -import os - -import betamax.serializers.base -import six -import yaml - - -def _should_use_block(value): - for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029": - if c in value: - return True - return False - - -def _represent_scalar(self, tag, value, style=None): - if style is None: - if _should_use_block(value): - style = '|' - else: - style = self.default_style - - node = yaml.representer.ScalarNode(tag, value, style=style) - if self.alias_key is not None: - self.represented_objects[self.alias_key] = node - return node - - -def _unicode_representer(dumper, uni): - node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) - return node - - -def _indent_json(val): - if not val: - return '' - - return json.dumps( - json.loads(val), indent=2, - separators=(',', ': '), sort_keys=False, - default=six.text_type) - - -def _is_json_body(interaction): - content_type = interaction['headers'].get('Content-Type', []) - return 'application/json' in content_type - - -class YamlJsonSerializer(betamax.serializers.base.BaseSerializer): - - name = "yamljson" - - @staticmethod - def generate_cassette_name(cassette_library_dir, cassette_name): - return os.path.join( - cassette_library_dir, "{name}.yaml".format(name=cassette_name)) - - def serialize(self, cassette_data): - # Reserialize internal json with indentation - for interaction in cassette_data['http_interactions']: - for key in ('request', 'response'): - if _is_json_body(interaction[key]): - interaction[key]['body']['string'] = _indent_json( - interaction[key]['body']['string']) - - class MyDumper(yaml.Dumper): - """Specialized Dumper which does nice blocks and unicode.""" - - yaml.representer.BaseRepresenter.represent_scalar = _represent_scalar - - MyDumper.add_representer(six.text_type, _unicode_representer) - - return yaml.dump( - cassette_data, Dumper=MyDumper, default_flow_style=False) - - def deserialize(self, cassette_data): - try: - deserialized = yaml.safe_load(cassette_data) - except yaml.error.YAMLError: - deserialized = None - - if deserialized is not None: - return deserialized - return {} diff --git a/keystoneauth1/fixture/v2.py b/keystoneauth1/fixture/v2.py deleted file mode 100644 index 4588d60..0000000 --- a/keystoneauth1/fixture/v2.py +++ /dev/null @@ -1,247 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 _utils -from keystoneauth1.fixture import exception - - -class _Service(dict): - - def add_endpoint(self, public, admin=None, internal=None, - tenant_id=None, region=None, id=None): - data = {'tenantId': tenant_id or uuid.uuid4().hex, - 'publicURL': public, - 'adminURL': admin or public, - 'internalURL': internal or public, - 'region': region, - 'id': id or uuid.uuid4().hex} - - self.setdefault('endpoints', []).append(data) - return data - - -class Token(dict): - """A V2 Keystone token that can be used for testing. - - This object is designed to allow clients to generate a correct V2 token for - use in there test code. It should prevent clients from having to know the - correct token format and allow them to test the portions of token handling - that matter to them and not copy and paste sample. - """ - - def __init__(self, token_id=None, expires=None, issued=None, - tenant_id=None, tenant_name=None, user_id=None, - user_name=None, trust_id=None, trustee_user_id=None, - audit_id=None, audit_chain_id=None): - super(Token, self).__init__() - - self.token_id = token_id or uuid.uuid4().hex - self.user_id = user_id or uuid.uuid4().hex - self.user_name = user_name or uuid.uuid4().hex - self.audit_id = audit_id or uuid.uuid4().hex - - if not issued: - issued = _utils.before_utcnow(minutes=2) - if not expires: - expires = issued + datetime.timedelta(hours=1) - - try: - self.issued = issued - except (TypeError, AttributeError): - # issued should be able to be passed as a string so ignore - self.issued_str = issued - - try: - self.expires = expires - except (TypeError, AttributeError): - # expires should be able to be passed as a string so ignore - self.expires_str = expires - - if tenant_id or tenant_name: - self.set_scope(tenant_id, tenant_name) - - if trust_id or trustee_user_id: - # the trustee_user_id will generally be the same as the user_id as - # the token is being issued to the trustee - self.set_trust(id=trust_id, - trustee_user_id=trustee_user_id or user_id) - - if audit_chain_id: - self.audit_chain_id = audit_chain_id - - @property - def root(self): - return self.setdefault('access', {}) - - @property - def _token(self): - return self.root.setdefault('token', {}) - - @property - def token_id(self): - return self._token['id'] - - @token_id.setter - def token_id(self, value): - self._token['id'] = value - - @property - def expires_str(self): - return self._token['expires'] - - @expires_str.setter - def expires_str(self, value): - self._token['expires'] = value - - @property - def expires(self): - return _utils.parse_isotime(self.expires_str) - - @expires.setter - def expires(self, value): - self.expires_str = value.isoformat() - - @property - def issued_str(self): - return self._token['issued_at'] - - @issued_str.setter - def issued_str(self, value): - self._token['issued_at'] = value - - @property - def issued(self): - return _utils.parse_isotime(self.issued_str) - - @issued.setter - def issued(self, value): - self.issued_str = value.isoformat() - - @property - def _user(self): - return self.root.setdefault('user', {}) - - @property - def user_id(self): - return self._user['id'] - - @user_id.setter - def user_id(self, value): - self._user['id'] = value - - @property - def user_name(self): - return self._user['name'] - - @user_name.setter - def user_name(self, value): - self._user['name'] = value - - @property - def tenant_id(self): - return self._token.get('tenant', {}).get('id') - - @tenant_id.setter - def tenant_id(self, value): - self._token.setdefault('tenant', {})['id'] = value - - @property - def tenant_name(self): - return self._token.get('tenant', {}).get('name') - - @tenant_name.setter - def tenant_name(self, value): - self._token.setdefault('tenant', {})['name'] = value - - @property - def _metadata(self): - return self.root.setdefault('metadata', {}) - - @property - def trust_id(self): - return self.root.setdefault('trust', {}).get('id') - - @trust_id.setter - def trust_id(self, value): - self.root.setdefault('trust', {})['id'] = value - - @property - def trustee_user_id(self): - return self.root.setdefault('trust', {}).get('trustee_user_id') - - @trustee_user_id.setter - def trustee_user_id(self, value): - self.root.setdefault('trust', {})['trustee_user_id'] = value - - @property - def audit_id(self): - try: - return self._token.get('audit_ids', [])[0] - except IndexError: - return None - - @audit_id.setter - def audit_id(self, value): - audit_chain_id = self.audit_chain_id - lval = [value] if audit_chain_id else [value, audit_chain_id] - self._token['audit_ids'] = lval - - @property - def audit_chain_id(self): - try: - return self._token.get('audit_ids', [])[1] - except IndexError: - return None - - @audit_chain_id.setter - def audit_chain_id(self, value): - self._token['audit_ids'] = [self.audit_id, value] - - def validate(self): - scoped = 'tenant' in self.token - catalog = self.root.get('serviceCatalog') - - if catalog and not scoped: - msg = 'You cannot have a service catalog on an unscoped token' - raise exception.FixtureValidationError(msg) - - if scoped and not self.user.get('roles'): - msg = 'You must have roles on a token to scope it' - raise exception.FixtureValidationError(msg) - - def add_role(self, name=None, id=None): - id = id or uuid.uuid4().hex - name = name or uuid.uuid4().hex - roles = self._user.setdefault('roles', []) - roles.append({'name': name}) - self._metadata.setdefault('roles', []).append(id) - return {'id': id, 'name': name} - - def add_service(self, type, name=None): - name = name or uuid.uuid4().hex - service = _Service(name=name, type=type) - self.root.setdefault('serviceCatalog', []).append(service) - return service - - def set_scope(self, id=None, name=None): - self.tenant_id = id or uuid.uuid4().hex - self.tenant_name = name or uuid.uuid4().hex - - def set_trust(self, id=None, trustee_user_id=None): - self.trust_id = id or uuid.uuid4().hex - self.trustee_user_id = trustee_user_id or uuid.uuid4().hex - - def set_bind(self, name, data): - self._token.setdefault('bind', {})[name] = data diff --git a/keystoneauth1/fixture/v3.py b/keystoneauth1/fixture/v3.py deleted file mode 100644 index 2fbcd58..0000000 --- a/keystoneauth1/fixture/v3.py +++ /dev/null @@ -1,466 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 _utils -from keystoneauth1.fixture import exception - - -class _Service(dict): - """One of the services that exist in the catalog. - - You use this by adding a service to a token which returns an instance of - this object and then you can add_endpoints to the service. - """ - - def add_endpoint(self, interface, url, region=None, id=None): - data = {'id': id or uuid.uuid4().hex, - 'interface': interface, - 'url': url, - 'region': region, - 'region_id': region} - self.setdefault('endpoints', []).append(data) - return data - - def add_standard_endpoints(self, public=None, admin=None, internal=None, - region=None): - ret = [] - - if public: - ret.append(self.add_endpoint('public', public, region=region)) - if admin: - ret.append(self.add_endpoint('admin', admin, region=region)) - if internal: - ret.append(self.add_endpoint('internal', internal, region=region)) - - return ret - - -class Token(dict): - """A V3 Keystone token that can be used for testing. - - This object is designed to allow clients to generate a correct V3 token for - use in there test code. It should prevent clients from having to know the - correct token format and allow them to test the portions of token handling - that matter to them and not copy and paste sample. - """ - - def __init__(self, expires=None, issued=None, user_id=None, user_name=None, - user_domain_id=None, user_domain_name=None, methods=None, - project_id=None, project_name=None, project_domain_id=None, - project_domain_name=None, domain_id=None, domain_name=None, - trust_id=None, trust_impersonation=None, trustee_user_id=None, - trustor_user_id=None, oauth_access_token_id=None, - oauth_consumer_id=None, audit_id=None, audit_chain_id=None, - is_admin_project=None, project_is_domain=None): - super(Token, self).__init__() - - self.user_id = user_id or uuid.uuid4().hex - self.user_name = user_name or uuid.uuid4().hex - self.user_domain_id = user_domain_id or uuid.uuid4().hex - self.user_domain_name = user_domain_name or uuid.uuid4().hex - self.audit_id = audit_id or uuid.uuid4().hex - - if not methods: - methods = ['password'] - self.methods.extend(methods) - - if not issued: - issued = _utils.before_utcnow(minutes=2) - - try: - self.issued = issued - except (TypeError, AttributeError): - # issued should be able to be passed as a string so ignore - self.issued_str = issued - - if not expires: - expires = self.issued + datetime.timedelta(hours=1) - - try: - self.expires = expires - except (TypeError, AttributeError): - # expires should be able to be passed as a string so ignore - self.expires_str = expires - - if (project_id or project_name or - project_domain_id or project_domain_name): - self.set_project_scope(id=project_id, - name=project_name, - domain_id=project_domain_id, - domain_name=project_domain_name, - is_domain=project_is_domain) - - if domain_id or domain_name: - self.set_domain_scope(id=domain_id, name=domain_name) - - if (trust_id or (trust_impersonation is not None) or - trustee_user_id or trustor_user_id): - self.set_trust_scope(id=trust_id, - impersonation=trust_impersonation, - trustee_user_id=trustee_user_id, - trustor_user_id=trustor_user_id) - - if oauth_access_token_id or oauth_consumer_id: - self.set_oauth(access_token_id=oauth_access_token_id, - consumer_id=oauth_consumer_id) - - if audit_chain_id: - self.audit_chain_id = audit_chain_id - - if is_admin_project is not None: - self.is_admin_project = is_admin_project - - @property - def root(self): - return self.setdefault('token', {}) - - @property - def expires_str(self): - return self.root.get('expires_at') - - @expires_str.setter - def expires_str(self, value): - self.root['expires_at'] = value - - @property - def expires(self): - return _utils.parse_isotime(self.expires_str) - - @expires.setter - def expires(self, value): - self.expires_str = value.isoformat() - - @property - def issued_str(self): - return self.root.get('issued_at') - - @issued_str.setter - def issued_str(self, value): - self.root['issued_at'] = value - - @property - def issued(self): - return _utils.parse_isotime(self.issued_str) - - @issued.setter - def issued(self, value): - self.issued_str = value.isoformat() - - @property - def _user(self): - return self.root.setdefault('user', {}) - - @property - def user_id(self): - return self._user.get('id') - - @user_id.setter - def user_id(self, value): - self._user['id'] = value - - @property - def user_name(self): - return self._user.get('name') - - @user_name.setter - def user_name(self, value): - self._user['name'] = value - - @property - def _user_domain(self): - return self._user.setdefault('domain', {}) - - @_user_domain.setter - def _user_domain(self, domain): - self._user['domain'] = domain - - @property - def user_domain_id(self): - return self._user_domain.get('id') - - @user_domain_id.setter - def user_domain_id(self, value): - self._user_domain['id'] = value - - @property - def user_domain_name(self): - return self._user_domain.get('name') - - @user_domain_name.setter - def user_domain_name(self, value): - self._user_domain['name'] = value - - @property - def methods(self): - return self.root.setdefault('methods', []) - - @property - def project_id(self): - return self.root.get('project', {}).get('id') - - @project_id.setter - def project_id(self, value): - self.root.setdefault('project', {})['id'] = value - - @property - def project_is_domain(self): - return self.root.get('is_domain') - - @project_is_domain.setter - def project_is_domain(self, value): - self.root['is_domain'] = value - - @property - def project_name(self): - return self.root.get('project', {}).get('name') - - @project_name.setter - def project_name(self, value): - self.root.setdefault('project', {})['name'] = value - - @property - def project_domain_id(self): - return self.root.get('project', {}).get('domain', {}).get('id') - - @project_domain_id.setter - def project_domain_id(self, value): - project = self.root.setdefault('project', {}) - project.setdefault('domain', {})['id'] = value - - @property - def project_domain_name(self): - return self.root.get('project', {}).get('domain', {}).get('name') - - @project_domain_name.setter - def project_domain_name(self, value): - project = self.root.setdefault('project', {}) - project.setdefault('domain', {})['name'] = value - - @property - def domain_id(self): - return self.root.get('domain', {}).get('id') - - @domain_id.setter - def domain_id(self, value): - self.root.setdefault('domain', {})['id'] = value - - @property - def domain_name(self): - return self.root.get('domain', {}).get('name') - - @domain_name.setter - def domain_name(self, value): - self.root.setdefault('domain', {})['name'] = value - - @property - def trust_id(self): - return self.root.get('OS-TRUST:trust', {}).get('id') - - @trust_id.setter - def trust_id(self, value): - self.root.setdefault('OS-TRUST:trust', {})['id'] = value - - @property - def trust_impersonation(self): - return self.root.get('OS-TRUST:trust', {}).get('impersonation') - - @trust_impersonation.setter - def trust_impersonation(self, value): - self.root.setdefault('OS-TRUST:trust', {})['impersonation'] = value - - @property - def trustee_user_id(self): - trust = self.root.get('OS-TRUST:trust', {}) - return trust.get('trustee_user', {}).get('id') - - @trustee_user_id.setter - def trustee_user_id(self, value): - trust = self.root.setdefault('OS-TRUST:trust', {}) - trust.setdefault('trustee_user', {})['id'] = value - - @property - def trustor_user_id(self): - trust = self.root.get('OS-TRUST:trust', {}) - return trust.get('trustor_user', {}).get('id') - - @trustor_user_id.setter - def trustor_user_id(self, value): - trust = self.root.setdefault('OS-TRUST:trust', {}) - trust.setdefault('trustor_user', {})['id'] = value - - @property - def oauth_access_token_id(self): - return self.root.get('OS-OAUTH1', {}).get('access_token_id') - - @oauth_access_token_id.setter - def oauth_access_token_id(self, value): - self.root.setdefault('OS-OAUTH1', {})['access_token_id'] = value - - @property - def oauth_consumer_id(self): - return self.root.get('OS-OAUTH1', {}).get('consumer_id') - - @oauth_consumer_id.setter - def oauth_consumer_id(self, value): - self.root.setdefault('OS-OAUTH1', {})['consumer_id'] = value - - @property - def audit_id(self): - try: - return self.root.get('audit_ids', [])[0] - except IndexError: - return None - - @audit_id.setter - def audit_id(self, value): - audit_chain_id = self.audit_chain_id - lval = [value] if audit_chain_id else [value, audit_chain_id] - self.root['audit_ids'] = lval - - @property - def audit_chain_id(self): - try: - return self.root.get('audit_ids', [])[1] - except IndexError: - return None - - @audit_chain_id.setter - def audit_chain_id(self, value): - self.root['audit_ids'] = [self.audit_id, value] - - @property - def role_ids(self): - return [r['id'] for r in self.root.get('roles', [])] - - @property - def role_names(self): - return [r['name'] for r in self.root.get('roles', [])] - - @property - def is_admin_project(self): - return self.root.get('is_admin_project') - - @is_admin_project.setter - def is_admin_project(self, value): - self.root['is_admin_project'] = value - - @is_admin_project.deleter - def is_admin_project(self): - self.root.pop('is_admin_project', None) - - def validate(self): - project = self.root.get('project') - domain = self.root.get('domain') - trust = self.root.get('OS-TRUST:trust') - catalog = self.root.get('catalog') - roles = self.root.get('roles') - scoped = project or domain or trust - - if sum((bool(project), bool(domain), bool(trust))) > 1: - msg = 'You cannot scope to multiple targets' - raise exception.FixtureValidationError(msg) - - if catalog and not scoped: - msg = 'You cannot have a service catalog on an unscoped token' - raise exception.FixtureValidationError(msg) - - if scoped and not self.user.get('roles'): - msg = 'You must have roles on a token to scope it' - raise exception.FixtureValidationError(msg) - - if bool(scoped) != bool(roles): - msg = 'You must be scoped to have roles and vice-versa' - raise exception.FixtureValidationError(msg) - - def add_role(self, name=None, id=None): - roles = self.root.setdefault('roles', []) - data = {'id': id or uuid.uuid4().hex, - 'name': name or uuid.uuid4().hex} - roles.append(data) - return data - - def add_service(self, type, name=None, id=None): - service = _Service(type=type, id=id or uuid.uuid4().hex) - if name: - service['name'] = name - self.root.setdefault('catalog', []).append(service) - return service - - def set_project_scope(self, id=None, name=None, domain_id=None, - domain_name=None, is_domain=None): - self.project_id = id or uuid.uuid4().hex - self.project_name = name or uuid.uuid4().hex - self.project_domain_id = domain_id or uuid.uuid4().hex - self.project_domain_name = domain_name or uuid.uuid4().hex - - if is_domain is not None: - self.project_is_domain = is_domain - - def set_domain_scope(self, id=None, name=None): - self.domain_id = id or uuid.uuid4().hex - self.domain_name = name or uuid.uuid4().hex - - def set_trust_scope(self, id=None, impersonation=False, - trustee_user_id=None, trustor_user_id=None): - self.trust_id = id or uuid.uuid4().hex - self.trust_impersonation = impersonation - self.trustee_user_id = trustee_user_id or uuid.uuid4().hex - self.trustor_user_id = trustor_user_id or uuid.uuid4().hex - - def set_oauth(self, access_token_id=None, consumer_id=None): - self.oauth_access_token_id = access_token_id or uuid.uuid4().hex - self.oauth_consumer_id = consumer_id or uuid.uuid4().hex - - @property - def service_providers(self): - return self.root.get('service_providers') - - def add_service_provider(self, sp_id, sp_auth_url, sp_url): - _service_providers = self.root.setdefault('service_providers', []) - sp = {'id': sp_id, 'auth_url': sp_auth_url, 'sp_url': sp_url} - _service_providers.append(sp) - return sp - - def set_bind(self, name, data): - self.root.setdefault('bind', {})[name] = data - - -class V3FederationToken(Token): - """A V3 Keystone Federation token that can be used for testing. - - Similar to V3Token, this object is designed to allow clients to generate - a correct V3 federation token for use in test code. - """ - - FEDERATED_DOMAIN_ID = 'Federated' - - def __init__(self, methods=None, identity_provider=None, protocol=None, - groups=None): - methods = methods or ['saml2'] - super(V3FederationToken, self).__init__(methods=methods) - self._user_domain = {'id': V3FederationToken.FEDERATED_DOMAIN_ID} - self.add_federation_info_to_user(identity_provider, protocol, groups) - - def add_federation_info_to_user(self, identity_provider=None, - protocol=None, groups=None): - data = { - "OS-FEDERATION": { - "identity_provider": identity_provider or uuid.uuid4().hex, - "protocol": protocol or uuid.uuid4().hex, - "groups": groups or [{"id": uuid.uuid4().hex}] - } - } - self._user.update(data) - return data diff --git a/keystoneauth1/hacking/__init__.py b/keystoneauth1/hacking/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/hacking/checks.py b/keystoneauth1/hacking/checks.py deleted file mode 100644 index 9e4a942..0000000 --- a/keystoneauth1/hacking/checks.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""keystoneauth1's pep8 extensions. - -In order to make the review process faster and easier for core devs we are -adding some keystoneauth1 specific pep8 checks. This will catch common -errors so that core devs don't have to. - -""" - - -import re - - -def check_oslo_namespace_imports(logical_line, blank_before, filename): - oslo_namespace_imports = re.compile( - r"(((from)|(import))\s+oslo\.)|(from\s+oslo\s+import\s+)") - - if re.match(oslo_namespace_imports, logical_line): - msg = ("K333: '%s' must be used instead of '%s'.") % ( - logical_line.replace('oslo.', 'oslo_'), - logical_line) - yield(0, msg) - - -def factory(register): - register(check_oslo_namespace_imports) diff --git a/keystoneauth1/identity/__init__.py b/keystoneauth1/identity/__init__.py deleted file mode 100644 index 18207ce..0000000 --- a/keystoneauth1/identity/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1.identity import base -from keystoneauth1.identity import generic -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import oidc - - -BaseIdentityPlugin = base.BaseIdentityPlugin - -V2Password = v2.Password -"""See :class:`keystoneauth1.identity.v2.Password`""" - -V2Token = v2.Token -"""See :class:`keystoneauth1.identity.v2.Token`""" - -V3Password = v3.Password -"""See :class:`keystoneauth1.identity.v3.Password`""" - -V3Token = v3.Token -"""See :class:`keystoneauth1.identity.v3.Token`""" - -Password = generic.Password -"""See :class:`keystoneauth1.identity.generic.Password`""" - -Token = generic.Token -"""See :class:`keystoneauth1.identity.generic.Token`""" - -V3OidcClientCredentials = oidc.OidcClientCredentials -"""See :class:`keystoneauth1.identity.v3.oidc.OidcClientCredentials`""" - -V3OidcPassword = oidc.OidcPassword -"""See :class:`keystoneauth1.identity.v3.oidc.OidcPassword`""" - -V3OidcAuthorizationCode = oidc.OidcAuthorizationCode -"""See :class:`keystoneauth1.identity.v3.oidc.OidcAuthorizationCode`""" - -V3OidcAccessToken = oidc.OidcAccessToken -"""See :class:`keystoneauth1.identity.v3.oidc.OidcAccessToken`""" - -V3TOTP = v3.TOTP -"""See :class:`keystoneauth1.identity.v3.TOTP`""" - -V3TokenlessAuth = v3.TokenlessAuth -"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`""" - -__all__ = ('BaseIdentityPlugin', - 'Password', - 'Token', - 'V2Password', - 'V2Token', - 'V3Password', - 'V3Token', - 'V3OidcPassword', - 'V3OidcAuthorizationCode', - 'V3OidcAccessToken', - 'V3TOTP', - 'V3TokenlessAuth') diff --git a/keystoneauth1/identity/access.py b/keystoneauth1/identity/access.py deleted file mode 100644 index a4704c0..0000000 --- a/keystoneauth1/identity/access.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional - -from keystoneauth1.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: keystoneauth1.access.AccessInfo - :param auth_url: the url where this AccessInfo was retrieved from. Required - if using the AUTH_INTERFACE with get_endpoint. (optional) - """ - - @positional() - 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 diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py deleted file mode 100644 index 50539f7..0000000 --- a/keystoneauth1/identity/base.py +++ /dev/null @@ -1,507 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 base64 -import hashlib -import json -import threading - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import plugin - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class BaseIdentityPlugin(plugin.BaseAuthPlugin): - - # 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, reauthenticate=True): - - super(BaseIdentityPlugin, self).__init__() - - self.auth_url = auth_url - self.auth_ref = None - self.reauthenticate = reauthenticate - - self._discovery_cache = {} - self._lock = threading.Lock() - - @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 function should not be called independently and is expected to be - invoked via the do_authenticate function. - - This function will be invoked if the AcessInfo 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: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.response.InvalidResponse: - The response returned wasn't appropriate. - :raises keystoneauth1.exceptions.http.HttpError: - An error from an invalid HTTP response. - - :returns: Token access information. - :rtype: :class:`keystoneauth1.access.AccessInfo` - """ - - 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: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.http.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: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :returns: Valid AccessInfo - :rtype: :class:`keystoneauth1.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_data(self, session, service_type=None, interface=None, - region_name=None, service_name=None, allow={}, - allow_version_hack=True, discover_versions=True, - skip_discovery=False, min_version=None, - max_version=None, endpoint_override=None, **kwargs): - """Return a valid endpoint data for a service. - - If a valid token is not present then a new one will be fetched using - the session and kwargs. - - version, min_version and max_version can all be given either as a - string or a tuple. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.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 interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. Can also be - `keystoneauth1.plugin.AUTH_INTERFACE` to indicate - that the auth_url should be used instead of the - value in the catalog. (optional, 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 dict allow: Extra filters to pass when discovering API - versions. (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param bool discover_versions: Whether to get version metadata from - the version discovery document even - if it's not neccessary to fulfill the - major version request. (optional, - defaults to True) - :param bool skip_discovery: Whether to skip version discovery even - if a version has been given. This is useful - if endpoint_override or similar has been - given and grabbing additional information - about the endpoint is not useful. - :param min_version: The minimum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param max_version: The maximum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param str endpoint_override: URL to use instead of looking in the - catalog. Catalog lookup will be skipped, - but version discovery will be run. - Sets allow_version_hack to False - (optional) - :param kwargs: Ignored. - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :return: Valid EndpointData or None if not available. - :rtype: `keystoneauth1.discover.EndpointData` or None - """ - min_version, max_version = discover._normalize_version_args( - None, min_version, max_version) - - # 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 plugin.AUTH_INTERFACE: - endpoint_data = discover.EndpointData( - service_url=self.auth_url, - service_type=service_type or 'identity') - project_id = None - elif endpoint_override: - # TODO(mordred) Make a code path that will look for a - # matching entry in the catalog if the catalog - # exists and fill in the interface, region_name, etc. - # For now, just use any information the use has - # provided. - endpoint_data = discover.EndpointData( - catalog_url=endpoint_override, - interface=interface, - region_name=region_name, - service_name=service_name) - # Setting an endpoint_override then calling get_endpoint_data means - # you absolutely want the discovery info for the URL in question. - # There are no code flows where this will happen for any other - # reasons. - allow_version_hack = False - project_id = self.get_project_id(session) - 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 - - # It's possible for things higher in the stack, because of - # defaults, to explicitly pass None. - if not interface: - interface = 'public' - - service_catalog = self.get_access(session).service_catalog - project_id = self.get_project_id(session) - # NOTE(mordred): service_catalog.url_data_for raises if it can't - # find a match, so this will always be a valid object. - endpoint_data = service_catalog.endpoint_data_for( - service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name) - if not endpoint_data: - return None - - if skip_discovery: - return endpoint_data - - try: - return endpoint_data.get_versioned_data( - session, - project_id=project_id, - min_version=min_version, - max_version=max_version, - cache=self._discovery_cache, - discover_versions=discover_versions, - allow_version_hack=allow_version_hack, allow=allow) - except (exceptions.DiscoveryFailure, - exceptions.HttpError, - exceptions.ConnectionError): - # If a version was requested, we didn't find it, return - # None. - if max_version or min_version: - return None - # If one wasn't, then the endpoint_data we already have - # should be fine - return endpoint_data - - def get_endpoint(self, session, service_type=None, interface=None, - region_name=None, service_name=None, version=None, - allow={}, allow_version_hack=True, - skip_discovery=False, - min_version=None, max_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. - - version, min_version and max_version can all be given either as a - string or a tuple. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.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 interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. Can also be - `keystoneauth1.plugin.AUTH_INTERFACE` to indicate - that the auth_url should be used instead of the - value in the catalog. (optional, 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 version: The minimum version number required for this - endpoint. (optional) - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param bool skip_discovery: Whether to skip version discovery even - if a version has been given. This is useful - if endpoint_override or similar has been - given and grabbing additional information - about the endpoint is not useful. - :param min_version: The minimum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param max_version: The maximum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :return: A valid endpoint URL or None if not available. - :rtype: string or None - """ - # Explode `version` into min_version and max_version - everything below - # here uses the latter rather than the former. - min_version, max_version = discover._normalize_version_args( - version, min_version, max_version) - # Set discover_versions to False since we're only going to return - # a URL. Fetching the microversion data would be needlessly - # expensive in the common case. However, discover_versions=False - # will still run discovery if the version requested is not the - # version in the catalog. - endpoint_data = self.get_endpoint_data( - session, service_type=service_type, interface=interface, - region_name=region_name, service_name=service_name, - allow=allow, min_version=min_version, max_version=max_version, - discover_versions=False, skip_discovery=skip_discovery, - allow_version_hack=allow_version_hack, **kwargs) - return endpoint_data.url if endpoint_data else None - - 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_sp_auth_url(self, session, sp_id, **kwargs): - try: - return self.get_access( - session).service_providers.get_auth_url(sp_id) - except exceptions.ServiceProviderNotFound: - return None - - def get_sp_url(self, session, sp_id, **kwargs): - try: - return self.get_access( - session).service_providers.get_sp_url(sp_id) - except exceptions.ServiceProviderNotFound: - return None - - @positional() - 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: keystoneauth1.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 keystoneauth1.exceptions.discovery.DiscoveryFailure: - if for some reason the lookup fails. - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :returns: A discovery object with the results of looking up that URL. - """ - return discover.get_discovery(session=session, url=url, - cache=self._discovery_cache, - authenticated=authenticated) - - def get_cache_id_elements(self): - """Get the elements for this auth plugin that make it unique. - - As part of the get_cache_id requirement we need to determine what - aspects of this plugin and its values that make up the unique elements. - - This should be overridden by plugins that wish to allow caching. - - :returns: The unique attributes and values of this plugin. - :rtype: A flat dict with a str key and str or None value. This is - required as we feed these values into a hash. Pairs where the - value is None are ignored in the hashed id. - """ - raise NotImplementedError() - - def get_cache_id(self): - """Fetch an identifier that uniquely identifies the auth options. - - The returned identifier need not be decomposable or otherwise provide - any way to recreate the plugin. - - This string MUST change if any of the parameters that are used to - uniquely identity this plugin change. It should not change upon a - reauthentication of the plugin. - - :returns: A unique string for the set of options - :rtype: str or None if this is unsupported or unavailable. - """ - try: - elements = self.get_cache_id_elements() - except NotImplementedError: - return None - - hasher = hashlib.sha256() - - for k, v in sorted(elements.items()): - if v is not None: - # NOTE(jamielennox): in python3 you need to pass bytes to hash - if isinstance(k, six.string_types): - k = k.encode('utf-8') - if isinstance(v, six.string_types): - v = v.encode('utf-8') - - hasher.update(k) - hasher.update(v) - - return base64.b64encode(hasher.digest()).decode('utf-8') - - def get_auth_state(self): - """Retrieve the current authentication state for the plugin. - - Retrieve any internal state that represents the authenticated plugin. - - This should not fetch any new data if it is not present. - - :returns: a string that can be stored or None if there is no auth state - present in the plugin. This string can be reloaded with - set_auth_state to set the same authentication. - :rtype: str or None if no auth present. - """ - if self.auth_ref: - data = {'auth_token': self.auth_ref.auth_token, - 'body': self.auth_ref._data} - - return json.dumps(data) - - def set_auth_state(self, data): - """Install existing authentication state for a plugin. - - Take the output of get_auth_state and install that authentication state - into the current authentication plugin. - """ - if data: - auth_data = json.loads(data) - self.auth_ref = access.create(body=auth_data['body'], - auth_token=auth_data['auth_token']) - else: - self.auth_ref = None diff --git a/keystoneauth1/identity/generic/__init__.py b/keystoneauth1/identity/generic/__init__.py deleted file mode 100644 index df1831e..0000000 --- a/keystoneauth1/identity/generic/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1.identity.generic.base import BaseGenericPlugin # noqa -from keystoneauth1.identity.generic.password import Password # noqa -from keystoneauth1.identity.generic.token import Token # noqa - - -__all__ = ('BaseGenericPlugin', - 'Password', - 'Token', - ) diff --git a/keystoneauth1/identity/generic/base.py b/keystoneauth1/identity/generic/base.py deleted file mode 100644 index 603767e..0000000 --- a/keystoneauth1/identity/generic/base.py +++ /dev/null @@ -1,215 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import abc - -import six -import six.moves.urllib.parse as urlparse - -from keystoneauth1 import _utils as utils -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class BaseGenericPlugin(base.BaseIdentityPlugin): - """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, - default_domain_id=None, - default_domain_name=None, - reauthenticate=True): - super(BaseGenericPlugin, self).__init__(auth_url=auth_url, - reauthenticate=reauthenticate) - - 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._default_domain_id = default_domain_id - self._default_domain_name = default_domain_name - - self._plugin = None - - @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: keystoneauth1.session.Session - :param tuple version: A tuple of the API version at the URL. - :param str url: The base URL for this version. - :param str raw_status: The status that was in the discovery field. - - :returns: A plugin that can match the parameters or None if nothing. - """ - return None - - @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 the parameters that are common to v2 plugins.""" - return {'trust_id': self._trust_id, - 'tenant_id': self._project_id, - 'tenant_name': self._project_name, - 'reauthenticate': self.reauthenticate} - - @property - def _v3_params(self): - """Return the 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, - 'reauthenticate': self.reauthenticate} - - @property - def project_domain_id(self): - return self._project_domain_id or self._default_domain_id - - @project_domain_id.setter - def project_domain_id(self, value): - self._project_domain_id = value - - @property - def project_domain_name(self): - return self._project_domain_name or self._default_domain_name - - @project_domain_name.setter - def project_domain_name(self, value): - self._project_domain_name = value - - 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('Failed to discover available identity versions when ' - 'contacting %s. Attempting to parse version from URL.', - self.auth_url) - - url_parts = urlparse.urlparse(self.auth_url) - path = url_parts.path.lower() - - if path.startswith('/v2.0'): - if self._has_domain_scope: - raise exceptions.DiscoveryFailure( - 'Cannot use v2 authentication with 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: - # NOTE(jamielennox): version_data is always in oldest to newest - # order. This is fine normally because we explicitly skip v2 below - # if there is domain data present. With default_domain params - # though we want a v3 plugin if available and fall back to v2 so we - # have to process in reverse order. FIXME(jamielennox): if we ever - # go for another version we should reverse this logic as we always - # want to favour the newest available version. - reverse = self._default_domain_id or self._default_domain_name - disc_data = disc.version_data(reverse=bool(reverse)) - - v2_with_domain_scope = False - 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. - v2_with_domain_scope = True - continue - - plugin = self.create_plugin(session, - version, - data['url'], - raw_status=data['raw_status']) - - if plugin: - break - if not plugin and v2_with_domain_scope: - raise exceptions.DiscoveryFailure( - 'Cannot use v2 authentication with domain scope') - - if plugin: - return plugin - - # so there were no URLs that i could use for auth of any version. - raise exceptions.DiscoveryFailure('Could not determine a suitable URL ' - 'for the plugin') - - 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) - - def get_cache_id_elements(self, _implemented=False): - # NOTE(jamielennox): implemented here is just a way to make sure that - # something overrides this method. We don't want the base - # implementation to respond with a dict without the subclass modifying - # it to add their own data in case the subclass doesn't support caching - if not _implemented: - raise NotImplemented() - - return {'auth_url': self.auth_url, - '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, - 'trust_id': self._trust_id} diff --git a/keystoneauth1/identity/generic/password.py b/keystoneauth1/identity/generic/password.py deleted file mode 100644 index 4af6411..0000000 --- a/keystoneauth1/identity/generic/password.py +++ /dev/null @@ -1,90 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional - -from keystoneauth1 import discover -from keystoneauth1.identity.generic import base -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 - - -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. - - """ - - @positional() - 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: - 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): - u_domain_id = self._user_domain_id or self._default_domain_id - u_domain_name = self._user_domain_name or self._default_domain_name - - return v3.Password(auth_url=url, - user_id=self._user_id, - username=self._username, - user_domain_id=u_domain_id, - user_domain_name=u_domain_name, - password=self._password, - **self._v3_params) - - @property - def user_domain_id(self): - return self._user_domain_id or self._default_domain_id - - @user_domain_id.setter - def user_domain_id(self, value): - self._user_domain_id = value - - @property - def user_domain_name(self): - return self._user_domain_name or self._default_domain_name - - @user_domain_name.setter - def user_domain_name(self, value): - self._user_domain_name = value - - def get_cache_id_elements(self): - elements = super(Password, self).get_cache_id_elements( - _implemented=True) - elements['username'] = self._username - elements['user_id'] = self._user_id - elements['password'] = self._password - elements['user_domain_id'] = self.user_domain_id - elements['user_domain_name'] = self.user_domain_name - return elements diff --git a/keystoneauth1/identity/generic/token.py b/keystoneauth1/identity/generic/token.py deleted file mode 100644 index 00239b6..0000000 --- a/keystoneauth1/identity/generic/token.py +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1 import discover -from keystoneauth1.identity.generic import base -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 - - -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) - - def get_cache_id_elements(self): - elements = super(Token, self).get_cache_id_elements(_implemented=True) - elements['token'] = self._token - return elements diff --git a/keystoneauth1/identity/v2.py b/keystoneauth1/identity/v2.py deleted file mode 100644 index 933657a..0000000 --- a/keystoneauth1/identity/v2.py +++ /dev/null @@ -1,178 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - -_logger = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class Auth(base.BaseIdentityPlugin): - """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 - """ - - @positional() - 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 - - 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() - except ValueError: - raise exceptions.InvalidResponse(response=resp) - - if 'access' not in resp_data: - 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 - """ - - @property - def has_scope_parameters(self): - """Return true if parameters can be used to create a scoped token.""" - return self.tenant_id or self.tenant_name or self.trust_id - - -_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. - """ - - @positional(4) - 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 - - 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} - - def get_cache_id_elements(self): - return {'username': self.username, - 'user_id': self.user_id, - 'password': self.password, - 'auth_url': self.auth_url, - 'tenant_id': self.tenant_id, - 'tenant_name': self.tenant_name, - 'trust_id': self.trust_id} - - -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 - - def get_auth_data(self, headers=None): - if headers is not None: - headers['X-Auth-Token'] = self.token - return {'token': {'id': self.token}} - - def get_cache_id_elements(self): - return {'token': self.token, - 'auth_url': self.auth_url, - 'tenant_id': self.tenant_id, - 'tenant_name': self.tenant_name, - 'trust_id': self.trust_id} diff --git a/keystoneauth1/identity/v3/__init__.py b/keystoneauth1/identity/v3/__init__.py deleted file mode 100644 index 38e78db..0000000 --- a/keystoneauth1/identity/v3/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1.identity.v3.base import * # noqa -from keystoneauth1.identity.v3.federation import * # noqa -from keystoneauth1.identity.v3.k2k import * # noqa -from keystoneauth1.identity.v3.oidc import * # noqa -from keystoneauth1.identity.v3.password import * # noqa -from keystoneauth1.identity.v3.token import * # noqa -from keystoneauth1.identity.v3.totp import * # noqa -from keystoneauth1.identity.v3.tokenless_auth import * # noqa - - -__all__ = ('Auth', - 'AuthConstructor', - 'AuthMethod', - 'BaseAuth', - - 'FederationBaseAuth', - - 'Keystone2Keystone', - - 'Password', - 'PasswordMethod', - - 'Token', - 'TokenMethod', - - 'OidcAccessToken', - 'OidcAuthorizationCode', - 'OidcClientCredentials', - 'OidcPassword', - - 'TOTPMethod', - 'TOTP', - - 'TokenlessAuth') diff --git a/keystoneauth1/identity/v3/base.py b/keystoneauth1/identity/v3/base.py deleted file mode 100644 index 1f8f8de..0000000 --- a/keystoneauth1/identity/v3/base.py +++ /dev/null @@ -1,282 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - -_logger = utils.get_logger(__name__) - -__all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth') - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuth(base.BaseIdentityPlugin): - """Identity V3 Authentication Plugin. - - :param string auth_url: Identity service endpoint 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 - :param bool include_catalog: Include the service catalog in the returned - token. (optional) default True. - """ - - @positional() - 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 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 - - @property - def has_scope_parameters(self): - """Return true if parameters can be used to create a scoped token.""" - return (self.domain_id or self.domain_name or - self.project_id or self.project_name or - self.trust_id) - - -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(json.dumps(resp.json())) - resp_data = resp.json() - except ValueError: - raise exceptions.InvalidResponse(response=resp) - - if 'token' not in resp_data: - raise exceptions.InvalidResponse(response=resp) - - return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'], - body=resp_data) - - def get_cache_id_elements(self): - if not self.auth_methods: - return None - - params = {'auth_url': self.auth_url, - '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, - 'trust_id': self.trust_id} - - for method in self.auth_methods: - try: - elements = method.get_cache_id_elements() - except NotImplementedError: - return None - - params.update(elements) - - return params - - -@six.add_metaclass(abc.ABCMeta) -class AuthMethod(object): - """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.keys()) - 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: keystoneauth1.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) - """ - - def get_cache_id_elements(self): - """Get the elements for this auth method that make it unique. - - These elements will be used as part of the - :py:meth:`keystoneauth1.plugin.BaseIdentityPlugin.get_cache_id` to - allow caching of the auth plugin. - - Plugins should override this if they want to allow caching of their - state. - - To avoid collision or overrides the keys of the returned dictionary - should be prefixed with the plugin identifier. For example the password - plugin returns its username value as 'password_username'. - """ - raise NotImplementedError() - - -@six.add_metaclass(abc.ABCMeta) -class AuthConstructor(Auth): - """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) diff --git a/keystoneauth1/identity/v3/federation.py b/keystoneauth1/identity/v3/federation.py deleted file mode 100644 index 8f5ff23..0000000 --- a/keystoneauth1/identity/v3/federation.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import abc - -import six - -from keystoneauth1.identity.v3 import base -from keystoneauth1.identity.v3 import token - -__all__ = ('FederationBaseAuth',) - - -@six.add_metaclass(abc.ABCMeta) -class _Rescoped(base.BaseAuth): - """A plugin that is always going to go through a rescope process. - - The original keystone plugins could simply pass a project or domain to - along with the credentials and get a scoped token. For federation, K2K and - newer mechanisms we always get an unscoped token first and then rescope. - - This is currently not public as it's generally an abstraction of a flow - used by plugins within keystoneauth1. - - It also cannot go in base as it depends on token.Token for rescoping which - would create a circular dependency. - """ - - rescoping_plugin = token.Token - - 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: keystoneauth1.session.Session - - :returns: a token data representation - :rtype: :py:class:`keystoneauth1.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.""" - - -class FederationBaseAuth(_Rescoped): - """Federation authentication plugin. - - :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: name of the protocol the client will authenticate - against. - :type protocol: string - - """ - - def __init__(self, auth_url, identity_provider, protocol, **kwargs): - super(FederationBaseAuth, self).__init__(auth_url=auth_url, **kwargs) - self.identity_provider = identity_provider - self.protocol = protocol - - @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 diff --git a/keystoneauth1/identity/v3/k2k.py b/keystoneauth1/identity/v3/k2k.py deleted file mode 100644 index 666fb48..0000000 --- a/keystoneauth1/identity/v3/k2k.py +++ /dev/null @@ -1,173 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 six - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import federation -from keystoneauth1 import plugin - -__all__ = ('Keystone2Keystone',) - - -class Keystone2Keystone(federation._Rescoped): - """Plugin to execute the Keystone to Keyestone authentication flow. - - In this plugin, an ECP wrapped SAML assertion provided by a keystone - Identity Provider (IdP) is used to request an OpenStack unscoped token - from a keystone Service Provider (SP). - - :param base_plugin: Auth plugin already authenticated against the keystone - IdP. - :type base_plugin: keystoneauth1.identity.v3.base.BaseAuth - - :param service_provider: The Service Provider ID as returned by - ServiceProviderManager.list() - :type service_provider: str - - """ - - REQUEST_ECP_URL = '/auth/OS-FEDERATION/saml2/ecp' - """Path where the ECP wrapped SAML assertion should be presented to the - Keystone Service Provider.""" - - HTTP_MOVED_TEMPORARILY = 302 - HTTP_SEE_OTHER = 303 - - def __init__(self, base_plugin, service_provider, **kwargs): - super(Keystone2Keystone, self).__init__(auth_url=None, **kwargs) - - self._local_cloud_plugin = base_plugin - self._sp_id = service_provider - - @classmethod - def _remote_auth_url(cls, auth_url): - """Return auth_url of the remote Keystone Service Provider. - - Remote cloud's auth_url is an endpoint for getting federated unscoped - token, typically that would be - ``https://remote.example.com:5000/v3/OS-FEDERATION/identity_providers/ - /protocols//auth``. However we need to generate a - real auth_url, used for token scoping. This function assumes there are - static values today in the remote auth_url stored in the Service - Provider attribute and those can be used as a delimiter. If the - sp_auth_url doesn't comply with standard federation auth url the - function will simply return whole string. - - :param auth_url: auth_url of the remote cloud - :type auth_url: str - - :returns: auth_url of remote cloud where a token can be validated or - scoped. - :rtype: str - - """ - PATTERN = '/OS-FEDERATION/' - idx = auth_url.index(PATTERN) if PATTERN in auth_url else len(auth_url) - return auth_url[:idx] - - def _get_ecp_assertion(self, session): - body = { - 'auth': { - 'identity': { - 'methods': ['token'], - 'token': { - 'id': self._local_cloud_plugin.get_token(session) - } - }, - 'scope': { - 'service_provider': { - 'id': self._sp_id - } - } - } - } - - endpoint_filter = {'version': (3, 0), - 'interface': plugin.AUTH_INTERFACE} - - headers = {'Accept': 'application/json'} - - resp = session.post(self.REQUEST_ECP_URL, - json=body, - auth=self._local_cloud_plugin, - endpoint_filter=endpoint_filter, - headers=headers, - authenticated=False, - raise_exc=False) - - # NOTE(marek-denis): I am not sure whether disabling exceptions in the - # Session object and testing if resp.ok is sufficient. An alternative - # would be catching locally all exceptions and reraising with custom - # warning. - if not resp.ok: - msg = ("Error while requesting ECP wrapped assertion: response " - "exit code: %(status_code)d, reason: %(err)s") - msg = msg % {'status_code': resp.status_code, 'err': resp.reason} - raise exceptions.AuthorizationFailure(msg) - - if not resp.text: - raise exceptions.InvalidResponse(resp) - - return six.text_type(resp.text) - - def _send_service_provider_ecp_authn_response(self, session, sp_url, - sp_auth_url): - """Present ECP wrapped SAML assertion to the keystone SP. - - The assertion is issued by the keystone IdP and it is targeted to the - keystone that will serve as Service Provider. - - :param session: a session object to send out HTTP requests. - - :param sp_url: URL where the ECP wrapped SAML assertion will be - presented to the keystone SP. Usually, something like: - https://sp.com/Shibboleth.sso/SAML2/ECP - :type sp_url: str - - :param sp_auth_url: Federated authentication URL of the keystone SP. - It is specified by IdP, for example: - https://sp.com/v3/OS-FEDERATION/identity_providers/ - idp_id/protocols/protocol_id/auth - :type sp_auth_url: str - - """ - response = session.post( - sp_url, - headers={'Content-Type': 'application/vnd.paos+xml'}, - data=self._get_ecp_assertion(session), - 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. - if response.status_code in (self.HTTP_MOVED_TEMPORARILY, - self.HTTP_SEE_OTHER): - response = session.get( - sp_auth_url, - headers={'Content-Type': 'application/vnd.paos+xml'}, - authenticated=False) - - return response - - def get_unscoped_auth_ref(self, session, **kwargs): - sp_auth_url = self._local_cloud_plugin.get_sp_auth_url( - session, self._sp_id) - sp_url = self._local_cloud_plugin.get_sp_url(session, self._sp_id) - self.auth_url = self._remote_auth_url(sp_auth_url) - - response = self._send_service_provider_ecp_authn_response( - session, sp_url, sp_auth_url) - return access.create(resp=response) diff --git a/keystoneauth1/identity/v3/oidc.py b/keystoneauth1/identity/v3/oidc.py deleted file mode 100644 index 08c4a36..0000000 --- a/keystoneauth1/identity/v3/oidc.py +++ /dev/null @@ -1,480 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import federation - -_logger = utils.get_logger(__name__) - -__all__ = ('OidcAuthorizationCode', - 'OidcClientCredentials', - 'OidcPassword', - 'OidcAccessToken') - - -@six.add_metaclass(abc.ABCMeta) -class _OidcBase(federation.FederationBaseAuth): - """Base class for different OpenID Connect based flows. - - The OpenID Connect specification can be found at:: - ``http://openid.net/specs/openid-connect-core-1_0.html`` - """ - - grant_type = None - - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_type, - scope="openid profile", - access_token_endpoint=None, - discovery_endpoint=None, - grant_type=None, - **kwargs): - """The OpenID Connect plugin 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 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_type: OAuth 2.0 Authorization Server Introspection - token type, it is used to decide which type - of token will be used when processing token - introspection. Valid values are: - "access_token" or "id_token" - :type access_token_type: string - - :param access_token_endpoint: OpenID Connect Provider Token Endpoint, - for example: - https://localhost:8020/oidc/OP/token - Note that if a discovery document is - provided this value will override - the discovered one. - :type access_token_endpoint: string - - :param discovery_endpoint: OpenID Connect Discovery Document URL, - for example: - https://localhost:8020/oidc/.well-known/openid-configuration - :type access_token_endpoint: string - - :param scope: OpenID Connect scope that is requested from OP, - for example: "openid profile email", defaults to - "openid profile". Note that OpenID Connect specification - states that "openid" must be always specified. - :type scope: string - """ - super(_OidcBase, self).__init__(auth_url, identity_provider, protocol, - **kwargs) - self.client_id = client_id - self.client_secret = client_secret - - self.discovery_endpoint = discovery_endpoint - self._discovery_document = {} - self.access_token_endpoint = access_token_endpoint - - self.access_token_type = access_token_type - self.scope = scope - - if grant_type is not None: - if grant_type != self.grant_type: - raise exceptions.OidcGrantTypeMissmatch() - warnings.warn("Passing grant_type as an argument has been " - "deprecated as it is now defined in the plugin " - "itself. You should stop passing this argument " - "to the plugin, as it will be ignored, since you " - "cannot pass a free text string as a grant_type. " - "This argument will be dropped from the plugin in " - "July 2017 or with the next major release of " - "keystoneauth (3.0.0)", - DeprecationWarning) - - def _get_discovery_document(self, session): - """Get the contents of the OpenID Connect Discovery Document. - - This method grabs the contents of the OpenID Connect Discovery Document - if a discovery_endpoint was passed to the constructor and returns it as - a dict, otherwise returns an empty dict. Note that it will fetch the - discovery document only once, so subsequent calls to this method will - return the cached result, if any. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the discovery document if any, - otherwise it will return an empty dict. - :rtype: dict - """ - if (self.discovery_endpoint is not None and - not self._discovery_document): - try: - resp = session.get(self.discovery_endpoint, - authenticated=False) - except exceptions.HttpError: - _logger.error("Cannot fetch discovery document %(discovery)s" % - {"discovery": self.discovery_endpoint}) - raise - - try: - self._discovery_document = resp.json() - except Exception: - pass - - if not self._discovery_document: - raise exceptions.InvalidOidcDiscoveryDocument() - - return self._discovery_document - - def _get_access_token_endpoint(self, session): - """Get the "token_endpoint" for the OpenID Connect flow. - - This method will return the correct access token endpoint to be used. - If the user has explicitly passed an access_token_endpoint to the - constructor that will be returned. If there is no explicit endpoint and - a discovery url is provided, it will try to get it from the discovery - document. If nothing is found, an exception will be raised. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :return: the endpoint to use - :rtype: string or None if no endpoint is found - """ - if self.access_token_endpoint is not None: - return self.access_token_endpoint - - discovery = self._get_discovery_document(session) - endpoint = discovery.get("token_endpoint") - if endpoint is None: - raise exceptions.OidcAccessTokenEndpointNotFound() - return endpoint - - def _get_access_token(self, session, payload): - """Exchange a variety of user supplied values for an access token. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :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 - """ - client_auth = (self.client_id, self.client_secret) - access_token_endpoint = self._get_access_token_endpoint(session) - - op_response = session.post(access_token_endpoint, - requests_auth=client_auth, - data=payload, - authenticated=False) - access_token = op_response.json()[self.access_token_type] - return access_token - - def _get_keystone_token(self, session, access_token): - r"""Exchange an access 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: keystoneauth1.session.Session - - :param access_token: The OpenID Connect access token. - :type access_token: str - """ - # use access token against protected URL - headers = {'Authorization': 'Bearer ' + access_token} - auth_response = session.post(self.federated_token_url, - headers=headers, - authenticated=False) - return auth_response - - def get_unscoped_auth_ref(self, session): - """Authenticate with OpenID Connect and get back claims. - - This is a multi-step process: - - 1.- An access token must be retrieved from the server. In order to do - so, we need to exchange an authorization grant or refresh token - with the token endpoint in order to obtain an access token. The - authorization grant varies from plugin to plugin. - - 2.- 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: keystoneauth1.session.Session - - :returns: a token data representation - :rtype: :py:class:`keystoneauth1.access.AccessInfoV3` - """ - # First of all, check if the grant type is supported - discovery = self._get_discovery_document(session) - grant_types = discovery.get("grant_types_supported") - if (grant_types and - self.grant_type is not None and - self.grant_type not in grant_types): - raise exceptions.OidcPluginNotSupported() - - # Get the payload - payload = self.get_payload(session) - payload.setdefault('grant_type', self.grant_type) - - # get an access token - access_token = self._get_access_token(session, payload) - - response = self._get_keystone_token(session, access_token) - - # grab the unscoped token - return access.create(resp=response) - - @abc.abstractmethod - def get_payload(self, session): - """Get the plugin specific payload for obtainin an access token. - - OpenID Connect supports different grant types. This method should - prepare the payload that needs to be exchanged with the server in - order to get an access token for the particular grant type that the - plugin is implementing. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - raise NotImplementedError() - - -class OidcPassword(_OidcBase): - """Implementation for OpenID Connect Resource Owner Password Credential.""" - - grant_type = "password" - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - username=None, password=None, - **kwargs): - """The OpenID Password plugin expects the following. - - :param username: Username used to authenticate - :type username: string - - :param password: Password used to authenticate - :type password: string - """ - super(OidcPassword, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - self.username = username - self.password = password - - def get_payload(self, session): - """Get an authorization grant for the "password" grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'username': self.username, - 'password': self.password, - 'scope': self.scope} - return payload - - -class OidcClientCredentials(_OidcBase): - """Implementation for OpenID Connect Client Credentials.""" - - grant_type = 'client_credentials' - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - **kwargs): - """The OpenID Client Credentials expects the following. - - :param client_id: Client ID used to authenticate - :type username: string - - :param client_secret: Client Secret used to authenticate - :type password: string - """ - super(OidcClientCredentials, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - - def get_payload(self, session): - """Get an authorization grant for the client credentials grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'scope': self.scope} - return payload - - -class OidcAuthorizationCode(_OidcBase): - """Implementation for OpenID Connect Authorization Code.""" - - grant_type = 'authorization_code' - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - redirect_uri=None, code=None, **kwargs): - """The OpenID Authorization Code plugin expects the following. - - :param redirect_uri: OpenID Connect Client Redirect URL - :type redirect_uri: string - - :param code: OAuth 2.0 Authorization Code - :type code: string - - """ - super(OidcAuthorizationCode, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - self.redirect_uri = redirect_uri - self.code = code - - def get_payload(self, session): - """Get an authorization grant for the "authorization_code" grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'redirect_uri': self.redirect_uri, 'code': self.code} - - return payload - - -class OidcAccessToken(_OidcBase): - """Implementation for OpenID Connect access token reuse.""" - - @positional(5) - def __init__(self, auth_url, identity_provider, protocol, - access_token, **kwargs): - """The OpenID Connect plugin based on the Access Token. - - 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 access_token: OpenID Connect Access token - :type access_token: string - """ - super(OidcAccessToken, self).__init__(auth_url, identity_provider, - protocol, - client_id=None, - client_secret=None, - access_token_endpoint=None, - access_token_type=None, - **kwargs) - self.access_token = access_token - - def get_payload(self, session): - """OidcAccessToken does not require a payload.""" - return {} - - def get_unscoped_auth_ref(self, session): - """Authenticate with OpenID Connect and get back claims. - - We 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:`keystoneauth1.access.AccessInfoV3` - """ - response = self._get_keystone_token(session, self.access_token) - return access.create(resp=response) diff --git a/keystoneauth1/identity/v3/password.py b/keystoneauth1/identity/v3/password.py deleted file mode 100644 index beb69b6..0000000 --- a/keystoneauth1/identity/v3/password.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1.identity.v3 import base - - -__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} - - def get_cache_id_elements(self): - return dict(('password_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -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 diff --git a/keystoneauth1/identity/v3/token.py b/keystoneauth1/identity/v3/token.py deleted file mode 100644 index c959d11..0000000 --- a/keystoneauth1/identity/v3/token.py +++ /dev/null @@ -1,54 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from keystoneauth1.identity.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} - - def get_cache_id_elements(self): - return {'token_token': 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) diff --git a/keystoneauth1/identity/v3/tokenless_auth.py b/keystoneauth1/identity/v3/tokenless_auth.py deleted file mode 100644 index 0e7e7f7..0000000 --- a/keystoneauth1/identity/v3/tokenless_auth.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import abc -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import plugin - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class TokenlessAuth(plugin.BaseAuthPlugin): - """A plugin for authenticating with Tokenless Auth. - - This is for Tokenless Authentication. Scoped information - like domain name and project ID will be passed in the headers and - token validation request will be authenticated based on - the provided HTTPS certificate along with the scope information. - """ - - def __init__(self, auth_url, - domain_id=None, - domain_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None): - """A init method for TokenlessAuth. - - :param string auth_url: Identity service endpoint for authentication. - The URL must include a version or any request - will result in a 404 NotFound error. - :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. - """ - self.auth_url = auth_url - 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 - - def get_headers(self, session, **kwargs): - """Fetch authentication headers for message. - - This is to override the default get_headers method to provide - tokenless auth scope headers if token is not provided in the - session. - - :param session: The session object that the auth_plugin belongs to. - :type session: keystoneauth1.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 - """ - scope_headers = {} - if self.project_id: - scope_headers['X-Project-Id'] = self.project_id - elif self.project_name: - scope_headers['X-Project-Name'] = self.project_name - if self.project_domain_id: - scope_headers['X-Project-Domain-Id'] = ( - self.project_domain_id) - elif self.project_domain_name: - scope_headers['X-Project-Domain-Name'] = ( - self.project_domain_name) - else: - LOG.warning( - 'Neither Project Domain ID nor Project Domain Name was ' - 'provided.') - return None - elif self.domain_id: - scope_headers['X-Domain-Id'] = self.domain_id - elif self.domain_name: - scope_headers['X-Domain-Name'] = self.domain_name - else: - LOG.warning( - 'Neither Project nor Domain scope was provided.') - return None - return scope_headers - - def get_endpoint(self, session, service_type=None, **kwargs): - """Return a valid endpoint for a service. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.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. - :return: A valid endpoint URL or None if not available. - :rtype: string or None - """ - if (service_type is plugin.AUTH_INTERFACE - or service_type.lower() == 'identity'): - return self.auth_url - - return None diff --git a/keystoneauth1/identity/v3/totp.py b/keystoneauth1/identity/v3/totp.py deleted file mode 100644 index ac0a754..0000000 --- a/keystoneauth1/identity/v3/totp.py +++ /dev/null @@ -1,81 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import copy - -from keystoneauth1.identity.v3 import base - - -__all__ = ('TOTPMethod', 'TOTP') - - -class TOTPMethod(base.AuthMethod): - """Construct a User/Passcode based authentication method. - - :param string passcode: TOTP passcode 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', - 'passcode'] - - def get_auth_data(self, session, auth, headers, **kwargs): - user = {'passcode': self.passcode} - - 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 'totp', {'user': user} - - def get_cache_id_elements(self): - # NOTE(gyee): passcode is not static so we cannot use it as part of - # the key in caching. - params = copy.copy(self._method_parameters) - params.remove('passcode') - return dict(('totp_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -class TOTP(base.AuthConstructor): - """A plugin for authenticating with a username and TOTP passcode. - - :param string auth_url: Identity service endpoint for authentication. - :param string passcode: TOTP passcode 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 = TOTPMethod diff --git a/keystoneauth1/loading/__init__.py b/keystoneauth1/loading/__init__.py deleted file mode 100644 index e31b2f4..0000000 --- a/keystoneauth1/loading/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.loading import adapter -from keystoneauth1.loading.base import * # noqa -from keystoneauth1.loading import cli -from keystoneauth1.loading import conf -from keystoneauth1.loading.identity import * # noqa -from keystoneauth1.loading.opts import * # noqa -from keystoneauth1.loading import session - - -register_auth_argparse_arguments = cli.register_argparse_arguments -load_auth_from_argparse_arguments = cli.load_from_argparse_arguments - -get_auth_common_conf_options = conf.get_common_conf_options -get_auth_plugin_conf_options = conf.get_plugin_conf_options -register_auth_conf_options = conf.register_conf_options -load_auth_from_conf_options = conf.load_from_conf_options - -register_session_argparse_arguments = session.register_argparse_arguments -load_session_from_argparse_arguments = session.load_from_argparse_arguments -register_session_conf_options = session.register_conf_options -load_session_from_conf_options = session.load_from_conf_options -get_session_conf_options = session.get_conf_options - -register_adapter_argparse_arguments = adapter.register_argparse_arguments -register_service_adapter_argparse_arguments = ( - adapter.register_service_argparse_arguments) -register_adapter_conf_options = adapter.register_conf_options -load_adapter_from_conf_options = adapter.load_from_conf_options -get_adapter_conf_options = adapter.get_conf_options - - -__all__ = ( - # loading.base - 'BaseLoader', - 'get_available_plugin_names', - 'get_available_plugin_loaders', - 'get_plugin_loader', - 'PLUGIN_NAMESPACE', - - # loading.identity - 'BaseIdentityLoader', - 'BaseV2Loader', - 'BaseV3Loader', - 'BaseFederationLoader', - 'BaseGenericLoader', - - # auth cli - 'register_auth_argparse_arguments', - 'load_auth_from_argparse_arguments', - - # auth conf - 'get_auth_common_conf_options', - 'get_auth_plugin_conf_options', - 'register_auth_conf_options', - 'load_auth_from_conf_options', - - # session - 'register_session_argparse_arguments', - 'load_session_from_argparse_arguments', - 'register_session_conf_options', - 'load_session_from_conf_options', - 'get_session_conf_options', - - # adapter - 'register_adapter_argparse_arguments', - 'register_service_adapter_argparse_arguments', - 'register_adapter_conf_options', - 'load_adapter_from_conf_options', - 'get_adapter_conf_options', - - # loading.opts - 'Opt', -) diff --git a/keystoneauth1/loading/_plugins/__init__.py b/keystoneauth1/loading/_plugins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/loading/_plugins/admin_token.py b/keystoneauth1/loading/_plugins/admin_token.py deleted file mode 100644 index 3aba63d..0000000 --- a/keystoneauth1/loading/_plugins/admin_token.py +++ /dev/null @@ -1,46 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 loading -from keystoneauth1 import token_endpoint - - -class AdminToken(loading.BaseLoader): - """Use an existing token and a known endpoint to perform requests. - - This plugin is primarily useful for development or for use with identity - service ADMIN tokens. Because this token is used directly there is no - fetching a service catalog or determining scope information and so it - cannot be used by clients that expect use this scope information. - - Because there is no service catalog the endpoint that is supplied with - initialization is used for all operations performed with this plugin so - must be the full base URL to an actual service. - """ - - @property - def plugin_class(self): - return token_endpoint.Token - - def get_options(self): - options = super(AdminToken, self).get_options() - - options.extend([ - loading.Opt('endpoint', - deprecated=[loading.Opt('url')], - help='The endpoint that will always be used'), - loading.Opt('token', - secret=True, - help='The token that will always be used'), - ]) - - return options diff --git a/keystoneauth1/loading/_plugins/identity/__init__.py b/keystoneauth1/loading/_plugins/identity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/loading/_plugins/identity/generic.py b/keystoneauth1/loading/_plugins/identity/generic.py deleted file mode 100644 index b6c139c..0000000 --- a/keystoneauth1/loading/_plugins/identity/generic.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 identity -from keystoneauth1 import loading - - -class Token(loading.BaseGenericLoader): - """Given an existing token rescope it to another target. - - This plugin uses the Identity service's rescope mechanism to get a new - token based upon an existing token. Because an auth plugin requires a - service catalog and scope information it is often easier to fetch a new - token based on an existing one than validate and reuse the one you already - have. - - As a generic plugin this plugin is identity version independent and will - discover available versions before use. This means it expects to be - providen an unversioned URL to operate against. - """ - - @property - def plugin_class(self): - return identity.Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', secret=True, - help='Token to authenticate with'), - ]) - - return options - - -class Password(loading.BaseGenericLoader): - """Authenticate via a username and password. - - Authenticate to the identity service using an inbuilt username and - password. This is the standard and most common form of authentication. - - As a generic plugin this plugin is identity version independent and will - discover available versions before use. This means it expects to be - providen an unversioned URL to operate against. - """ - - @property - def plugin_class(self): - return identity.Password - - def get_options(cls): - options = super(Password, cls).get_options() - options.extend([ - loading.Opt('user-id', help='User id'), - loading.Opt('username', - help='Username', - deprecated=[loading.Opt('user-name')]), - loading.Opt('user-domain-id', help="User's domain id"), - loading.Opt('user-domain-name', help="User's domain name"), - loading.Opt('password', - secret=True, - prompt='Password: ', - help="User's password"), - ]) - return options diff --git a/keystoneauth1/loading/_plugins/identity/v2.py b/keystoneauth1/loading/_plugins/identity/v2.py deleted file mode 100644 index aaacd15..0000000 --- a/keystoneauth1/loading/_plugins/identity/v2.py +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 identity -from keystoneauth1 import loading - - -class Token(loading.BaseV2Loader): - - @property - def plugin_class(self): - return identity.V2Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', secret=True, help='Token'), - ]) - - return options - - -class Password(loading.BaseV2Loader): - - @property - def plugin_class(self): - return identity.V2Password - - def get_options(self): - options = super(Password, self).get_options() - - options.extend([ - loading.Opt('username', - deprecated=[loading.Opt('user-name')], - help='Username to login with'), - loading.Opt('user-id', help='User ID to login with'), - loading.Opt('password', - secret=True, - prompt='Password: ', - help='Password to use'), - ]) - - return options diff --git a/keystoneauth1/loading/_plugins/identity/v3.py b/keystoneauth1/loading/_plugins/identity/v3.py deleted file mode 100644 index cc0ae1f..0000000 --- a/keystoneauth1/loading/_plugins/identity/v3.py +++ /dev/null @@ -1,256 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 identity -from keystoneauth1 import loading - - -def _add_common_identity_options(options): - options.extend([ - loading.Opt('user-id', help='User ID'), - loading.Opt('username', - help='Username', - deprecated=[loading.Opt('user-name')]), - loading.Opt('user-domain-id', help="User's domain id"), - loading.Opt('user-domain-name', help="User's domain name"), - ]) - - -def _assert_identity_options(options): - if (options.get('username') and - not (options.get('user_domain_name') or - options.get('user_domain_id'))): - m = "You have provided a username. In the V3 identity API a " \ - "username is only unique within a domain so you must " \ - "also provide either a user_domain_id or user_domain_name." - raise exceptions.OptionError(m) - - -class Password(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3Password - - def get_options(self): - options = super(Password, self).get_options() - _add_common_identity_options(options) - - options.extend([ - loading.Opt('password', - secret=True, - prompt='Password: ', - help="User's password"), - ]) - - return options - - def load_from_options(self, **kwargs): - _assert_identity_options(kwargs) - - return super(Password, self).load_from_options(**kwargs) - - -class Token(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', - secret=True, - help='Token to authenticate with'), - ]) - - return options - - -class _OpenIDConnectBase(loading.BaseFederationLoader): - - def load_from_options(self, **kwargs): - if not (kwargs.get('access_token_endpoint') or - kwargs.get('discovery_endpoint')): - m = ("You have to specify either an 'access-token-endpoint' or " - "a 'discovery-endpoint'.") - raise exceptions.OptionError(m) - - return super(_OpenIDConnectBase, self).load_from_options(**kwargs) - - def get_options(self): - options = super(_OpenIDConnectBase, self).get_options() - - options.extend([ - loading.Opt('client-id', help='OAuth 2.0 Client ID'), - loading.Opt('client-secret', secret=True, - help='OAuth 2.0 Client Secret'), - loading.Opt('openid-scope', default="openid profile", - dest="scope", - help='OpenID Connect scope that is requested from ' - 'authorization server. Note that the OpenID ' - 'Connect specification states that "openid" ' - 'must be always specified.'), - loading.Opt('access-token-endpoint', - help='OpenID Connect Provider Token Endpoint. Note ' - 'that if a discovery document is being passed this ' - 'option will override the endpoint provided by the ' - 'server in the discovery document.'), - loading.Opt('discovery-endpoint', - help='OpenID Connect Discovery Document URL. ' - 'The discovery document will be used to obtain the ' - 'values of the access token endpoint and the ' - 'authentication endpoint. This URL should look like ' - 'https://idp.example.org/.well-known/' - 'openid-configuration'), - loading.Opt('access-token-type', - help='OAuth 2.0 Authorization Server Introspection ' - 'token type, it is used to decide which type ' - 'of token will be used when processing token ' - 'introspection. Valid values are: ' - '"access_token" or "id_token"'), - ]) - - return options - - -class OpenIDConnectClientCredentials(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcClientCredentials - - def get_options(self): - options = super(OpenIDConnectClientCredentials, self).get_options() - - return options - - -class OpenIDConnectPassword(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcPassword - - def get_options(self): - options = super(OpenIDConnectPassword, self).get_options() - - options.extend([ - loading.Opt('username', help='Username', required=True), - loading.Opt('password', secret=True, - help='Password', required=True), - ]) - - return options - - -class OpenIDConnectAuthorizationCode(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcAuthorizationCode - - def get_options(self): - options = super(OpenIDConnectAuthorizationCode, self).get_options() - - options.extend([ - loading.Opt('redirect-uri', help='OpenID Connect Redirect URL'), - loading.Opt('code', secret=True, required=True, - deprecated=[loading.Opt('authorization-code')], - help='OAuth 2.0 Authorization Code'), - ]) - - return options - - -class OpenIDConnectAccessToken(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return identity.V3OidcAccessToken - - def get_options(self): - options = super(OpenIDConnectAccessToken, self).get_options() - - options.extend([ - loading.Opt('access-token', secret=True, required=True, - help='OAuth 2.0 Access Token'), - ]) - return options - - -class TOTP(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3TOTP - - def get_options(self): - options = super(TOTP, self).get_options() - _add_common_identity_options(options) - - options.extend([ - loading.Opt('passcode', secret=True, help="User's TOTP passcode"), - ]) - - return options - - def load_from_options(self, **kwargs): - _assert_identity_options(kwargs) - - return super(TOTP, self).load_from_options(**kwargs) - - -class TokenlessAuth(loading.BaseLoader): - - @property - def plugin_class(self): - return identity.V3TokenlessAuth - - def get_options(self): - options = super(TokenlessAuth, self).get_options() - - options.extend([ - loading.Opt('auth-url', required=True, - help='Authentication URL'), - loading.Opt('domain-id', help='Domain ID to scope to'), - loading.Opt('domain-name', help='Domain name to scope to'), - loading.Opt('project-id', help='Project ID to scope to'), - loading.Opt('project-name', help='Project name to scope to'), - loading.Opt('project-domain-id', - help='Domain ID containing project'), - loading.Opt('project-domain-name', - help='Domain name containing project'), - ]) - - return options - - def load_from_options(self, **kwargs): - if (not kwargs.get('domain_id') and - not kwargs.get('domain_name') and - not kwargs.get('project_id') and - not kwargs.get('project_name') or - (kwargs.get('project_name') and - not (kwargs.get('project_domain_name') or - kwargs.get('project_domain_id')))): - m = ('You need to provide either a domain_name, domain_id, ' - 'project_id or project_name. ' - 'If you have provided a project_name, in the V3 identity ' - 'API a project_name is only unique within a domain so ' - 'you must also provide either a project_domain_id or ' - 'project_domain_name.') - raise exceptions.OptionError(m) - - return super(TokenlessAuth, self).load_from_options(**kwargs) diff --git a/keystoneauth1/loading/_plugins/noauth.py b/keystoneauth1/loading/_plugins/noauth.py deleted file mode 100644 index ea75f5b..0000000 --- a/keystoneauth1/loading/_plugins/noauth.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 loading -from keystoneauth1 import noauth - - -class NoAuth(loading.BaseLoader): - """Use no tokens to perform requests. - - This must be used together with adapter.Adapter.endpoint_override - to instantiate clients for services deployed in noauth/standalone mode. - - There is no fetching a service catalog or determining scope information - and so it cannot be used by clients that expect use this scope information. - - """ - - @property - def plugin_class(self): - return noauth.NoAuth - - def get_options(self): - return [] diff --git a/keystoneauth1/loading/_utils.py b/keystoneauth1/loading/_utils.py deleted file mode 100644 index 2cb796c..0000000 --- a/keystoneauth1/loading/_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -cfg = None -_NOT_FOUND = object() - - -def get_oslo_config(): - """Runtime load the oslo.config object. - - In performance optimization of openstackclient it was determined that even - optimistically loading oslo.config if available had a performance cost. - Given that we used to only raise the ImportError when the function was - called also attempt to do the import to do everything at runtime. - """ - global cfg - - # First Call - if not cfg: - try: - from oslo_config import cfg - except ImportError: - cfg = _NOT_FOUND - - if cfg is _NOT_FOUND: - raise ImportError("oslo.config is not an automatic dependency of " - "keystoneauth. If you wish to use oslo.config " - "you need to import it into your application's " - "requirements file. ") - - return cfg diff --git a/keystoneauth1/loading/adapter.py b/keystoneauth1/loading/adapter.py deleted file mode 100644 index 24b1055..0000000 --- a/keystoneauth1/loading/adapter.py +++ /dev/null @@ -1,209 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 adapter -from keystoneauth1.loading import _utils -from keystoneauth1.loading import base - - -__all__ = ('register_argparse_arguments', - 'register_service_argparse_arguments', - 'register_conf_options', - 'load_from_conf_options', - 'get_conf_options') - - -class Adapter(base.BaseLoader): - - @property - def plugin_class(self): - return adapter.Adapter - - def get_options(self): - return [] - - @staticmethod - def get_conf_options(): - """Get oslo_config options that are needed for a :py:class:`.Adapter`. - - 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: - :service_type: The default service_type for URL discovery. - :service_name: The default service_name for URL discovery. - :interface: The default interface for URL discovery. - (deprecated) - :valid_interfaces: List of acceptable interfaces for URL - discovery. Can be a list of any of - 'public', 'internal' or 'admin'. - :region_name: The default region_name for URL discovery. - :endpoint_override: Always use this endpoint URL for requests - for this client. - :version: The minimum version restricted to a given Major - API. Mutually exclusive with min_version and - max_version. - :min_version: The minimum major version of a given API, - intended to be used as the lower bound of a - range with max_version. Mutually exclusive with - version. If min_version is given with no - max_version it is as if max version is - 'latest'. - :max_version: The maximum major version of a given API, - intended to be used as the upper bound of a - range with min_version. Mutually exclusive with - version. - - :returns: A list of oslo_config options. - """ - cfg = _utils.get_oslo_config() - - return [cfg.StrOpt('service-type', - help='The default service_type for endpoint URL ' - 'discovery.'), - cfg.StrOpt('service-name', - help='The default service_name for endpoint URL ' - 'discovery.'), - cfg.StrOpt('interface', - help='The default interface for endpoint URL ' - 'discovery.', - deprecated_for_removal=True, - deprecated_reason='Using valid-interfaces is' - ' preferrable because it is' - ' capable of accepting a list of' - ' possible interfaces.'), - cfg.ListOpt('valid-interfaces', - help='List of interfaces, in order of preference, ' - 'for endpoint URL.'), - cfg.StrOpt('region-name', - help='The default region_name for endpoint URL ' - 'discovery.'), - cfg.StrOpt('endpoint-override', - help='Always use this endpoint URL for requests ' - 'for this client.'), - cfg.StrOpt('version', - help='Minimum Major API version within a given ' - 'Major API version for endpoint URL ' - 'discovery. Mutually exclusive with ' - 'min_version and max_version'), - cfg.StrOpt('min-version', - help='The minimum major version of a given API, ' - 'intended to be used as the lower bound of a ' - 'range with max_version. Mutually exclusive ' - 'with version. If min_version is given with ' - 'no max_version it is as if max version is ' - '"latest".'), - cfg.StrOpt('max-version', - help='The maximum major version of a given API, ' - 'intended to be used as the upper bound of a ' - 'range with min_version. Mutually exclusive ' - 'with version.'), - ] - - def register_conf_options(self, conf, group): - """Register the oslo_config options that are needed for an Adapter. - - The options that are set are: - :service_type: The default service_type for URL discovery. - :service_name: The default service_name for URL discovery. - :interface: The default interface for URL discovery. - (deprecated) - :valid_interfaces: List of acceptable interfaces for URL - discovery. Can be a list of any of - 'public', 'internal' or 'admin'. - :region_name: The default region_name for URL discovery. - :endpoint_override: Always use this endpoint URL for requests - for this client. - :version: The minimum version restricted to a given Major - API. Mutually exclusive with min_version and - max_version. - :min_version: The minimum major version of a given API, - intended to be used as the lower bound of a - range with max_version. Mutually exclusive with - version. If min_version is given with no - max_version it is as if max version is - 'latest'. - :max_version: The maximum major version of a given API, - intended to be used as the upper bound of a - range with min_version. Mutually exclusive with - version. - - :param oslo_config.Cfg conf: config object to register with. - :param string group: The ini group to register options in. - :returns: The list of options that was registered. - """ - opts = self.get_conf_options() - conf.register_group(_utils.get_oslo_config().OptGroup(group)) - conf.register_opts(opts, group=group) - return opts - - def load_from_conf_options(self, conf, group, **kwargs): - """Create an Adapter 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 Adapter - construction. - :returns: A new Adapter object. - :rtype: :py:class:`.Adapter` - """ - c = conf[group] - - if c.valid_interfaces and c.interface: - raise TypeError("interface and valid_interfaces are mutually" - " exclusive. Please use valid_interfaces.") - if c.valid_interfaces: - for iface in c.valid_interfaces: - if iface not in ('public', 'internal', 'admin'): - raise TypeError("'{iface}' is not a valid value for" - " valid_interfaces. Valid valies are" - " public, internal or admin") - kwargs.setdefault('interface', c.valid_interfaces) - else: - kwargs.setdefault('interface', c.interface) - kwargs.setdefault('service_type', c.service_type) - kwargs.setdefault('service_name', c.service_name) - kwargs.setdefault('region_name', c.region_name) - kwargs.setdefault('endpoint_override', c.endpoint_override) - kwargs.setdefault('version', c.version) - kwargs.setdefault('min_version', c.min_version) - kwargs.setdefault('max_version', c.max_version) - if kwargs['version'] and ( - kwargs['max_version'] or kwargs['min_version']): - raise TypeError( - "version is mutually exclusive with min_version and" - " max_version") - - return self.load_from_options(**kwargs) - - -def register_argparse_arguments(*args, **kwargs): - return adapter.register_adapter_argparse_arguments(*args, **kwargs) - - -def register_service_argparse_arguments(*args, **kwargs): - return adapter.register_service_adapter_argparse_arguments(*args, **kwargs) - - -def register_conf_options(*args, **kwargs): - return Adapter().register_conf_options(*args, **kwargs) - - -def load_from_conf_options(*args, **kwargs): - return Adapter().load_from_conf_options(*args, **kwargs) - - -def get_conf_options(): - return Adapter.get_conf_options() diff --git a/keystoneauth1/loading/base.py b/keystoneauth1/loading/base.py deleted file mode 100644 index ec91d07..0000000 --- a/keystoneauth1/loading/base.py +++ /dev/null @@ -1,187 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import abc - -import six -import stevedore - -from keystoneauth1 import exceptions - -PLUGIN_NAMESPACE = 'keystoneauth1.plugin' - - -__all__ = ('get_available_plugin_names', - 'get_available_plugin_loaders', - 'get_plugin_loader', - 'get_plugin_options', - 'BaseLoader', - 'PLUGIN_NAMESPACE') - - -def _auth_plugin_available(ext): - """Read the value of available for whether to load this plugin.""" - return ext.obj.available - - -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.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE, - check_func=_auth_plugin_available, - invoke_on_load=True, - propagate_map_exceptions=True) - return frozenset(mgr.names()) - - -def get_available_plugin_loaders(): - """Retrieve all the plugin classes available on the system. - - :returns: A dict with plugin entrypoint name as the key and the plugin - loader as the value. - :rtype: dict - """ - mgr = stevedore.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE, - check_func=_auth_plugin_available, - invoke_on_load=True, - propagate_map_exceptions=True) - - return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.obj))) - - -def get_plugin_loader(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:`keystoneauth1.loading.BaseLoader` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - try: - mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE, - invoke_on_load=True, - name=name) - except RuntimeError: - raise exceptions.NoMatchingPlugin(name) - - return mgr.driver - - -def get_plugin_options(name): - """Get the options for a specific plugin. - - This will be the list of options that is registered and loaded by the - specified plugin. - - :returns: A list of :py:class:`keystoneauth1.loading.Opt` options. - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - return get_plugin_loader(name).get_options() - - -@six.add_metaclass(abc.ABCMeta) -class BaseLoader(object): - - @property - def plugin_class(self): - raise NotImplementedError() - - def create_plugin(self, **kwargs): - """Create a plugin from the options available for the loader. - - Given the options that were specified by the loader create an - appropriate plugin. You can override this function in your loader. - - This used to be specified by providing the plugin_class property and - this is still supported, however specifying a property didn't let you - choose a plugin type based upon the options that were presented. - - Override this function if you wish to return different plugins based on - the options presented, otherwise you can simply provide the - plugin_class property. - - Added 2.9 - """ - return self.plugin_class(**kwargs) - - @abc.abstractmethod - def get_options(self): - """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 [] - - @property - def available(self): - """Return if the plugin is available for loading. - - If a plugin is missing dependencies or for some other reason should not - be available to the current system it should override this property and - return False to exclude itself from the plugin list. - - :rtype: bool - """ - return True - - def load_from_options(self, **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. - """ - missing_required = [o for o in self.get_options() - if o.required and kwargs.get(o.dest) is None] - - if missing_required: - raise exceptions.MissingRequiredOptions(missing_required) - - return self.create_plugin(**kwargs) - - def load_from_options_getter(self, getter, **kwargs): - """Load a plugin from getter function that returns 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 a - :py:class:`keystoneauth1.loading.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:`keystoneauth1.plugin.BaseAuthPlugin` - """ - for opt in (o for o in self.get_options() if o.dest not in kwargs): - val = getter(opt) - if val is not None: - val = opt.type(val) - kwargs[opt.dest] = val - - return self.load_from_options(**kwargs) diff --git a/keystoneauth1/loading/cli.py b/keystoneauth1/loading/cli.py deleted file mode 100644 index 79816ca..0000000 --- a/keystoneauth1/loading/cli.py +++ /dev/null @@ -1,105 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional - -from keystoneauth1.loading import base - - -__all__ = ('register_argparse_arguments', - 'load_from_argparse_arguments') - - -def _register_plugin_argparse_arguments(parser, plugin): - for opt in plugin.get_options(): - parser.add_argument(*opt.argparse_args, - default=opt.argparse_default, - metavar=opt.metavar, - help=opt.help, - dest='os_%s' % opt.dest) - - -@positional() -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 parser: the parser to attach argparse options to. - :type parser: argparse.ArgumentParser - :param list argv: the arguments provided to the appliation. - :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: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - in_parser = argparse.ArgumentParser(add_help=False) - env_plugin = os.environ.get('OS_AUTH_TYPE', - os.environ.get('OS_AUTH_PLUGIN', default)) - for p in (in_parser, parser): - p.add_argument('--os-auth-type', - '--os-auth-plugin', - metavar='', - default=env_plugin, - help='Authentication type to use') - - options, _args = in_parser.parse_known_args(argv) - - if not options.os_auth_type: - return None - - if isinstance(options.os_auth_type, base.BaseLoader): - msg = 'Default Authentication options' - plugin = options.os_auth_type - else: - msg = 'Options specific to the %s plugin.' % options.os_auth_type - plugin = base.get_plugin_loader(options.os_auth_type) - - group = parser.add_argument_group('Authentication Options', msg) - _register_plugin_argparse_arguments(group, plugin) - return plugin - - -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: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - if not namespace.os_auth_type: - return None - - if isinstance(namespace.os_auth_type, type): - plugin = namespace.os_auth_type - else: - plugin = base.get_plugin_loader(namespace.os_auth_type) - - def _getter(opt): - return getattr(namespace, 'os_%s' % opt.dest) - - return plugin.load_from_options_getter(_getter, **kwargs) diff --git a/keystoneauth1/loading/conf.py b/keystoneauth1/loading/conf.py deleted file mode 100644 index a150260..0000000 --- a/keystoneauth1/loading/conf.py +++ /dev/null @@ -1,135 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.loading import base -from keystoneauth1.loading import opts - -_AUTH_TYPE_OPT = opts.Opt('auth_type', - deprecated=[opts.Opt('auth_plugin')], - help='Authentication type to load') - -_section_help = 'Config Section from which to load plugin specific options' -_AUTH_SECTION_OPT = opts.Opt('auth_section', help=_section_help) - - -__all__ = ('get_common_conf_options', - 'get_plugin_conf_options', - 'register_conf_options', - 'load_from_conf_options') - - -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_type: 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_TYPE_OPT._to_oslo_opt(), _AUTH_SECTION_OPT._to_oslo_opt()] - - -def get_plugin_conf_options(plugin): - """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. - - :param plugin: The name of the plugin loader or a plugin loader object - :type plugin: str or keystoneauth1._loading.BaseLoader - - :returns: A list of oslo_config options. - """ - try: - getter = plugin.get_options - except AttributeError: - opts = base.get_plugin_options(plugin) - else: - opts = getter() - - return [o._to_oslo_opt() for o in opts] - - -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_type: 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._to_oslo_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_TYPE_OPT._to_oslo_opt(), group=group) - - -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 str group: The group name that options should be read from. - - :returns: An authentication Plugin or None if a name is not provided - :rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.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_type - if not name: - return None - - plugin = base.get_plugin_loader(name) - plugin_opts = plugin.get_options() - oslo_opts = [o._to_oslo_opt() for o in plugin_opts] - - conf.register_opts(oslo_opts, group=group) - - def _getter(opt): - return conf[group][opt.dest] - - return plugin.load_from_options_getter(_getter, **kwargs) diff --git a/keystoneauth1/loading/identity.py b/keystoneauth1/loading/identity.py deleted file mode 100644 index e08e257..0000000 --- a/keystoneauth1/loading/identity.py +++ /dev/null @@ -1,162 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.loading import base -from keystoneauth1.loading import opts - -__all__ = ('BaseIdentityLoader', - 'BaseV2Loader', - 'BaseV3Loader', - 'BaseFederationLoader', - 'BaseGenericLoader') - - -class BaseIdentityLoader(base.BaseLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common across all - plugins that are developed against the OpenStack identity service. It - provides the options expected by the - :py:class:`keystoneauth1.identity.BaseIdentityPlugin` class. - """ - - def get_options(self): - options = super(BaseIdentityLoader, self).get_options() - - options.extend([ - opts.Opt('auth-url', - required=True, - help='Authentication URL'), - ]) - - return options - - -class BaseV2Loader(BaseIdentityLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common to the V2 - identity API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v2.Auth` class. - """ - - def get_options(self): - options = super(BaseV2Loader, self).get_options() - - options.extend([ - opts.Opt('tenant-id', help='Tenant ID'), - opts.Opt('tenant-name', help='Tenant Name'), - opts.Opt('trust-id', help='Trust ID'), - ]) - - return options - - -class BaseV3Loader(BaseIdentityLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common to the V3 - identity API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v3.Auth` class. - """ - - def get_options(self): - options = super(BaseV3Loader, self).get_options() - - options.extend([ - opts.Opt('domain-id', help='Domain ID to scope to'), - opts.Opt('domain-name', help='Domain name to scope to'), - opts.Opt('project-id', help='Project ID to scope to'), - opts.Opt('project-name', help='Project name to scope to'), - opts.Opt('project-domain-id', - help='Domain ID containing project'), - opts.Opt('project-domain-name', - help='Domain name containing project'), - opts.Opt('trust-id', help='Trust ID'), - ]) - - return options - - def load_from_options(self, **kwargs): - if (kwargs.get('project_name') and - not (kwargs.get('project_domain_name') or - kwargs.get('project_domain_id'))): - m = "You have provided a project_name. In the V3 identity API a " \ - "project_name is only unique within a domain so you must " \ - "also provide either a project_domain_id or " \ - "project_domain_name." - raise exceptions.OptionError(m) - - return super(BaseV3Loader, self).load_from_options(**kwargs) - - -class BaseFederationLoader(BaseV3Loader): - """Base Option handling for federation plugins. - - This class defines options and handling that should be common to the V3 - identity federation API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v3.FederationBaseAuth` class. - """ - - def get_options(self): - options = super(BaseFederationLoader, self).get_options() - - options.extend([ - opts.Opt('identity-provider', - help="Identity Provider's name", - required=True), - opts.Opt('protocol', - help='Protocol for federated plugin', - required=True), - ]) - - return options - - -class BaseGenericLoader(BaseIdentityLoader): - """Base Option handling for generic plugins. - - This class defines options and handling that should be common to generic - plugins. These plugins target the OpenStack identity service however are - designed to be independent of API version. It provides the options expected - by the :py:class:`keystoneauth1.identity.v3.BaseGenericPlugin` class. - """ - - def get_options(self): - options = super(BaseGenericLoader, self).get_options() - - options.extend([ - opts.Opt('domain-id', help='Domain ID to scope to'), - opts.Opt('domain-name', help='Domain name to scope to'), - opts.Opt('project-id', help='Project ID to scope to', - deprecated=[opts.Opt('tenant-id')]), - opts.Opt('project-name', help='Project name to scope to', - deprecated=[opts.Opt('tenant-name')]), - opts.Opt('project-domain-id', - help='Domain ID containing project'), - opts.Opt('project-domain-name', - help='Domain name containing project'), - opts.Opt('trust-id', help='Trust ID'), - opts.Opt('default-domain-id', - help='Optional domain ID to use with v3 and v2 ' - 'parameters. It will be used for both the user ' - 'and project domain in v3 and ignored in ' - 'v2 authentication.'), - opts.Opt('default-domain-name', - help='Optional domain name to use with v3 API and v2 ' - 'parameters. It will be used for both the user ' - 'and project domain in v3 and ignored in ' - 'v2 authentication.'), - ]) - - return options diff --git a/keystoneauth1/loading/opts.py b/keystoneauth1/loading/opts.py deleted file mode 100644 index 03eed84..0000000 --- a/keystoneauth1/loading/opts.py +++ /dev/null @@ -1,151 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import itertools -import os - -from positional import positional - -from keystoneauth1.loading import _utils - - -__all__ = ('Opt',) - - -class Opt(object): - """An option required by an authentication plugin. - - Opts provide a means for authentication plugins that are going to be - dynamically loaded to specify the parameters that are to be passed to the - plugin on initialization. - - The Opt specifies information about the way the plugin parameter is to be - represented in different loading mechanisms. - - When defining an Opt with a - the - should be present in the name - parameter. This will automatically be converted to an _ when passing to the - plugin initialization. For example, you should specify:: - - Opt('user-domain-id') - - which will pass the value as `user_domain_id` to the plugin's - initialization. - - :param str name: The name of the option. - :param callable type: The type of the option. This is a callable which is - passed the raw option that was loaded (often a string) and is required - to return the parameter in the type expected by __init__. - :param str help: The help text that is shown along with the option. - :param bool secret: If the parameter is secret it should not be printed or - logged in debug output. - :param str dest: the name of the argument that will be passed to __init__. - This allows you to have a different name in loading than is used by the - __init__ function. Defaults to the value of name. - :param keystoneauth1.loading.Opt: A list of other options that are - deprecated in favour of this one. This ensures the old options are - still registered. - :type opt: list(Opt) - :param default: A default value that can be used if one is not provided. - :param str metavar: The that should be printed in CLI help text. - :param bool required: If the option is required to load the plugin. If a - required option is not present loading should fail. - :param str prompt: If the option can be requested via a prompt (where - appropriate) set the string that should be used to prompt with. - """ - - @positional() - def __init__(self, - name, - type=str, - help=None, - secret=False, - dest=None, - deprecated=None, - default=None, - metavar=None, - required=False, - prompt=None): - if not callable(type): - raise TypeError('type must be callable') - - if dest is None: - dest = name.replace('-', '_') - - self.name = name - self.type = type - self.help = help - self.secret = secret - self.required = required - self.dest = dest - self.deprecated = [] if deprecated is None else deprecated - self.default = default - self.metavar = metavar - self.prompt = prompt - # These are for oslo.config compat - self.deprecated_opts = self.deprecated - self.deprecated_for_removal = [] - self.sample_default = None - self.group = None - - def __repr__(self): - """Return string representation of option name.""" - return '' % self.name - - def _to_oslo_opt(self): - cfg = _utils.get_oslo_config() - deprecated_opts = [cfg.DeprecatedOpt(o.name) for o in self.deprecated] - - return cfg.Opt(name=self.name, - type=self.type, - help=self.help, - secret=self.secret, - required=self.required, - dest=self.dest, - deprecated_opts=deprecated_opts, - metavar=self.metavar) - - def __eq__(self, other): - """Define equality operator on option parameters.""" - return (type(self) == type(other) and - self.name == other.name and - self.type == other.type and - self.help == other.help and - self.secret == other.secret and - self.required == other.required and - self.dest == other.dest and - self.deprecated == other.deprecated and - self.default == other.default and - self.metavar == other.metavar) - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequality operator on option parameters.""" - return not self.__eq__(other) - - @property - def _all_opts(self): - return itertools.chain([self], self.deprecated) - - @property - def argparse_args(self): - return ['--os-%s' % o.name for o in self._all_opts] - - @property - def argparse_default(self): - # select the first ENV that is not false-y or return None - for o in self._all_opts: - v = os.environ.get('OS_%s' % o.name.replace('-', '_').upper()) - if v: - return v - - return self.default diff --git a/keystoneauth1/loading/session.py b/keystoneauth1/loading/session.py deleted file mode 100644 index 4e5ee3e..0000000 --- a/keystoneauth1/loading/session.py +++ /dev/null @@ -1,254 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 positional import positional - -from keystoneauth1.loading import _utils -from keystoneauth1.loading import base -from keystoneauth1 import session - - -__all__ = ('register_argparse_arguments', - 'load_from_argparse_arguments', - 'register_conf_options', - 'load_from_conf_options', - 'get_conf_options') - - -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 - - -class Session(base.BaseLoader): - - @property - def plugin_class(self): - return session.Session - - def get_options(self): - return [] - - @positional(1) - def load_from_options(self, - 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: - # passing cert and key together is deprecated in favour of the - # requests lib form of having the cert and key as a tuple - cert = (cert, key) - - return super(Session, self).load_from_options(verify=verify, - cert=cert, - **kwargs) - - def register_argparse_arguments(self, parser): - session_group = parser.add_argument_group( - 'API Connection Options', - 'Options controlling the HTTP API Connections') - - session_group.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.') - - session_group.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].') - - session_group.add_argument( - '--os-cert', - metavar='', - default=os.environ.get('OS_CERT'), - help='Defaults to env[OS_CERT].') - - session_group.add_argument( - '--os-key', - metavar='', - default=os.environ.get('OS_KEY'), - help='Defaults to env[OS_KEY].') - - session_group.add_argument( - '--timeout', - default=600, - type=_positive_non_zero_float, - metavar='', - help='Set request timeout (in seconds).') - - def load_from_argparse_arguments(self, namespace, **kwargs): - kwargs.setdefault('insecure', namespace.insecure) - kwargs.setdefault('cacert', namespace.os_cacert) - kwargs.setdefault('cert', namespace.os_cert) - kwargs.setdefault('key', namespace.os_key) - kwargs.setdefault('timeout', namespace.timeout) - - return self.load_from_options(**kwargs) - - def get_conf_options(self, 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. - """ - cfg = _utils.get_oslo_config() - - 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'), - ] - - def register_conf_options(self, 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 = self.get_conf_options(deprecated_opts=deprecated_opts) - conf.register_group(_utils.get_oslo_config().OptGroup(group)) - conf.register_opts(opts, group=group) - return opts - - def load_from_conf_options(self, 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.setdefault('insecure', c.insecure) - kwargs.setdefault('cacert', c.cafile) - kwargs.setdefault('cert', c.certfile) - kwargs.setdefault('key', c.keyfile) - kwargs.setdefault('timeout', c.timeout) - - return self.load_from_options(**kwargs) - - -def register_argparse_arguments(*args, **kwargs): - return Session().register_argparse_arguments(*args, **kwargs) - - -def load_from_argparse_arguments(*args, **kwargs): - return Session().load_from_argparse_arguments(*args, **kwargs) - - -def register_conf_options(*args, **kwargs): - return Session().register_conf_options(*args, **kwargs) - - -def load_from_conf_options(*args, **kwargs): - return Session().load_from_conf_options(*args, **kwargs) - - -def get_conf_options(*args, **kwargs): - return Session().get_conf_options(*args, **kwargs) diff --git a/keystoneauth1/noauth.py b/keystoneauth1/noauth.py deleted file mode 100644 index 9503771..0000000 --- a/keystoneauth1/noauth.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - - -class NoAuth(plugin.BaseAuthPlugin): - """A provider that will always use no auth. - - This is useful to unify session/adapter loading for services - that might be deployed in standalone/noauth mode. - """ - - def get_token(self, session): - return 'notused' diff --git a/keystoneauth1/plugin.py b/keystoneauth1/plugin.py deleted file mode 100644 index 3551a0d..0000000 --- a/keystoneauth1/plugin.py +++ /dev/null @@ -1,252 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# NOTE(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 = object() - -IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token' - - -class BaseAuthPlugin(object): - """The basic structure of an authentication plugin. - - .. note:: - See :doc:`/authentication-plugins` for a description of plugins - provided by this library. - - """ - - 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: keystoneauth1.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: keystoneauth1.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: keystoneauth1.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: keystoneauth1.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: keystoneauth1.session.Session - - :returns: A project identifier or None if one is not available. - :rtype: str - """ - return None - - def get_sp_auth_url(self, session, sp_id, **kwargs): - """Return auth_url from the Service Provider object. - - This url is used for obtaining unscoped federated token from remote - cloud. - - :param sp_id: ID of the Service Provider to be queried. - :type sp_id: string - - :returns: A Service Provider auth_url or None if one is not available. - :rtype: str - - """ - return None - - def get_sp_url(self, session, sp_id, **kwargs): - """Return sp_url from the Service Provider object. - - This url is used for passing SAML2 assertion to the remote cloud. - - :param sp_id: ID of the Service Provider to be queried. - :type sp_id: str - - :returns: A Service Provider sp_url or None if one is not available. - :rtype: str - - """ - return None - - def get_cache_id(self): - """Fetch an identifier that uniquely identifies the auth options. - - The returned identifier need not be decomposable or otherwise provide - anyway to recreate the plugin. It should not contain sensitive data in - plaintext. - - This string MUST change if any of the parameters that are used to - uniquely identity this plugin change. - - If get_cache_id returns a str value suggesting that caching is - supported then get_auth_cache and set_auth_cache must also be - implemented. - - :returns: A unique string for the set of options - :rtype: str or None if this is unsupported or unavailable. - """ - return None - - def get_auth_state(self): - """Retrieve the current authentication state for the plugin. - - Retrieve any internal state that represents the authenticated plugin. - - This should not fetch any new data if it is not present. - - :raises NotImplementedError: if the plugin does not support this - feature. - - :returns: raw python data (which can be JSON serialized) that can be - moved into another plugin (of the same type) to have the - same authenticated state. - :rtype: object or None if unauthenticated. - """ - raise NotImplementedError() - - def set_auth_state(self, data): - """Install existing authentication state for a plugin. - - Take the output of get_auth_state and install that authentication state - into the current authentication plugin. - - :raises NotImplementedError: if the plugin does not support this - feature. - """ - raise NotImplementedError() diff --git a/keystoneauth1/service_token.py b/keystoneauth1/service_token.py deleted file mode 100644 index d06402b..0000000 --- a/keystoneauth1/service_token.py +++ /dev/null @@ -1,73 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - -SERVICE_AUTH_HEADER_NAME = 'X-Service-Token' - -__all__ = ('ServiceTokenAuthWrapper',) - - -class ServiceTokenAuthWrapper(plugin.BaseAuthPlugin): - - def __init__(self, user_auth, service_auth): - self.user_auth = user_auth - self.service_auth = service_auth - - def get_headers(self, session, **kwargs): - headers = self.user_auth.get_headers(session, **kwargs) - - token = self.service_auth.get_token(session, **kwargs) - headers[SERVICE_AUTH_HEADER_NAME] = token - - return headers - - def invalidate(self): - # NOTE(jamielennox): hmm, what to do here? Should we invalidate both - # the service and user auth? Only one? There's no way to know what the - # failure was to selectively invalidate. - user = self.user_auth.invalidate() - service = self.service_auth.invalidate() - return user or service - - def get_connection_params(self, *args, **kwargs): - # NOTE(jamielennox): This is also a bit of a guess but unlikely to be a - # problem in practice. We don't know how merging connection parameters - # between these plugins will conflict - but there aren't many plugins - # that set this anyway. - # Take the service auth params first so that user auth params will be - # given priority. - params = self.service_auth.get_connection_params(*args, **kwargs) - params.update(self.user_auth.get_connection_params(*args, **kwargs)) - return params - - # TODO(jamielennox): Everything below here is a generic wrapper that could - # be extracted into a base wrapper class. We can do this as soon as there - # is a need for it, but we may never actually need it. - - def get_token(self, *args, **kwargs): - return self.user_auth.get_token(*args, **kwargs) - - def get_endpoint(self, *args, **kwargs): - return self.user_auth.get_endpoint(*args, **kwargs) - - def get_user_id(self, *args, **kwargs): - return self.user_auth.get_user_id(*args, **kwargs) - - def get_project_id(self, *args, **kwargs): - return self.user_auth.get_project_id(*args, **kwargs) - - def get_sp_auth_url(self, *args, **kwargs): - return self.user_auth.get_sp_auth_url(*args, **kwargs) - - def get_sp_url(self, *args, **kwargs): - return self.user_auth.get_sp_url(*args, **kwargs) diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py deleted file mode 100644 index 16cc84c..0000000 --- a/keystoneauth1/session.py +++ /dev/null @@ -1,1114 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 functools -import hashlib -import json -import logging -import os -import platform -import socket -import sys -import time -import uuid - -from positional import positional -import requests -import six -from six.moves import urllib - -import keystoneauth1 -from keystoneauth1 import _utils as utils -from keystoneauth1 import discover -from keystoneauth1 import exceptions - -try: - import netaddr -except ImportError: - netaddr = None - -try: - import osprofiler.web as osprofiler_web -except ImportError: - osprofiler_web = None - -DEFAULT_USER_AGENT = 'keystoneauth1/%s %s %s/%s' % ( - keystoneauth1.__version__, requests.utils.default_user_agent(), - platform.python_implementation(), platform.python_version()) - -# 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 = utils.get_logger(__name__) - - -def _construct_session(session_obj=None): - # NOTE(morganfainberg): if the logic in this function changes be sure to - # update the betamax fixture's '_construct_session_with_betamax" function - # as well. - if not session_obj: - session_obj = requests.Session() - # Use TCPKeepAliveAdapter to fix bug 1323862 - for scheme in list(session_obj.adapters): - session_obj.mount(scheme, TCPKeepAliveAdapter()) - return session_obj - - -def _mv_legacy_headers_for_service(mv_service_type): - """Workaround for services that predate standardization. - - TODO(sdague): eventually convert this to using os-service-types - and put the logic there. However, right now this is so little - logic, inlining it for release is a better call. - - """ - headers = [] - if mv_service_type == "compute": - headers.append("X-OpenStack-Nova-API-Version") - elif mv_service_type == "baremetal": - headers.append("X-OpenStack-Ironic-API-Version") - return headers - - -class _JSONEncoder(json.JSONEncoder): - - def default(self, o): - if isinstance(o, datetime.datetime): - return o.isoformat() - if isinstance(o, uuid.UUID): - return six.text_type(o) - if netaddr and isinstance(o, netaddr.IPAddress): - return six.text_type(o) - - return super(_JSONEncoder, self).default(o) - - -class _StringFormatter(object): - """A String formatter that fetches values on demand.""" - - def __init__(self, session, auth): - self.session = session - self.auth = auth - - def __getitem__(self, item): - if item == 'project_id': - value = self.session.get_project_id(self.auth) - elif item == 'user_id': - value = self.session.get_user_id(self.auth) - else: - raise AttributeError(item) - - if not value: - raise ValueError("This type of authentication does not provide a " - "%s that can be substituted" % item) - - return value - - -def _determine_calling_package(): - """Walk the call frames trying to identify what is using this module.""" - # Create a lookup table mapping file name to module name. The ``inspect`` - # module does this but is far less efficient. Same story with the - # frame walking below. One could use ``inspect.stack()`` but it - # has far more overhead. - mod_lookup = dict((m.__file__, n) for n, m in sys.modules.items() - if hasattr(m, '__file__')) - - # NOTE(shaleh): these are not useful because they hide the real - # user of the code. debtcollector did not import keystoneauth but - # it will show up in the call stack. Similarly we do not want to - # report ourselves or keystone client as the user agent. The real - # user is the code importing them. - ignored = ('debtcollector', 'keystoneauth1', 'keystoneclient') - - i = 0 - while True: - i += 1 - - try: - # NOTE(shaleh): this is safe in CPython but could break in - # other implementations of Python. Yes, the `inspect` - # module could be used instead. But it does a lot more - # work so it has worse performance. - f = sys._getframe(i) - try: - name = mod_lookup[f.f_code.co_filename] - # finds the full name module.foo.bar but all we need - # is the module name. - name, _, _ = name.partition('.') - if name not in ignored: - return name - except KeyError: - pass # builtin or the like - except ValueError: - # hit the bottom of the frame stack - break - - return '' - - -def _determine_user_agent(): - """Attempt to programatically generate a user agent string. - - First, look at the name of the process. Return this unless it is in - the `ignored` list. Otherwise, look at the function call stack and - try to find the name of the code that invoked this module. - """ - # NOTE(shaleh): mod_wsgi is not any more useful than just - # reporting "keystoneauth". Ignore it and perform the package name - # heuristic. - ignored = ('mod_wsgi', ) - - try: - name = sys.argv[0] - except IndexError: - # sys.argv is empty, usually the Python interpreter prevents this. - return '' - - if not name: - return '' - - name = os.path.basename(name) - if name in ignored: - name = _determine_calling_package() - return name - - -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 :mod:`requests` library. - - :param auth: An authentication plugin to authenticate the session with. - (optional, defaults to None) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - :param requests.Session session: A requests session object that can be used - for issuing requests. (optional) - :param str 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 str user_agent: A User-Agent header string to use for the request. - If not provided, a default of - :attr:`~keystoneauth1.session.DEFAULT_USER_AGENT` is - used, which contains the keystoneauth1 version as - well as those of the requests library and which - Python is being used. When a non-None value is - passed, it will be prepended to the default. - :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) - :param dict additional_headers: Additional headers that should be attached - to every request passing through the - session. Headers of the same name specified - per request will take priority. - :param str app_name: The name of the application that is creating the - session. This will be used to create the user_agent. - :param str app_version: The version of the application creating the - session. This will be used to create the - user_agent. - :param list additional_user_agent: A list of tuple of name, version that - will be added to the user agent. This - can be used by libraries that are part - of the communication process. - :param dict discovery_cache: A dict to be used for caching of discovery - information. This is normally managed - transparently, but if the user wants to - share a single cache across multiple sessions - that do not share an auth plugin, it can - be provided here. (optional, defaults to - None which means automatically manage) - """ - - user_agent = None - - _REDIRECT_STATUSES = (301, 302, 303, 305, 307, 308) - - _DEFAULT_REDIRECT_LIMIT = 30 - - @positional(2) - def __init__(self, auth=None, session=None, original_ip=None, verify=True, - cert=None, timeout=None, user_agent=None, - redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None, - app_name=None, app_version=None, additional_user_agent=None, - discovery_cache=None): - - self.auth = auth - self.session = _construct_session(session) - self.original_ip = original_ip - self.verify = verify - self.cert = cert - self.timeout = None - self.redirect = redirect - self.additional_headers = additional_headers or {} - self.app_name = app_name - self.app_version = app_version - self.additional_user_agent = additional_user_agent or [] - self._determined_user_agent = None - if discovery_cache is None: - discovery_cache = {} - self._discovery_cache = discovery_cache - - if timeout is not None: - self.timeout = float(timeout) - - if user_agent is not None: - self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT) - - self._json = _JSONEncoder() - - @property - def adapters(self): - return self.session.adapters - - @adapters.setter - def adapters(self, value): - self.session.adapters = value - - def mount(self, scheme, adapter): - self.session.mount(scheme, adapter) - - def _remove_service_catalog(self, body): - try: - data = json.loads(body) - - # V3 token - if 'token' in data and 'catalog' in data['token']: - data['token']['catalog'] = '' - return self._json.encode(data) - - # V2 token - if 'serviceCatalog' in data['access']: - data['access']['serviceCatalog'] = '' - return self._json.encode(data) - - except Exception: - # Don't fail trying to clean up the request body. - pass - return body - - @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: - token_hasher = hashlib.sha1() - token_hasher.update(header[1].encode('utf-8')) - token_hash = token_hasher.hexdigest() - return (header[0], '{SHA1}%s' % token_hash) - return header - - @positional() - def _http_log_request(self, url, method=None, data=None, - json=None, headers=None, query_params=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, six.string_types): - string_parts.append('--cacert "%s"' % self.verify) - - if method: - string_parts.extend(['-X', method]) - - if query_params: - # Don't check against `is not None` as this can be - # an empty dictionary, which we shouldn't bother logging. - url = url + '?' + urllib.parse.urlencode(query_params) - # URLs with query strings need to be wrapped in quotes in order - # for the CURL command to run properly. - string_parts.append('"%s"' % url) - else: - string_parts.append(url) - - if headers: - for header in headers.items(): - string_parts.append('-H "%s: %s"' - % self._process_header(header)) - if json: - data = self._json.encode(json) - if data: - if isinstance(data, six.binary_type): - try: - data = data.decode("ascii") - except UnicodeDecodeError: - data = "" - string_parts.append("-d '%s'" % data) - - logger.debug(' '.join(string_parts)) - - @positional() - def _http_log_response(self, response=None, json=None, - status_code=None, headers=None, text=None, - logger=_logger): - if not logger.isEnabledFor(logging.DEBUG): - return - - if response is not None: - if not status_code: - status_code = response.status_code - if not headers: - headers = response.headers - if not text: - # 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 = self._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)) - if json: - text = self._json.encode(json) - - string_parts = ['RESP:'] - - if status_code: - string_parts.append('[%s]' % status_code) - if headers: - for header in headers.items(): - string_parts.append('%s: %s' % self._process_header(header)) - if text: - string_parts.append('\nRESP BODY: %s\n' % text) - - logger.debug(' '.join(string_parts)) - - @staticmethod - def _set_microversion_headers( - headers, microversion, service_type, endpoint_filter): - # We're converting it to normalized version number for two reasons. - # First, to validate it's a real version number. Second, so that in - # the future we can pre-validate that it is within the range of - # available microversions before we send the request. - # TODO(mordred) Validate when we get the response back that - # the server executed in the microversion we expected. - # TODO(mordred) Validate that the requested microversion works - # with the microversion range we found in discovery. - microversion = discover.normalize_version_number(microversion) - # Can't specify a M.latest microversion - if (microversion[0] != discover.LATEST and - discover.LATEST in microversion[1:]): - raise TypeError( - "Specifying a '{major}.latest' microversion is not allowed.") - microversion = discover.version_to_string(microversion) - if not service_type: - if endpoint_filter and 'service_type' in endpoint_filter: - service_type = endpoint_filter['service_type'] - else: - raise TypeError( - "microversion {microversion} was requested but no" - " service_type information is available. Either provide a" - " service_type in endpoint_filter or pass" - " microversion_service_type as an argument.".format( - microversion=microversion)) - - # TODO(mordred) cinder uses volume in its microversion header. This - # logic should be handled in the future by os-service-types but for - # now hard-code for cinder. - if (service_type.startswith('volume') - or service_type == 'block-storage'): - service_type = 'volume' - headers.setdefault('OpenStack-API-Version', - '{service_type} {microversion}'.format( - service_type=service_type, - microversion=microversion)) - header_names = _mv_legacy_headers_for_service(service_type) - for h in header_names: - headers.setdefault(h, microversion) - - @positional() - 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, - allow={}, client_name=None, client_version=None, - microversion=None, microversion_service_type=None, - **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 str 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 str method: The http method to use. (e.g. 'GET', 'POST') - :param str 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 str 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. This string may contain - the values ``%(project_id)s`` and - ``%(user_id)s`` to have those values - replaced by the project_id/user_id of the - current authentication. (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: keystoneauth1.plugin.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 keystoneauth1.session default - logger will be used. - :type logger: logging.Logger - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param microversion: Microversion to send for this request. - microversion can be given as a string or a tuple. - (optional) - :param str microversion_service_type: The service_type to be sent in - the microversion header, if a microversion is given. - Defaults to the value of service_type from - endpoint_filter if one exists. If endpoint_filter is not - provided or does not have a service_type, microversion - is given and microversion_service_type is not provided, - an exception will be raised. - :param kwargs: any other parameter that can be passed to - :meth:`requests.Session.request` (such as `headers`). - Except: - - - `data` will be overwritten by the data in the `json` - param. - - `allow_redirects` is ignored as redirects are handled - by the session. - - :raises keystoneauth1.exceptions.base.ClientException: For connection - failure, or to indicate an error response code. - - :returns: The response to the request. - """ - headers = kwargs.setdefault('headers', dict()) - if microversion: - self._set_microversion_headers( - headers, microversion, microversion_service_type, - endpoint_filter) - - 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 % _StringFormatter(self, auth) - elif endpoint_filter: - base_url = self.get_endpoint(auth, allow=allow, - **endpoint_filter) - - if not base_url: - raise exceptions.EndpointNotFound() - - 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: - # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be - # ordered by decreasing significance. If a user sets their product - # that value will be used. Otherwise we attempt to derive a useful - # product value. The value will be prepended it to the KSA version, - # requests version, and then the Python version. - - agent = [] - - if self.app_name and self.app_version: - agent.append('%s/%s' % (self.app_name, self.app_version)) - elif self.app_name: - agent.append(self.app_name) - - for additional in self.additional_user_agent: - agent.append('%s/%s' % additional) - - if client_name and client_version: - agent.append('%s/%s' % (client_name, client_version)) - elif client_name: - agent.append(client_name) - - if not agent: - # NOTE(jamielennox): determine_user_agent will return an empty - # string on failure so checking for None will ensure it is only - # called once even on failure. - if self._determined_user_agent is None: - self._determined_user_agent = _determine_user_agent() - - if self._determined_user_agent: - agent.append(self._determined_user_agent) - - agent.append(DEFAULT_USER_AGENT) - user_agent = headers.setdefault('User-Agent', ' '.join(agent)) - - if self.original_ip: - headers.setdefault('Forwarded', - 'for=%s;by=%s' % (self.original_ip, user_agent)) - - if json is not None: - headers.setdefault('Content-Type', 'application/json') - kwargs['data'] = self._json.encode(json) - - for k, v in self.additional_headers.items(): - headers.setdefault(k, v) - - kwargs.setdefault('verify', self.verify) - - if requests_auth: - kwargs['auth'] = requests_auth - - # Query parameters that are included in the url string will - # be logged properly, but those sent in the `params` parameter - # (which the requests library handles) need to be explicitly - # picked out so they can be included in the URL that gets loggged. - query_params = kwargs.get('params', dict()) - - if log: - self._http_log_request(url, method=method, - data=kwargs.get('data'), - headers=headers, - query_params=query_params, - 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: - # 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) - - # log callee and caller request-id for each api call - if log: - # service_name should be fetched from endpoint_filter if it is not - # present then use service_type as service_name. - service_name = None - if endpoint_filter: - service_name = endpoint_filter.get('service_name') - if not service_name: - service_name = endpoint_filter.get('service_type') - - # Nova uses 'x-compute-request-id' and other services like - # Glance, Cinder etc are using 'x-openstack-request-id' to store - # request-id in the header - request_id = (resp.headers.get('x-openstack-request-id') or - resp.headers.get('x-compute-request-id')) - if request_id: - logger.debug('%(method)s call to %(service_name)s for ' - '%(url)s used request id ' - '%(response_request_id)s', - {'method': resp.request.method, - 'service_name': service_name, - 'url': resp.url, - 'response_request_id': request_id}) - - # 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.ConnectTimeout(msg) - except requests.exceptions.ConnectionError as e: - # NOTE(sdague): urllib3/requests connection error is a - # translation of SocketError. However, SocketError - # happens for many different reasons, and that low - # level message is often really important in figuring - # out the difference between network misconfigurations - # and firewall blocking. - msg = 'Unable to establish connection to %s: %s' % (url, e) - raise exceptions.ConnectFailure(msg) - except requests.exceptions.RequestException as e: - msg = 'Unexpected exception for %(url)s: %(error)s' % { - 'url': url, 'error': e} - raise exceptions.UnknownConnectionError(msg, e) - - except exceptions.RetriableConnectionFailure 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(response=resp, logger=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) - - def _auth_required(self, auth, msg): - if not auth: - auth = self.auth - - if not auth: - msg_fmt = 'An auth plugin is required to %s' - raise exceptions.MissingAuthPlugin(msg_fmt % 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Authentication headers or None for failure. - :rtype: :class:`dict` - """ - auth = self._auth_required(auth, 'fetch a token') - return auth.get_headers(self, **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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - .. warning:: - **DEPRECATED**: This assumes that the only header that is used to - authenticate a message is ``X-Auth-Token``. This may not be - correct. Use :meth:`get_auth_headers` instead. - - :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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: An endpoint if available or None. - :rtype: string - """ - if 'endpoint_override' in kwargs: - return kwargs['endpoint_override'] - - auth = self._auth_required(auth, 'determine endpoint URL') - - return auth.get_endpoint(self, **kwargs) - - def get_endpoint_data(self, auth=None, **kwargs): - """Get endpoint data as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - :raises TypeError: If arguments are invalid - - :returns: Endpoint data if available or None. - :rtype: keystoneauth1.discover.EndpointData - """ - auth = self._auth_required(auth, 'determine endpoint URL') - return auth.get_endpoint_data(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 - parmeters. 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - :raises keystoneauth1.exceptions.auth_plugins.UnsupportedParameters: - if the plugin returns a parameter that is not supported by this - session. - - :returns: Authentication headers or None for failure. - :rtype: :class:`dict` - """ - auth = self._auth_required(auth, 'fetch connection params') - 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: - pass - - if params_copy: - raise exceptions.UnsupportedParameters(list(params_copy.keys())) - - 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: keystoneauth1.plugin.BaseAuthPlugin - - """ - auth = self._auth_required(auth, 'validate') - 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current user_id or None if not supported by plugin. - :rtype: :class:`str` - """ - auth = self._auth_required(auth, 'get user_id') - 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: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current project_id or None if not supported by plugin. - :rtype: :class:`str` - """ - auth = self._auth_required(auth, 'get project_id') - return auth.get_project_id(self) - - -REQUESTS_VERSION = tuple(int(v) for v in requests.__version__.split('.')) - - -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: - https://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 and REQUESTS_VERSION >= (2, 4, 1): - 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) - ] - - # Windows subsystem for Linux does not support this feature - if (hasattr(socket, 'TCP_KEEPCNT') and - not utils.is_windows_linux_subsystem): - 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) diff --git a/keystoneauth1/tests/__init__.py b/keystoneauth1/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/__init__.py b/keystoneauth1/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/access/__init__.py b/keystoneauth1/tests/unit/access/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/access/test_v2_access.py b/keystoneauth1/tests/unit/access/test_v2_access.py deleted file mode 100644 index aabef67..0000000 --- a/keystoneauth1/tests/unit/access/test_v2_access.py +++ /dev/null @@ -1,218 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import datetime -import uuid - -from oslo_utils import timeutils - -from keystoneauth1 import access -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class AccessV2Test(utils.TestCase): - - def test_building_unscoped_accessinfo(self): - token = fixture.V2Token(expires='2012-10-03T16:58:01Z') - - auth_ref = access.create(body=token) - - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertFalse(auth_ref.has_service_catalog()) - - self.assertEqual(auth_ref.auth_token, token.token_id) - self.assertEqual(auth_ref.username, token.user_name) - self.assertEqual(auth_ref.user_id, token.user_id) - - self.assertEqual(auth_ref.role_ids, []) - self.assertEqual(auth_ref.role_names, []) - - self.assertIsNone(auth_ref.tenant_name) - self.assertIsNone(auth_ref.tenant_id) - - 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.assertIsNone(auth_ref.user_domain_id) - self.assertIsNone(auth_ref.user_domain_name) - - 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) - self.assertIsNone(auth_ref.bind) - - def test_will_expire_soon(self): - token = fixture.V2Token() - expires = timeutils.utcnow() + datetime.timedelta(minutes=5) - token.expires = expires - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - 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 = fixture.V2Token() - token.set_scope() - s = token.add_service('identity') - s.add_endpoint('http://url') - - role_data = token.add_role() - - auth_ref = access.create(body=token) - - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertTrue(auth_ref.has_service_catalog()) - - self.assertEqual(auth_ref.auth_token, token.token_id) - self.assertEqual(auth_ref.username, token.user_name) - self.assertEqual(auth_ref.user_id, token.user_id) - - self.assertEqual(auth_ref.role_ids, [role_data['id']]) - self.assertEqual(auth_ref.role_names, [role_data['name']]) - - self.assertEqual(auth_ref.tenant_name, token.tenant_name) - self.assertEqual(auth_ref.tenant_id, token.tenant_id) - - self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) - self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) - - self.assertIsNone(auth_ref.project_domain_id, 'default') - self.assertIsNone(auth_ref.project_domain_name, 'Default') - self.assertIsNone(auth_ref.user_domain_id, 'default') - self.assertIsNone(auth_ref.user_domain_name, 'Default') - - 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 = { - 'access': { - 'token': { - 'id': uuid.uuid4().hex, - 'expires': '2020-01-01T00:00:10.000123Z', - 'tenantId': 'tenant_id1', - }, - 'user': { - 'id': 'user_id1', - 'name': 'user_name1', - 'roles': [ - {'name': 'role1'}, - {'name': 'role2'}, - ], - }, - }, - } - - auth_ref = access.create(body=diablo_token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - 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.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.user_domain_id) - self.assertIsNone(auth_ref.user_domain_name) - self.assertEqual(auth_ref.role_names, ['role1', 'role2']) - - def test_grizzly_token(self): - grizzly_token = { - 'access': { - 'token': { - 'id': uuid.uuid4().hex, - 'expires': '2020-01-01T00:00:10.000123Z', - }, - 'user': { - 'id': 'user_id1', - 'name': 'user_name1', - 'tenantId': 'tenant_id1', - 'tenantName': 'tenant_name1', - 'roles': [ - {'name': 'role1'}, - {'name': 'role2'}, - ], - }, - }, - } - - auth_ref = access.create(body=grizzly_token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual(auth_ref.project_id, 'tenant_id1') - self.assertEqual(auth_ref.project_name, 'tenant_name1') - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.user_domain_id, 'default') - self.assertIsNone(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.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual([role_id], auth_ref.role_ids) - self.assertEqual([role_id], - auth_ref._data['access']['metadata']['roles']) - self.assertEqual([role_name], auth_ref.role_names) - self.assertEqual([{'name': role_name}], - auth_ref._data['access']['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.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - 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_binding(self): - token = fixture.V2Token() - principal = uuid.uuid4().hex - token.set_bind('kerberos', principal) - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual({'kerberos': principal}, auth_ref.bind) - - def test_is_admin_project(self): - token = fixture.V2Token() - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertIs(True, auth_ref.is_admin_project) diff --git a/keystoneauth1/tests/unit/access/test_v2_service_catalog.py b/keystoneauth1/tests/unit/access/test_v2_service_catalog.py deleted file mode 100644 index f62764a..0000000 --- a/keystoneauth1/tests/unit/access/test_v2_service_catalog.py +++ /dev/null @@ -1,269 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class ServiceCatalogTest(utils.TestCase): - def setUp(self): - super(ServiceCatalogTest, self).setUp() - - self.AUTH_RESPONSE_BODY = 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) - - self.AUTH_RESPONSE_BODY.add_role(id='234', name='compute:admin') - role = self.AUTH_RESPONSE_BODY.add_role(id='235', - name='object-store:admin') - role['tenantId'] = '1' - - s = self.AUTH_RESPONSE_BODY.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 = self.AUTH_RESPONSE_BODY.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 = self.AUTH_RESPONSE_BODY.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') - - def test_building_a_service_catalog(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - self.assertEqual(sc.url_for(service_type='compute'), - "https://compute.north.host/v1/1234") - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name="South", - service_type='compute') - - def test_service_catalog_endpoints(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - public_ep = sc.get_endpoints(service_type='compute', - interface='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_empty(self): - self.AUTH_RESPONSE_BODY['access']['serviceCatalog'] = [] - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - self.assertRaises(exceptions.EmptyCatalog, - auth_ref.service_catalog.url_for, - service_type='image', - interface='internalURL') - - def test_service_catalog_get_endpoints_region_names(self): - auth_ref = access.create(body=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.create(body=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/') - - 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.create(body=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.assertEqual(len(urls), 0) - - def test_service_catalog_service_name(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - url = sc.url_for(service_name='Image Servers', interface='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', - interface='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', - interface='public') - - self.assertEqual(0, len(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.create(body=token) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='publicURL') - - self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='publicURL', - region_name='region-1') - - self.assertEqual(('public-1', ), urls) - - def test_service_catalog_endpoint_id(self): - token = fixture.V2Token() - token.set_scope() - endpoint_id = uuid.uuid4().hex - public_url = uuid.uuid4().hex - - s = token.add_service('compute') - s.add_endpoint(public=public_url, id=endpoint_id) - s.add_endpoint(public=uuid.uuid4().hex) - - auth_ref = access.create(body=token) - - # initially assert that we get back all our urls for a simple filter - urls = auth_ref.service_catalog.get_urls(interface='public') - self.assertEqual(2, len(urls)) - - urls = auth_ref.service_catalog.get_urls(endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - # with bad endpoint_id nothing should be found - urls = auth_ref.service_catalog.get_urls(endpoint_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual(0, len(urls)) - - # we ignore a service_id because v2 doesn't know what it is - urls = auth_ref.service_catalog.get_urls(endpoint_id=endpoint_id, - service_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual((public_url, ), urls) - - def test_service_catalog_without_service_type(self): - token = fixture.V2Token() - token.set_scope() - - public_urls = [] - - for i in range(0, 3): - public_url = uuid.uuid4().hex - public_urls.append(public_url) - - s = token.add_service(uuid.uuid4().hex) - s.add_endpoint(public=public_url) - - auth_ref = access.create(body=token) - urls = auth_ref.service_catalog.get_urls(service_type=None, - interface='public') - - self.assertEqual(3, len(urls)) - - for p in public_urls: - self.assertIn(p, urls) diff --git a/keystoneauth1/tests/unit/access/test_v3_access.py b/keystoneauth1/tests/unit/access/test_v3_access.py deleted file mode 100644 index 6dfca99..0000000 --- a/keystoneauth1/tests/unit/access/test_v3_access.py +++ /dev/null @@ -1,264 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 datetime -from oslo_utils import timeutils - -from keystoneauth1 import access -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class AccessV3Test(utils.TestCase): - - def test_building_unscoped_accessinfo(self): - token = fixture.V3Token() - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertFalse(auth_ref.has_service_catalog()) - self.assertNotIn('catalog', auth_ref._data['token']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(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) - - self.assertFalse(auth_ref.domain_scoped) - self.assertFalse(auth_ref.project_scoped) - self.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(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( - token['token']['expires_at'])) - self.assertEqual(auth_ref.issued, timeutils.parse_isotime( - token['token']['issued_at'])) - - self.assertEqual(auth_ref.expires, token.expires) - self.assertEqual(auth_ref.issued, token.issued) - - self.assertEqual(auth_ref.audit_id, token.audit_id) - self.assertIsNone(auth_ref.audit_chain_id) - self.assertIsNone(token.audit_chain_id) - self.assertIsNone(auth_ref.bind) - - def test_will_expire_soon(self): - expires = timeutils.utcnow() + datetime.timedelta(minutes=5) - token = fixture.V3Token(expires=expires) - auth_ref = access.create(body=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): - token = fixture.V3Token() - token.set_domain_scope() - - s = token.add_service(type='identity') - s.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertTrue(auth_ref) - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertEqual(token.domain_name, auth_ref.domain_name) - self.assertEqual(token.domain_id, auth_ref.domain_id) - - self.assertIsNone(auth_ref.project_name) - self.assertIsNone(auth_ref.project_id) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(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.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_building_project_scoped_accessinfo(self): - token = fixture.V3Token() - token.set_project_scope() - - s = token.add_service(type='identity') - s.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertIsNone(auth_ref.domain_name) - self.assertIsNone(auth_ref.domain_id) - - self.assertEqual(token.project_name, auth_ref.project_name) - self.assertEqual(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) - - self.assertEqual(token.project_domain_id, auth_ref.project_domain_id) - self.assertEqual(token.project_domain_name, - auth_ref.project_domain_name) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertFalse(auth_ref.domain_scoped) - self.assertTrue(auth_ref.project_scoped) - self.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_building_project_as_domain_scoped_accessinfo(self): - token = fixture.V3Token() - token.set_project_scope(is_domain=True) - - service = token.add_service(type='identity') - service.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertIsNone(auth_ref.domain_name) - self.assertIsNone(auth_ref.domain_id) - - self.assertEqual(token.project_name, auth_ref.project_name) - self.assertEqual(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) - - self.assertEqual(token.project_domain_id, auth_ref.project_domain_id) - self.assertEqual(token.project_domain_name, - auth_ref.project_domain_name) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertFalse(auth_ref.domain_scoped) - self.assertTrue(auth_ref.project_scoped) - self.assertTrue(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(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.create(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._data['token']['OS-OAUTH1']['consumer_id']) - self.assertEqual( - access_token_id, - auth_ref._data['token']['OS-OAUTH1']['access_token_id']) - - 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.create(body=token) - self.assertFalse(auth_ref.is_federated) - - def test_binding(self): - token = fixture.V3Token() - principal = uuid.uuid4().hex - token.set_bind('kerberos', principal) - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - - self.assertEqual({'kerberos': principal}, auth_ref.bind) - - def test_is_admin_project_unset(self): - token = fixture.V3Token() - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(True, auth_ref.is_admin_project) - - def test_is_admin_project_true(self): - token = fixture.V3Token(is_admin_project=True) - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(True, auth_ref.is_admin_project) - - def test_is_admin_project_false(self): - token = fixture.V3Token(is_admin_project=False) - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(False, auth_ref.is_admin_project) diff --git a/keystoneauth1/tests/unit/access/test_v3_service_catalog.py b/keystoneauth1/tests/unit/access/test_v3_service_catalog.py deleted file mode 100644 index 713586c..0000000 --- a/keystoneauth1/tests/unit/access/test_v3_service_catalog.py +++ /dev/null @@ -1,392 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class ServiceCatalogTest(utils.TestCase): - def setUp(self): - super(ServiceCatalogTest, self).setUp() - - self.AUTH_RESPONSE_BODY = fixture.V3Token( - audit_chain_id=uuid.uuid4().hex) - self.AUTH_RESPONSE_BODY.set_project_scope() - - self.AUTH_RESPONSE_BODY.add_role(name='admin') - self.AUTH_RESPONSE_BODY.add_role(name='member') - - s = self.AUTH_RESPONSE_BODY.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 = self.AUTH_RESPONSE_BODY.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 = self.AUTH_RESPONSE_BODY.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') - - 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.create(auth_token=uuid.uuid4().hex, - body=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', - interface='internal'), - "https://compute.north.host/novapi/internal") - - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name='South', - service_type='compute') - - def test_service_catalog_endpoints(self): - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - public_ep = sc.get_endpoints(service_type='compute', - interface='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" - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - url = sc.url_for(service_type='image', interface='public') - self.assertEqual(url, "http://glance.north.host/glanceapi/public") - - self.AUTH_RESPONSE_BODY['token']['region_name'] = "South" - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - url = sc.url_for(service_type='image', - region_name="South", - interface='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.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - self.assertRaises(exceptions.EmptyCatalog, - auth_ref.service_catalog.url_for, - service_type='image', - interface='internalURL') - - def test_service_catalog_get_endpoints_region_names(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).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): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).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_service_catalog_get_url_region_names(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).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.assertEqual(len(urls), 0) - - def test_service_catalog_service_name(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - url = sc.url_for(service_name='glance', interface='public', - service_type='image', region_name='North') - self.assertEqual('http://glance.north.host/glanceapi/public', url) - - url = sc.url_for(service_name='glance', interface='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', - interface='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', - interface='public') - - self.assertEqual(0, len(urls)) - - def test_service_catalog_without_name(self): - f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) - - 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) - - pr_auth_ref = access.create(body=f) - 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', - interface='public') - self.assertEqual(url_ref, url) - - ab_auth_ref = access.create(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', - interface='public') - - -class ServiceCatalogV3Test(ServiceCatalogTest): - - def test_building_a_service_catalog(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - self.assertEqual(sc.url_for(service_type='compute'), - 'https://compute.north.host/novapi/public') - self.assertEqual(sc.url_for(service_type='compute', - interface='internal'), - 'https://compute.north.host/novapi/internal') - - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name='South', - service_type='compute') - - def test_service_catalog_endpoints(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - public_ep = sc.get_endpoints(service_type='compute', - interface='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.create(resp=None, body=token) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public') - - self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public', - region_name='region-1') - - self.assertEqual(('public-1', ), urls) - - def test_service_catalog_endpoint_id(self): - token = fixture.V3Token() - token.set_project_scope() - - service_id = uuid.uuid4().hex - endpoint_id = uuid.uuid4().hex - public_url = uuid.uuid4().hex - - s = token.add_service('compute', id=service_id) - s.add_endpoint('public', public_url, id=endpoint_id) - s.add_endpoint('public', uuid.uuid4().hex) - - auth_ref = access.create(body=token) - - # initially assert that we get back all our urls for a simple filter - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public') - self.assertEqual(2, len(urls)) - - # with bad endpoint_id nothing should be found - urls = auth_ref.service_catalog.get_urls(service_type='compute', - endpoint_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual(0, len(urls)) - - # with service_id we get back both public endpoints - urls = auth_ref.service_catalog.get_urls(service_type='compute', - service_id=service_id, - interface='public') - self.assertEqual(2, len(urls)) - - # with service_id and endpoint_id we get back the url we want - urls = auth_ref.service_catalog.get_urls(service_type='compute', - service_id=service_id, - endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - # with service_id and endpoint_id we get back the url we want - urls = auth_ref.service_catalog.get_urls(service_type='compute', - endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - def test_service_catalog_without_service_type(self): - token = fixture.V3Token() - token.set_project_scope() - - public_urls = [] - - for i in range(0, 3): - public_url = uuid.uuid4().hex - public_urls.append(public_url) - - s = token.add_service(uuid.uuid4().hex) - s.add_endpoint('public', public_url) - - auth_ref = access.create(body=token) - urls = auth_ref.service_catalog.get_urls(interface='public') - - self.assertEqual(3, len(urls)) - - for p in public_urls: - self.assertIn(p, urls) diff --git a/keystoneauth1/tests/unit/client_fixtures.py b/keystoneauth1/tests/unit/client_fixtures.py deleted file mode 100644 index e749ac2..0000000 --- a/keystoneauth1/tests/unit/client_fixtures.py +++ /dev/null @@ -1,122 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 as kfixture - - -def project_scoped_token(): - fixture = kfixture.V3Token( - user_id='c4da488862bd435c9e6c0275a0d0e49a', - user_name='exampleuser', - user_domain_id='4e6893b7ba0b4006840c3845660b86ed', - user_domain_name='exampledomain', - expires='2010-11-01T03:32:15-05:00', - project_id='225da22d3ce34b15877ea70b2a575f58', - project_name='exampleproject', - project_domain_id='4e6893b7ba0b4006840c3845660b86ed', - project_domain_name='exampledomain') - - fixture.add_role(id='76e72a', name='admin') - fixture.add_role(id='f4f392', name='member') - - region = 'RegionOne' - tenant = '225da22d3ce34b15877ea70b2a575f58' - - service = fixture.add_service('volume') - service.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) - - service = fixture.add_service('image') - service.add_standard_endpoints(public='http://public.com:9292/v1', - internal='http://internal:9292/v1', - admin='http://admin:9292/v1', - region=region) - - service = fixture.add_service('compute') - service.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) - - service = fixture.add_service('ec2') - service.add_standard_endpoints( - public='http://public.com:8773/services/Cloud', - internal='http://internal:8773/services/Cloud', - admin='http://admin:8773/services/Admin', - region=region) - - service = fixture.add_service('identity') - service.add_standard_endpoints(public='http://public.com:5000/v3', - internal='http://internal:5000/v3', - admin='http://admin:35357/v3', - region=region) - - return fixture - - -def domain_scoped_token(): - fixture = kfixture.V3Token( - user_id='c4da488862bd435c9e6c0275a0d0e49a', - user_name='exampleuser', - user_domain_id='4e6893b7ba0b4006840c3845660b86ed', - user_domain_name='exampledomain', - expires='2010-11-01T03:32:15-05:00', - domain_id='8e9283b7ba0b1038840c3842058b86ab', - domain_name='anotherdomain') - - fixture.add_role(id='76e72a', name='admin') - fixture.add_role(id='f4f392', name='member') - region = 'RegionOne' - - service = fixture.add_service('volume') - service.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) - - service = fixture.add_service('image') - service.add_standard_endpoints(public='http://public.com:9292/v1', - internal='http://internal:9292/v1', - admin='http://admin:9292/v1', - region=region) - - service = fixture.add_service('compute') - service.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) - - service = fixture.add_service('ec2') - service.add_standard_endpoints( - public='http://public.com:8773/services/Cloud', - internal='http://internal:8773/services/Cloud', - admin='http://admin:8773/services/Admin', - region=region) - - service = fixture.add_service('identity') - service.add_standard_endpoints(public='http://public.com:5000/v3', - internal='http://internal:5000/v3', - admin='http://admin:35357/v3', - region=region) - - return fixture - - -AUTH_SUBJECT_TOKEN = '3e2813b7ba0b4006840c3825860b86ed' - -AUTH_RESPONSE_HEADERS = { - 'X-Subject-Token': AUTH_SUBJECT_TOKEN, -} diff --git a/keystoneauth1/tests/unit/data/README b/keystoneauth1/tests/unit/data/README deleted file mode 100644 index e77f01d..0000000 --- a/keystoneauth1/tests/unit/data/README +++ /dev/null @@ -1,7 +0,0 @@ -This directory holds the betamax test cassettes that are pre-generated -for unit testing. This can be removed in the future with a functional -test that stands up a full devstack, records a cassette and then -replays it as part of the test suite. - -Until the functional testing is implemented do not remove this -directory or enclosed files. diff --git a/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json b/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json deleted file mode 100644 index e7b6cd7..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json +++ /dev/null @@ -1 +0,0 @@ -{"auth":{"tenantName": "customer-x", "passwordCredentials": {"username": "joeuser", "password": "secrete"}}} diff --git a/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json b/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json deleted file mode 100644 index 341a93a..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "access":{ - "token":{ - "expires":"2012-02-05T00:00:00", - "id":"887665443383838", - "tenant":{ - "id":"1", - "name":"customer-x" - } - }, - "serviceCatalog":[ - { - "endpoints":[ - { - "adminURL":"http://swift.admin-nets.local:8080/", - "region":"RegionOne", - "internalURL":"http://127.0.0.1:8080/v1/AUTH_1", - "publicURL":"http://swift.publicinternets.com/v1/AUTH_1" - } - ], - "type":"object-store", - "name":"swift" - }, - { - "endpoints":[ - { - "adminURL":"http://cdn.admin-nets.local/v1.1/1", - "region":"RegionOne", - "internalURL":"http://127.0.0.1:7777/v1.1/1", - "publicURL":"http://cdn.publicinternets.com/v1.1/1" - } - ], - "type":"object-store", - "name":"cdn" - } - ], - "user":{ - "id":"1", - "roles":[ - { - "tenantId":"1", - "id":"3", - "name":"Member" - } - ], - "name":"joeuser" - } - } -} diff --git a/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json b/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json deleted file mode 100644 index 8acdaf5..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json +++ /dev/null @@ -1,13 +0,0 @@ -{ "auth": { - "identity": { - "methods": ["password"], - "password": { - "user": { - "name": "admin", - "domain": { "id": "default" }, - "password": "adminpwd" - } - } - } - } -} diff --git a/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json b/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json deleted file mode 100644 index 2defa33..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json +++ /dev/null @@ -1,15 +0,0 @@ -{"token": {"methods": ["password"], "roles": [{"id": -"9fe2ff9ee4384b1894a90878d3e92bab", "name": "_member_"}, {"id": -"c703057be878458588961ce9a0ce686b", "name": "admin"}], "expires_at": -"2014-06-10T2:55:16.806001Z", "project": {"domain": {"id": "default", "name": -"Default"}, "id": "8538a3f13f9541b28c2620eb19065e45", "name": "admin"}, -"catalog": [{"endpoints": [{"url": "http://localhost:3537/v2.0", "region": -"RegionOne", "interface": "admin", "id": "29beb2f1567642eb810b042b6719ea88"}, -{"url": "http://localhost:5000/v2.0", "region": "RegionOne", "interface": -"internal", "id": "8707e3735d4415c97ae231b4841eb1c"}, {"url": -"http://localhost:5000/v2.0", "region": "RegionOne", "interface": "public", -"id": "ef303187fc8d41668f25199c298396a5"}], "type": "identity", "id": -"bd73972c0e14fb69bae8ff76e112a90", "name": "keystone"}], "extras": {}, -"user": {"domain": {"id": "default", "name": "Default"}, "id": -"3ec3164f750146be97f21559ee4d9c51", "name": "admin"}, "audit_ids": -["yRt0UrxJSs6-WYJgwEMMmg"], "issued_at": "201406-10T20:55:16.806027Z"}} diff --git a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml b/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml deleted file mode 100644 index 5793652..0000000 --- a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml +++ /dev/null @@ -1,92 +0,0 @@ -http_interactions: -- request: - body: - string: |- - { - "auth": { - "tenantName": "test_tenant_name", - "passwordCredentials": { - "username": "test_user_name", - "password": "test_password" - } - } - } - encoding: utf-8 - headers: - Content-Length: - - '128' - Accept-Encoding: - - gzip, deflate - Accept: - - application/json - User-Agent: - - keystoneauth1 - Connection: - - keep-alive - Content-Type: - - application/json - method: POST - uri: http://keystoneauth-betamax.test/v2.0/tokens - response: - body: - string: |- - { - "access": { - "token": { - "issued_at": "2015-11-27T15:17:19.755470", - "expires": "2015-11-27T16:17:19Z", - "id": "c000c5ee4ba04594a00886028584b50d", - "tenant": { - "enabled": true, - "description": null, - "name": "test_tenant_name", - "id": "6932cad596634a61ac9c759fb91beef1" - }, - "audit_ids": [ - "jY3gYg_YTbmzY2a4ioGuCw" - ] - }, - "user": { - "username": "test_user_name", - "roles_links": [], - "id": "96995e6cc15b40fa8e7cd762f6a5d4c0", - "roles": [ - { - "name": "_member_" - } - ], - "name": "67eff5f6-9477-4961-88b4-437e6596a795" - }, - "metadata": { - "is_admin": 0, - "roles": [ - "9fe2ff9ee4384b1894a90878d3e92bab" - ] - } - } - } - encoding: null - headers: - X-Openstack-Request-Id: - - req-f9e188b4-06fd-4a4c-a952-2315b368218c - Content-Length: - - '2684' - Connection: - - keep-alive - Date: - - Fri, 27 Nov 2015 15:17:19 GMT - Content-Type: - - application/json - Vary: - - X-Auth-Token - X-Distribution: - - Ubuntu - Server: - - Fake - status: - message: OK - code: 200 - url: http://keystoneauth-betamax.test/v2.0/tokens - recorded_at: '2015-11-27T15:17:19' -recorded_with: betamax/0.5.1 - diff --git a/keystoneauth1/tests/unit/data/ksa_serializer_data.json b/keystoneauth1/tests/unit/data/ksa_serializer_data.json deleted file mode 100644 index 59a2ddc..0000000 --- a/keystoneauth1/tests/unit/data/ksa_serializer_data.json +++ /dev/null @@ -1 +0,0 @@ -{"http_interactions": [{"request": {"body": {"string": "{\"auth\": {\"tenantName\": \"test_tenant_name\", \"passwordCredentials\": {\"username\": \"test_user_name\", \"password\": \"test_password\"}}}", "encoding": "utf-8"}, "headers": {"Content-Length": ["128"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/json"], "User-Agent": ["keystoneauth1"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "POST", "uri": "http://keystoneauth-betamax.test/v2.0/tokens"}, "response": {"body": {"string": "{\"access\": {\"token\": {\"issued_at\": \"2015-11-27T15:17:19.755470\", \"expires\": \"2015-11-27T16:17:19Z\", \"id\": \"c000c5ee4ba04594a00886028584b50d\", \"tenant\": {\"description\": null, \"enabled\": true, \"id\": \"6932cad596634a61ac9c759fb91beef1\", \"name\": \"test_tenant_name\"}, \"audit_ids\": [\"jY3gYg_YTbmzY2a4ioGuCw\"]}, \"user\": {\"username\": \"test_user_name\", \"roles_links\": [], \"id\": \"96995e6cc15b40fa8e7cd762f6a5d4c0\", \"roles\": [{\"name\": \"_member_\"}], \"name\": \"67eff5f6-9477-4961-88b4-437e6596a795\"}, \"metadata\": {\"is_admin\": 0, \"roles\": [\"9fe2ff9ee4384b1894a90878d3e92bab\"]}}}", "encoding": null}, "headers": {"X-Openstack-Request-Id": ["req-f9e188b4-06fd-4a4c-a952-2315b368218c"], "Content-Length": ["2684"], "Connection": ["keep-alive"], "Date": ["Fri, 27 Nov 2015 15:17:19 GMT"], "Content-Type": ["application/json"], "Vary": ["X-Auth-Token"], "X-Distribution": ["Ubuntu"], "Server": ["Fake"]}, "status": {"message": "OK", "code": 200}, "url": "http://keystoneauth-betamax.test/v2.0/tokens"}, "recorded_at": "2015-11-27T15:17:19"}], "recorded_with": "betamax/0.5.1"} diff --git a/keystoneauth1/tests/unit/data/test_pre_record_hook.json b/keystoneauth1/tests/unit/data/test_pre_record_hook.json deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/exceptions/__init__.py b/keystoneauth1/tests/unit/exceptions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/exceptions/test_exceptions.py b/keystoneauth1/tests/unit/exceptions/test_exceptions.py deleted file mode 100644 index 341ef57..0000000 --- a/keystoneauth1/tests/unit/exceptions/test_exceptions.py +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.tests.unit import utils - - -class ExceptionTests(utils.TestCase): - - def test_clientexception_with_message(self): - test_message = 'Unittest exception message.' - exc = exceptions.ClientException(message=test_message) - self.assertEqual(test_message, exc.message) - - def test_clientexception_with_no_message(self): - exc = exceptions.ClientException() - self.assertEqual(exceptions.ClientException.__name__, - exc.message) - - def test_using_default_message(self): - exc = exceptions.AuthorizationFailure() - self.assertEqual(exceptions.AuthorizationFailure.message, - exc.message) diff --git a/keystoneauth1/tests/unit/extras/__init__.py b/keystoneauth1/tests/unit/extras/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/kerberos/__init__.py b/keystoneauth1/tests/unit/extras/kerberos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/kerberos/base.py b/keystoneauth1/tests/unit/extras/kerberos/base.py deleted file mode 100644 index f76d148..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/base.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2010-2011 OpenStack Foundation -# 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. - -from keystoneauth1.tests.unit.extras.kerberos import utils -from keystoneauth1.tests.unit import utils as test_utils - - -REQUEST = {'auth': {'identity': {'methods': ['kerberos'], - 'kerberos': {}}}} - - -class TestCase(test_utils.TestCase): - """Test case base class for Kerberos unit tests.""" - - TEST_V3_URL = test_utils.TestCase.TEST_ROOT_URL + 'v3' - - def setUp(self): - super(TestCase, self).setUp() - - km = utils.KerberosMock(self.requests_mock) - self.kerberos_mock = self.useFixture(km) - - def assertRequestBody(self, body=None): - """Ensure the request body is the standard Kerberos auth request. - - :param dict body: the body to compare. If not provided the last request - body will be used. - """ - if not body: - body = self.requests_mock.last_request.json() - - self.assertEqual(REQUEST, body) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py b/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py deleted file mode 100644 index 874543c..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 loading -from keystoneauth1.tests.unit import utils as test_utils - - -class FedKerbLoadingTests(test_utils.TestCase): - - def test_options(self): - opts = [o.name for o in - loading.get_plugin_loader('v3fedkerb').get_options()] - - allowed_opts = ['domain-id', - 'domain-name', - 'identity-provider', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'protocol', - 'trust-id', - 'auth-url', - ] - - self.assertItemsEqual(allowed_opts, opts) - - def create(self, **kwargs): - loader = loading.get_plugin_loader('v3fedkerb') - return loader.load_from_options(**kwargs) - - def test_load_none(self): - self.assertRaises(exceptions.MissingRequiredOptions, self.create) - - def test_load(self): - self.create(auth_url='auth_url', - identity_provider='idp', - protocol='protocol') diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py b/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py deleted file mode 100644 index e4da307..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 loading -from keystoneauth1.tests.unit import utils as test_utils - - -class KerberosLoadingTests(test_utils.TestCase): - - def test_options(self): - opts = [o.name for o in - loading.get_plugin_loader('v3kerberos').get_options()] - - allowed_opts = ['domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - ] - - self.assertItemsEqual(allowed_opts, opts) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py b/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py deleted file mode 100644 index a5fa41c..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py +++ /dev/null @@ -1,77 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras import kerberos -from keystoneauth1 import fixture as ks_fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.kerberos import base - - -class TestMappedAuth(base.TestCase): - - def setUp(self): - if kerberos.requests_kerberos is None: - self.skipTest("Kerberos support isn't available.") - - super(TestMappedAuth, self).setUp() - - self.protocol = uuid.uuid4().hex - self.identity_provider = uuid.uuid4().hex - - @property - def token_url(self): - fmt = '%s/OS-FEDERATION/identity_providers/%s/protocols/%s/auth' - return fmt % ( - self.TEST_V3_URL, - self.identity_provider, - self.protocol) - - def test_unscoped_mapped_auth(self): - token_id, _ = self.kerberos_mock.mock_auth_success( - url=self.token_url, method='GET') - - plugin = kerberos.MappedKerberos( - auth_url=self.TEST_V3_URL, protocol=self.protocol, - identity_provider=self.identity_provider) - - sess = session.Session() - tok = plugin.get_token(sess) - - self.assertEqual(token_id, tok) - - def test_project_scoped_mapped_auth(self): - self.kerberos_mock.mock_auth_success(url=self.token_url, - method='GET') - - scoped_id = uuid.uuid4().hex - scoped_body = ks_fixture.V3Token() - scoped_body.set_project_scope() - - self.requests_mock.post( - '%s/auth/tokens' % self.TEST_V3_URL, - json=scoped_body, - headers={'X-Subject-Token': scoped_id, - 'Content-Type': 'application/json'}) - - plugin = kerberos.MappedKerberos( - auth_url=self.TEST_V3_URL, protocol=self.protocol, - identity_provider=self.identity_provider, - project_id=scoped_body.project_id) - - sess = session.Session() - tok = plugin.get_token(sess) - proj = plugin.get_project_id(sess) - - self.assertEqual(scoped_id, tok) - self.assertEqual(scoped_body.project_id, proj) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_v3.py b/keystoneauth1/tests/unit/extras/kerberos/test_v3.py deleted file mode 100644 index 1d6e5e9..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_v3.py +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.extras import kerberos -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.kerberos import base - - -class TestKerberosAuth(base.TestCase): - - def setUp(self): - if kerberos.requests_kerberos is None: - self.skipTest("Kerberos support isn't available.") - - super(TestKerberosAuth, self).setUp() - - def test_authenticate_with_kerberos_domain_scoped(self): - token_id, token_body = self.kerberos_mock.mock_auth_success() - - a = kerberos.Kerberos(self.TEST_ROOT_URL + 'v3') - s = session.Session(a) - token = a.get_token(s) - - self.assertRequestBody() - self.assertEqual( - self.kerberos_mock.challenge_header, - self.requests_mock.last_request.headers['Authorization']) - self.assertEqual(token_id, a.auth_ref.auth_token) - self.assertEqual(token_id, token) diff --git a/keystoneauth1/tests/unit/extras/kerberos/utils.py b/keystoneauth1/tests/unit/extras/kerberos/utils.py deleted file mode 100644 index 78513c3..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/utils.py +++ /dev/null @@ -1,83 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 -try: - # requests_kerberos won't be available on py3, it doesn't work with py3. - import requests_kerberos -except ImportError: - requests_kerberos = None - -from keystoneauth1 import fixture as ks_fixture -from keystoneauth1.tests.unit import utils as test_utils - - -class KerberosMock(fixtures.Fixture): - - def __init__(self, requests_mock): - super(KerberosMock, self).__init__() - - self.challenge_header = 'Negotiate %s' % uuid.uuid4().hex - self.pass_header = 'Negotiate %s' % uuid.uuid4().hex - self.requests_mock = requests_mock - - def setUp(self): - super(KerberosMock, self).setUp() - - if requests_kerberos is None: - return - - m = fixtures.MockPatchObject(requests_kerberos.HTTPKerberosAuth, - 'generate_request_header', - self._generate_request_header) - - self.header_fixture = self.useFixture(m) - - m = fixtures.MockPatchObject(requests_kerberos.HTTPKerberosAuth, - 'authenticate_server', - self._authenticate_server) - - self.authenticate_fixture = self.useFixture(m) - - def _generate_request_header(self, *args, **kwargs): - return self.challenge_header - - def _authenticate_server(self, response): - return response.headers.get('www-authenticate') == self.pass_header - - def mock_auth_success( - self, - token_id=None, - token_body=None, - method='POST', - url=test_utils.TestCase.TEST_ROOT_URL + 'v3/auth/tokens'): - if not token_id: - token_id = uuid.uuid4().hex - if not token_body: - token_body = ks_fixture.V3Token() - - response_list = [{'text': 'Fail', - 'status_code': 401, - 'headers': {'WWW-Authenticate': 'Negotiate'}}, - {'headers': {'X-Subject-Token': token_id, - 'Content-Type': 'application/json', - 'WWW-Authenticate': self.pass_header}, - 'status_code': 200, - 'json': token_body}] - - self.requests_mock.register_uri(method, - url, - response_list=response_list) - - return token_id, token_body diff --git a/keystoneauth1/tests/unit/extras/oauth1/__init__.py b/keystoneauth1/tests/unit/extras/oauth1/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py b/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py deleted file mode 100644 index 482c155..0000000 --- a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py +++ /dev/null @@ -1,117 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 oauthlib import oauth1 -import six -from testtools import matchers - -from keystoneauth1.extras import oauth1 as ksa_oauth1 -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils as test_utils - - -class OAuth1AuthTests(test_utils.TestCase): - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_TOKEN = uuid.uuid4().hex - - 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 _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'], six.string_types) - 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 - - 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 - - oauth_token = fixture.V3Token(methods=['oauth1'], - oauth_consumer_id=consumer_key, - oauth_access_token_id=access_key) - oauth_token.set_project_scope() - - self.stub_auth(json=oauth_token) - - a = ksa_oauth1.V3OAuth1(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) - - def test_warning_dual_scope(self): - ksa_oauth1.V3OAuth1(self.TEST_URL, - consumer_key=uuid.uuid4().hex, - consumer_secret=uuid.uuid4().hex, - access_key=uuid.uuid4().hex, - access_secret=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - self.assertIn('ignored by the identity server', self.logger.output) diff --git a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py b/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py deleted file mode 100644 index ef3ffc0..0000000 --- a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py +++ /dev/null @@ -1,57 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 loading -from keystoneauth1.tests.unit import utils as test_utils - - -class OAuth1LoadingTests(test_utils.TestCase): - - def setUp(self): - super(OAuth1LoadingTests, self).setUp() - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3oauth1') - return loader.load_from_options(**kwargs) - - def test_basic(self): - access_key = uuid.uuid4().hex - access_secret = uuid.uuid4().hex - consumer_key = uuid.uuid4().hex - consumer_secret = uuid.uuid4().hex - - p = self.create(access_key=access_key, - access_secret=access_secret, - consumer_key=consumer_key, - consumer_secret=consumer_secret) - - oauth_method = p.auth_methods[0] - - self.assertEqual(self.auth_url, p.auth_url) - self.assertEqual(access_key, oauth_method.access_key) - self.assertEqual(access_secret, oauth_method.access_secret) - self.assertEqual(consumer_key, oauth_method.consumer_key) - self.assertEqual(consumer_secret, oauth_method.consumer_secret) - - def test_options(self): - options = loading.get_plugin_loader('v3oauth1').get_options() - - self.assertEqual(set([o.name for o in options]), - set(['auth-url', - 'access-key', - 'access-secret', - 'consumer-key', - 'consumer-secret'])) diff --git a/keystoneauth1/tests/unit/extras/saml2/__init__.py b/keystoneauth1/tests/unit/extras/saml2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml b/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml deleted file mode 100644 index 487bcac..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - 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 - - - - \ No newline at end of file diff --git a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml b/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml deleted file mode 100644 index 913252e..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - 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. - - - - \ No newline at end of file diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py b/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py deleted file mode 100644 index 59d87c0..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py +++ /dev/null @@ -1,120 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 string - -DIR = os.path.dirname(os.path.abspath(__file__)) - - -def template(f, **kwargs): - with open(os.path.join(DIR, 'templates', f)) as f: - return string.Template(f.read()).substitute(**kwargs) - - -def soap_response(**kwargs): - kwargs.setdefault('provider', 'https://idp.testshib.org/idp/shibboleth') - kwargs.setdefault('consumer', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - kwargs.setdefault('issuer', 'https://openstack4.local/shibboleth') - return template('soap_response.xml', **kwargs).encode('utf-8') - - -def saml_assertion(**kwargs): - kwargs.setdefault('issuer', 'https://idp.testshib.org/idp/shibboleth') - kwargs.setdefault('destination', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - return template('saml_assertion.xml', **kwargs).encode('utf-8') - - -def authn_request(**kwargs): - kwargs.setdefault('issuer', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - return template('authn_request.xml', **kwargs).encode('utf-8') - - -SP_SOAP_RESPONSE = soap_response() -SAML2_ASSERTION = saml_assertion() -AUTHN_REQUEST = authn_request() - -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/OS-FEDERATION/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/OS-FEDERATION/domains", - "previous": 'null', - "next": 'null' - } -} diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml deleted file mode 100644 index f5a1c8d..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - $issuer - - - - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml deleted file mode 100644 index 1306937..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - x= - - - - - - $issuer - - - - - - - - - - - - - - - - - - - - - VALUE== - - - - - - - VALUE= - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml deleted file mode 100644 index 879e5f2..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - $issuer - - - - - - - ss:mem:6f1f20fafbb38433467e9d477df67615 - - - - - - $issuer - - - - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py b/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py deleted file mode 100644 index 8ff6844..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py +++ /dev/null @@ -1,261 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 lxml import etree -from six.moves import urllib - -from keystoneauth1 import exceptions -from keystoneauth1.extras import _saml2 as saml2 -from keystoneauth1.tests.unit import client_fixtures -from keystoneauth1.tests.unit.extras.saml2 import fixtures as saml2_fixtures -from keystoneauth1.tests.unit.extras.saml2 import utils -from keystoneauth1.tests.unit import matchers - - -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') - - TEST_TOKEN = uuid.uuid4().hex - - PROTOCOL = 'saml2' - - @property - def _uuid4(self): - return '4b911420-4982-4009-8afc-5c596cd487f5' - - def setUp(self): - super(AuthenticateviaADFSTests, self).setUp() - - 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.SP_ENTITYID = 'https://openstack4.local' - - self.adfsplugin = saml2.V3ADFSPassword( - self.TEST_URL, self.IDENTITY_PROVIDER, - self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, - self.TEST_USER, self.TEST_TOKEN, self.PROTOCOL) - - self.ADFS_SECURITY_TOKEN_RESPONSE = utils._load_xml( - 'ADFS_RequestSecurityTokenResponse.xml') - self.ADFS_FAULT = utils._load_xml('ADFS_fault.xml') - - def test_get_adfs_security_token(self): - """Test ADFSPassword._get_adfs_security_token().""" - self.requests_mock.post( - self.IDENTITY_PROVIDER_URL, - content=utils.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.assertThat(fixture_response, - matchers.XMLEquals(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_adfs_request_custom_endpointreference(self): - self.adfsplugin = saml2.V3ADFSPassword( - self.TEST_URL, self.IDENTITY_PROVIDER, - self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, - self.TEST_USER, self.TEST_TOKEN, self.PROTOCOL, self.SP_ENTITYID) - self.adfsplugin._prepare_adfs_request() - address = self.adfsplugin.prepared_request.xpath( - self.ADDRESS_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.SP_ENTITYID, address.text) - - def test_prepare_sp_request(self): - assertion = etree.XML(self.ADFS_SECURITY_TOKEN_RESPONSE) - assertion = assertion.xpath( - saml2.V3ADFSPassword.ADFS_ASSERTION_XPATH, - namespaces=saml2.V3ADFSPassword.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. - """ - content = utils.make_oneline(self.ADFS_FAULT) - self.requests_mock.register_uri('POST', - self.IDENTITY_PROVIDER_URL, - content=content, - 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, keystoneauth1 should be prepared - and correctly raise exceptions.InternalServerError once it cannot - parse XML fault message - """ - self.requests_mock.register_uri('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_mock methods. - 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.register_uri('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): - # clean cookie jar - self.session.session.cookies = [] - - self.assertRaises(exceptions.AuthorizationFailure, - self.adfsplugin._access_service_provider, - self.session) - - def test_check_valid_token_when_authenticated(self): - self.requests_mock.register_uri( - 'GET', self.FEDERATION_AUTH_URL, - json=saml2_fixtures.UNSCOPED_TOKEN, - headers=client_fixtures.AUTH_RESPONSE_HEADERS) - - self.session.session.cookies = [object()] - 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.register_uri( - 'POST', self.IDENTITY_PROVIDER_URL, - content=self.ADFS_SECURITY_TOKEN_RESPONSE, - status_code=200) - self.requests_mock.register_uri( - 'POST', self.SP_ENDPOINT, - headers={"set-cookie": 'x'}, - status_code=302) - self.requests_mock.register_uri( - '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. - self.session.session.cookies = [object()] - token = self.adfsplugin.get_auth_ref(self.session) - self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) diff --git a/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py b/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py deleted file mode 100644 index a1800a7..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py +++ /dev/null @@ -1,312 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 uuid - -import requests - -from keystoneauth1 import exceptions -from keystoneauth1.extras import _saml2 as saml2 -from keystoneauth1 import fixture as ksa_fixtures -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.saml2 import fixtures as saml2_fixtures -from keystoneauth1.tests.unit.extras.saml2 import utils -from keystoneauth1.tests.unit import matchers - -PAOS_HEADER = 'application/vnd.paos+xml' -CONTENT_TYPE_PAOS_HEADER = {'Content-Type': PAOS_HEADER} -InvalidResponse = saml2.v3.saml2.InvalidResponse - - -class SamlAuth2PluginTests(utils.TestCase): - """These test ONLY the standalone requests auth plugin. - - Tests for the auth plugin are later so that hopefully these can be - extracted into it's own module. - """ - - HEADER_MEDIA_TYPE_SEPARATOR = ',' - - TEST_USER = 'user' - TEST_PASS = 'pass' - TEST_SP_URL = 'http://sp.test' - TEST_IDP_URL = 'http://idp.test' - TEST_CONSUMER_URL = "https://openstack4.local/Shibboleth.sso/SAML2/ECP" - - def get_plugin(self, **kwargs): - kwargs.setdefault('identity_provider_url', self.TEST_IDP_URL) - kwargs.setdefault('requests_auth', (self.TEST_USER, self.TEST_PASS)) - return saml2.v3.saml2._SamlAuth(**kwargs) - - @property - def calls(self): - return [r.url.strip('/') for r in self.requests_mock.request_history] - - def basic_header(self, username=TEST_USER, password=TEST_PASS): - user_pass = ('%s:%s' % (username, password)).encode('utf-8') - return 'Basic %s' % base64.b64encode(user_pass).decode('utf-8') - - def test_request_accept_headers(self): - # Include some random Accept header - random_header = uuid.uuid4().hex - headers = {'Accept': random_header} - req = requests.Request('GET', 'http://another.test', headers=headers) - - plugin = self.get_plugin() - plugin_headers = plugin(req).headers - self.assertIn('Accept', plugin_headers) - - # Since we have included a random Accept header, the plugin should have - # added the PAOS_HEADER to it using the correct media type separator - accept_header = plugin_headers['Accept'] - self.assertIn(self.HEADER_MEDIA_TYPE_SEPARATOR, accept_header) - self.assertIn(random_header, - accept_header.split(self.HEADER_MEDIA_TYPE_SEPARATOR)) - self.assertIn(PAOS_HEADER, - accept_header.split(self.HEADER_MEDIA_TYPE_SEPARATOR)) - - def test_passed_when_not_200(self): - text = uuid.uuid4().hex - test_url = 'http://another.test' - self.requests_mock.get(test_url, - status_code=201, - headers=CONTENT_TYPE_PAOS_HEADER, - text=text) - - resp = requests.get(test_url, auth=self.get_plugin()) - self.assertEqual(201, resp.status_code) - self.assertEqual(text, resp.text) - - def test_200_without_paos_header(self): - text = uuid.uuid4().hex - test_url = 'http://another.test' - self.requests_mock.get(test_url, status_code=200, text=text) - - resp = requests.get(test_url, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - def test_standard_workflow_302_redirect(self): - text = uuid.uuid4().hex - - self.requests_mock.get(self.TEST_SP_URL, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(text=text) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=302, - headers={'Location': self.TEST_SP_URL}) - - resp = requests.get(self.TEST_SP_URL, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - self.assertEqual(self.calls, [self.TEST_SP_URL, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.TEST_SP_URL]) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_standard_workflow_303_redirect(self): - text = uuid.uuid4().hex - - self.requests_mock.get(self.TEST_SP_URL, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(text=text) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=303, - headers={'Location': self.TEST_SP_URL}) - - resp = requests.get(self.TEST_SP_URL, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - url_flow = [self.TEST_SP_URL, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.TEST_SP_URL] - - self.assertEqual(url_flow, [r.url.rstrip('/') for r in resp.history]) - self.assertEqual(url_flow, self.calls) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_initial_sp_call_invalid_response(self): - """Send initial SP HTTP request and receive wrong server response.""" - self.requests_mock.get(self.TEST_SP_URL, - headers=CONTENT_TYPE_PAOS_HEADER, - text='NON XML RESPONSE') - - self.assertRaises(InvalidResponse, - requests.get, - self.TEST_SP_URL, - auth=self.get_plugin()) - - self.assertEqual(self.calls, [self.TEST_SP_URL]) - - def test_consumer_mismatch_error_workflow(self): - consumer1 = 'http://consumer1/Shibboleth.sso/SAML2/ECP' - consumer2 = 'http://consumer2/Shibboleth.sso/SAML2/ECP' - soap_response = saml2_fixtures.soap_response(consumer=consumer1) - saml_assertion = saml2_fixtures.saml_assertion(destination=consumer2) - - self.requests_mock.get(self.TEST_SP_URL, - headers=CONTENT_TYPE_PAOS_HEADER, - content=soap_response) - - self.requests_mock.post(self.TEST_IDP_URL, content=saml_assertion) - - # receive the SAML error, body unchecked - saml_error = self.requests_mock.post(consumer1) - - self.assertRaises(saml2.v3.saml2.ConsumerMismatch, - requests.get, - self.TEST_SP_URL, - auth=self.get_plugin()) - - self.assertTrue(saml_error.called) - - -class AuthenticateviaSAML2Tests(utils.TestCase): - - TEST_USER = 'user' - TEST_PASS = 'pass' - TEST_IDP = 'tester' - TEST_PROTOCOL = 'saml2' - TEST_AUTH_URL = 'http://keystone.test:5000/v3/' - - TEST_IDP_URL = 'https://idp.test' - TEST_CONSUMER_URL = "https://openstack4.local/Shibboleth.sso/SAML2/ECP" - - def get_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.TEST_AUTH_URL) - kwargs.setdefault('username', self.TEST_USER) - kwargs.setdefault('password', self.TEST_PASS) - kwargs.setdefault('identity_provider', self.TEST_IDP) - kwargs.setdefault('identity_provider_url', self.TEST_IDP_URL) - kwargs.setdefault('protocol', self.TEST_PROTOCOL) - return saml2.V3Saml2Password(**kwargs) - - def sp_url(self, **kwargs): - kwargs.setdefault('base', self.TEST_AUTH_URL.rstrip('/')) - kwargs.setdefault('identity_provider', self.TEST_IDP) - kwargs.setdefault('protocol', self.TEST_PROTOCOL) - - templ = ('%(base)s/OS-FEDERATION/identity_providers/' - '%(identity_provider)s/protocols/%(protocol)s/auth') - return templ % kwargs - - @property - def calls(self): - return [r.url.strip('/') for r in self.requests_mock.request_history] - - def basic_header(self, username=TEST_USER, password=TEST_PASS): - user_pass = ('%s:%s' % (username, password)).encode('utf-8') - return 'Basic %s' % base64.b64encode(user_pass).decode('utf-8') - - def setUp(self): - super(AuthenticateviaSAML2Tests, self).setUp() - self.session = session.Session() - self.default_sp_url = self.sp_url() - - def test_workflow(self): - token_id = uuid.uuid4().hex - token = ksa_fixtures.V3Token() - - self.requests_mock.get(self.default_sp_url, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(headers={'X-Subject-Token': token_id}, json=token) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=302, - headers={'Location': self.sp_url()}) - - auth_ref = self.get_plugin().get_auth_ref(self.session) - - self.assertEqual(token_id, auth_ref.auth_token) - - self.assertEqual(self.calls, [self.default_sp_url, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.default_sp_url]) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_consumer_mismatch_error_workflow(self): - consumer1 = 'http://keystone.test/Shibboleth.sso/SAML2/ECP' - consumer2 = 'http://consumer2/Shibboleth.sso/SAML2/ECP' - - soap_response = saml2_fixtures.soap_response(consumer=consumer1) - saml_assertion = saml2_fixtures.saml_assertion(destination=consumer2) - - self.requests_mock.get(self.default_sp_url, - headers=CONTENT_TYPE_PAOS_HEADER, - content=soap_response) - - self.requests_mock.post(self.TEST_IDP_URL, content=saml_assertion) - - # receive the SAML error, body unchecked - saml_error = self.requests_mock.post(consumer1) - - self.assertRaises(exceptions.AuthorizationFailure, - self.get_plugin().get_auth_ref, - self.session) - - self.assertTrue(saml_error.called) - - def test_initial_sp_call_invalid_response(self): - """Send initial SP HTTP request and receive wrong server response.""" - self.requests_mock.get(self.default_sp_url, - headers=CONTENT_TYPE_PAOS_HEADER, - text='NON XML RESPONSE') - - self.assertRaises(exceptions.AuthorizationFailure, - self.get_plugin().get_auth_ref, - self.session) - - self.assertEqual(self.calls, [self.default_sp_url]) diff --git a/keystoneauth1/tests/unit/extras/saml2/utils.py b/keystoneauth1/tests/unit/extras/saml2/utils.py deleted file mode 100644 index d338c7e..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 lxml import etree - -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - -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 f.read() - - -class TestCase(utils.TestCase): - - TEST_URL = 'https://keystone:5000/v3' - - def setUp(self): - super(TestCase, self).setUp() - self.session = session.Session() diff --git a/keystoneauth1/tests/unit/identity/__init__.py b/keystoneauth1/tests/unit/identity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/identity/test_access.py b/keystoneauth1/tests/unit/identity/test_access.py deleted file mode 100644 index 15eaba3..0000000 --- a/keystoneauth1/tests/unit/identity/test_access.py +++ /dev/null @@ -1,76 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 access -from keystoneauth1 import fixture -from keystoneauth1.identity import access as access_plugin -from keystoneauth1 import plugin -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class AccessInfoPluginTests(utils.TestCase): - - def setUp(self): - super(AccessInfoPluginTests, self).setUp() - 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.create(body=token, auth_token=self.auth_token) - return access_plugin.AccessInfoPlugin(auth_ref, **kwargs) - - def test_auth_ref(self): - plugin_obj = self._plugin() - self.assertEqual(self.TEST_ROOT_URL, - plugin_obj.get_endpoint(self.session, - service_type='identity', - interface='public')) - self.assertEqual(self.auth_token, plugin_obj.get_token(session)) - - def test_auth_url(self): - auth_url = 'http://keystone.test.url' - obj = self._plugin(auth_url=auth_url) - - self.assertEqual(auth_url, - obj.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) - - def test_project_auth_properties(self): - plugin = self._plugin() - auth_ref = plugin.auth_ref - - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.project_id) - self.assertIsNone(auth_ref.project_name) - - def test_domain_auth_properties(self): - plugin = self._plugin() - auth_ref = plugin.auth_ref - - self.assertIsNone(auth_ref.domain_id) - self.assertIsNone(auth_ref.domain_name) diff --git a/keystoneauth1/tests/unit/identity/test_identity_common.py b/keystoneauth1/tests/unit/identity/test_identity_common.py deleted file mode 100644 index fb7e4ef..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_common.py +++ /dev/null @@ -1,1681 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 collections -import uuid - -import six -from six.moves import urllib - -from keystoneauth1 import _utils -from keystoneauth1 import access -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1 import plugin -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - -_Endpoints = collections.namedtuple( - 'ServiceVersion', - 'public, internal, admin') - -_ServiceVersion = collections.namedtuple( - 'ServiceVersion', - 'discovery, service') - - -class FakeServiceEndpoints(object): - def __init__(self, base_url, versions=None, project_id=None, **kwargs): - self.base_url = base_url - self._interfaces = {} - for interface in ('public', 'internal', 'admin'): - if interface in kwargs and not kwargs[interface]: - self._interfaces[interface] = False - else: - self._interfaces[interface] = True - - self.versions = {} - self.unversioned = self._make_urls() - if not versions: - self.catalog = self.unversioned - else: - self.catalog = self._make_urls(versions[0], project_id) - for version in versions: - self.versions[version] = _ServiceVersion( - self._make_urls(version), - self._make_urls(version, project_id), - ) - - def _make_urls(self, *parts): - return _Endpoints( - self._make_url('public', *parts), - self._make_url('internal', *parts), - self._make_url('admin', *parts), - ) - - def _make_url(self, interface, *parts): - if not self._interfaces[interface]: - return None - url = urllib.parse.urljoin(self.base_url + '/', interface) - for part in parts: - if part: - url = urllib.parse.urljoin(url + '/', part) - return url - - -@six.add_metaclass(abc.ABCMeta) -class CommonIdentityTests(object): - - PROJECT_ID = uuid.uuid4().hex - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - - TEST_COMPUTE_BASE = 'https://compute.example.com' - TEST_COMPUTE_PUBLIC = TEST_COMPUTE_BASE + '/nova/public' - TEST_COMPUTE_INTERNAL = TEST_COMPUTE_BASE + '/nova/internal' - TEST_COMPUTE_ADMIN = TEST_COMPUTE_BASE + '/nova/admin' - - TEST_VOLUME = FakeServiceEndpoints( - base_url='https://block-storage.example.com', - versions=['v3', 'v2'], project_id=PROJECT_ID) - - TEST_BAREMETAL_BASE = 'https://baremetal.example.com' - TEST_BAREMETAL_INTERNAL = TEST_BAREMETAL_BASE + '/internal' - - TEST_PASS = uuid.uuid4().hex - - def setUp(self): - super(CommonIdentityTests, self).setUp() - - 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. - """ - - @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. - """ - - 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) - - @abc.abstractproperty - 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_provided_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) - - cache = {} - # now either of the two plugins I use, it should not cause a second - # request to the discovery url. - s = session.Session(discovery_cache=cache) - 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) - self.assertIn(self.TEST_COMPUTE_ADMIN, cache.keys()) - - 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) - - filter = {'service_type': 'compute', 'interface': 'admin', - 'version': self.version} - - # create a session and call the endpoint, causing its cache to be set - sess = session.Session() - sess.get('/path', auth=self.create_auth_plugin(), - endpoint_filter=filter) - self.assertIn(self.TEST_COMPUTE_ADMIN, sess._discovery_cache.keys()) - - # now either of the two plugins I use, it should not cause a second - # request to the discovery url. - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - for auth in (a, b): - resp = sess.get('/path', auth=auth, endpoint_filter=filter) - 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. Calling discovery directly should also - # not cause an additional request. - 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_discovery_uses_session_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) - - filter = {'service_type': 'compute', 'interface': 'admin', - 'version': self.version} - - # create a plugin and call the endpoint, causing its cache to be set - plugin = self.create_auth_plugin() - session.Session().get('/path', auth=plugin, endpoint_filter=filter) - self.assertIn(self.TEST_COMPUTE_ADMIN, plugin._discovery_cache.keys()) - - # with the plugin in the session, no more calls to the discovery URL - sess = session.Session(auth=plugin) - for auth in (plugin, self.create_auth_plugin()): - resp = sess.get('/path', auth=auth, endpoint_filter=filter) - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - def test_direct_discovery_provided_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) - - # now either of the two sessions I use, it should not cause a second - # request to the discovery url. Calling discovery directly should also - # not cause an additional request. - sa = session.Session() - sb = session.Session() - discovery_cache = {} - - expected_url = urllib.parse.urljoin(self.TEST_ROOT_URL, '/v2.0') - for sess in (sa, sb): - - disc = discover.get_discovery( - sess, self.TEST_COMPUTE_ADMIN, cache=discovery_cache) - url = disc.url_for(('2', '0')) - - self.assertEqual(expected_url, url) - - self.assertIn(self.TEST_COMPUTE_ADMIN, discovery_cache.keys()) - - 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_direct_discovering_with_no_data(self): - # 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) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # A direct call for discovery should fail - self.assertRaises(exceptions.BadRequest, - discover.get_discovery, s, self.TEST_COMPUTE_ADMIN) - - def test_discovering_with_relative_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3) - - def test_direct_discovering(self): - v2_compute = self.TEST_COMPUTE_ADMIN + '/v2.0' - v3_compute = self.TEST_COMPUTE_ADMIN + '/v3' - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(v2_compute) - disc.add_v3(v3_compute) - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - catalog_url = s.get_endpoint( - service_type='compute', interface='admin') - disc = discover.get_discovery(s, catalog_url) - - url_v2 = disc.url_for(('2', '0')) - url_v3 = disc.url_for(('3', '0')) - - self.assertEqual(v2_compute, url_v2) - self.assertEqual(v3_compute, url_v3) - - # Verify that passing strings and not tuples works - url_v2 = disc.url_for('2.0') - url_v3 = disc.url_for('3.0') - - self.assertEqual(v2_compute, url_v2) - self.assertEqual(v3_compute, url_v3) - - def test_direct_discovering_with_relative_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - catalog_url = s.get_endpoint( - service_type='compute', interface='admin') - disc = discover.get_discovery(s, catalog_url) - - url_v2 = disc.url_for(('2', '0')) - url_v3 = disc.url_for(('3', '0')) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', url_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', url_v3) - - # Verify that passing strings and not tuples works - url_v2 = disc.url_for('2.0') - url_v3 = disc.url_for('3.0') - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', url_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', url_v3) - - def test_discovering_with_relative_anchored_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('/v2.0') - disc.add_v3('/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - # by the nature of urljoin a relative link with a /path gets joined - # back to the root. - self.assertEqual(self.TEST_COMPUTE_BASE + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_BASE + '/v3', endpoint_v3) - - def test_discovering_with_protocol_relative(self): - # strip up to and including the : leaving //host/path - path = self.TEST_COMPUTE_ADMIN[self.TEST_COMPUTE_ADMIN.find(':') + 1:] - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(path + '/v2.0') - disc.add_v3(path + '/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - # ensures that the http is carried over from the lookup url - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3) - - def test_discovering_when_version_missing(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertIsNone(endpoint_v3) - - def test_endpoint_data_no_version(self): - path = self.TEST_COMPUTE_ADMIN[self.TEST_COMPUTE_ADMIN.find(':') + 1:] - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(path + '/v2.0') - disc.add_v3(path + '/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin') - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', data.url) - - def test_endpoint_data_no_version_no_discovery(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - discover_versions=False) - - self.assertEqual(self.TEST_COMPUTE_ADMIN, data.url) - - def test_endpoint_no_version(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint(session=s, - service_type='compute', - interface='admin') - - self.assertEqual(self.TEST_COMPUTE_ADMIN, data) - - def test_endpoint_data_relative_version(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data_v2 = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - min_version=(2, 0), - max_version=(2, discover.LATEST)) - data_v3 = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - min_version=(3, 0), - max_version=(3, discover.LATEST)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', data_v2.url) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', data_v3.url) - - def test_get_versioned_data(self): - v2_compute = self.TEST_COMPUTE_ADMIN + '/v2.0' - v3_compute = self.TEST_COMPUTE_ADMIN + '/v3' - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(v2_compute) - disc.add_v3(v3_compute) - - # Make sure that we don't do more than one discovery call - # 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': disc}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin') - self.assertEqual(v3_compute, data.url) - - v2_data = data.get_versioned_data(s, min_version='2.0', - max_version='2.latest') - self.assertEqual(v2_compute, v2_data.url) - self.assertEqual(v2_compute, v2_data.service_url) - self.assertEqual(self.TEST_COMPUTE_ADMIN, v2_data.catalog_url) - - # Variants that all return v3 data - for vkwargs in (dict(min_version='3.0', max_version='3.latest'), - # min/max spans major versions - dict(min_version='2.0', max_version='3.latest'), - # latest major max - dict(min_version='2.0', max_version='latest'), - # implicit max - dict(min_version='2.0'), - # implicit min/max - dict()): - v3_data = data.get_versioned_data(s, **vkwargs) - self.assertEqual(v3_compute, v3_data.url) - self.assertEqual(v3_compute, v3_data.service_url) - self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url) - - def test_interface_list(self): - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - ep = s.get_endpoint(service_type='baremetal', - interface=['internal', 'public']) - self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL) - - ep = s.get_endpoint(service_type='baremetal', - interface=['public', 'internal']) - self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL) - - ep = s.get_endpoint(service_type='compute', - interface=['internal', 'public']) - self.assertEqual(ep, self.TEST_COMPUTE_INTERNAL) - - ep = s.get_endpoint(service_type='compute', - interface=['public', 'internal']) - self.assertEqual(ep, self.TEST_COMPUTE_PUBLIC) - - def test_get_versioned_data_volume_project_id(self): - - disc = fixture.DiscoveryList(v2=False, v3=False) - - # The version discovery dict will not have a project_id - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v3'].discovery.public, - id='v3.0', status='CURRENT', - min_version='3.0', version='3.20') - - # Adding a v2 version to a service named volumev3 is not - # an error. The service itself is cinder and has more than - # one major version. - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v2'].discovery.public, - id='v2.0', status='SUPPORTED') - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # volume endpoint ends in v3, we should not make an API call - endpoint = a.get_endpoint(session=s, - service_type='volumev3', - interface='public', - version='3.0') - self.assertEqual(self.TEST_VOLUME.catalog.public, endpoint) - - resps = [{'json': disc}, {'status_code': 500}] - - # We should only try to fetch the versioned discovery url once - self.requests_mock.get( - self.TEST_VOLUME.versions['v3'].discovery.public, resps) - - data = a.get_endpoint_data(session=s, - service_type='volumev3', - interface='public') - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - data.url) - - v3_data = data.get_versioned_data( - s, min_version='3.0', max_version='3.latest', - project_id=self.project_id) - - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v3_data.catalog_url) - self.assertEqual((3, 0), v3_data.min_microversion) - self.assertEqual((3, 20), v3_data.max_microversion) - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.service_url) - - # Because of the v3 optimization before, requesting v2 should now go - # find the unversioned endpoint - self.requests_mock.get(self.TEST_VOLUME.unversioned.public, resps) - v2_data = data.get_versioned_data( - s, min_version='2.0', max_version='2.latest', - project_id=self.project_id) - - # Even though we never requested volumev2 from the catalog, we should - # wind up re-constructing it via version discovery and re-appending - # the project_id to the URL - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.url) - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.service_url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v2_data.catalog_url) - self.assertEqual(None, v2_data.min_microversion) - self.assertEqual(None, v2_data.max_microversion) - - def test_get_versioned_data_volume_project_id_unversioned_first(self): - - disc = fixture.DiscoveryList(v2=False, v3=False) - - # The version discovery dict will not have a project_id - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v3'].discovery.public, - id='v3.0', status='CURRENT', - min_version='3.0', version='3.20') - - # Adding a v2 version to a service named volumev3 is not - # an error. The service itself is cinder and has more than - # one major version. - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v2'].discovery.public, - id='v2.0', status='SUPPORTED') - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # cinder endpoint ends in v3, we should not make an API call - endpoint = a.get_endpoint(session=s, - service_type='volumev3', - interface='public', - version='3.0') - self.assertEqual(self.TEST_VOLUME.catalog.public, endpoint) - - resps = [{'json': disc}, {'status_code': 500}] - - # We should only try to fetch the unversioned non-project_id url once - # Because the catalog has the versioned endpoint but we constructed - # an unversioned endpoint, the url needs to have a trailing / - self.requests_mock.get( - self.TEST_VOLUME.unversioned.public + '/', resps) - - # Fetch v2.0 first - since that doesn't match endpoint optimization, - # it should fetch the unversioned endpoint - v2_data = s.get_endpoint_data(service_type='volumev3', - interface='public', - min_version='2.0', - max_version='2.latest', - project_id=self.project_id) - - # Even though we never requested volumev2 from the catalog, we should - # wind up re-constructing it via version discovery and re-appending - # the project_id to the URL - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.url) - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.service_url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v2_data.catalog_url) - self.assertEqual(None, v2_data.min_microversion) - self.assertEqual(None, v2_data.max_microversion) - - # Since we fetched from the unversioned endpoint to satisfy the - # request for v2, we should have all the relevant data cached in the - # discovery object - and should not fetch anything new. - v3_data = v2_data.get_versioned_data( - s, min_version='3.0', max_version='3.latest', - project_id=self.project_id) - - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v3_data.catalog_url) - self.assertEqual((3, 0), v3_data.min_microversion) - self.assertEqual((3, 20), v3_data.max_microversion) - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.service_url) - - 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 = _utils.before_utcnow(minutes=20) - expired_token = self.get_auth_data(expires=expires) - expired_auth_ref = access.create(body=expired_token) - - 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)) - - def assertAccessInfoEqual(self, a, b): - self.assertEqual(a.auth_token, b.auth_token) - self.assertEqual(a._data, b._data) - - def test_check_cache_id_match(self): - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - self.assertIsNot(a, b) - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertIsNotNone(a_id) - self.assertIsNotNone(b_id) - - self.assertEqual(a_id, b_id) - - def test_check_cache_id_no_match(self): - a = self.create_auth_plugin(project_id='a') - b = self.create_auth_plugin(project_id='b') - - self.assertIsNot(a, b) - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertIsNotNone(a_id) - self.assertIsNotNone(b_id) - - self.assertNotEqual(a_id, b_id) - - def test_get_set_auth_state(self): - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - self.assertEqual(a.get_cache_id(), b.get_cache_id()) - - s = session.Session() - - a_token = a.get_token(s) - - self.assertEqual(1, self.requests_mock.call_count) - - auth_state = a.get_auth_state() - - self.assertIsNotNone(auth_state) - - b.set_auth_state(auth_state) - - b_token = b.get_token(s) - self.assertEqual(1, self.requests_mock.call_count) - - self.assertEqual(a_token, b_token) - self.assertAccessInfoEqual(a.auth_ref, b.auth_ref) - - def test_pathless_url(self): - disc = fixture.DiscoveryList(v2=False, v3=False) - url = 'http://path.less.url:1234' - disc.add_microversion(href=url, id='v2.1') - - self.stub_url('GET', base_url=url, status_code=200, json=disc) - - token = fixture.V2Token() - service = token.add_service('network') - service.add_endpoint(public=url, admin=url, internal=url) - - self.stub_url('POST', ['tokens'], base_url=url, json=token) - - v2_auth = identity.V2Password(url, username='u', password='p') - - sess = session.Session(auth=v2_auth) - - data = sess.get_endpoint_data(service_type='network') - - # Discovery ran and returned the URL - self.assertEqual(url, data.url) - - # Run with a project_id to ensure that path is covered - self.assertEqual( - 3, len(list(data._get_discovery_url_choices(project_id='42')))) - - -class V3(CommonIdentityTests, utils.TestCase): - - @property - def version(self): - return 'v3' - - def get_auth_data(self, **kwargs): - kwargs.setdefault('project_id', self.PROJECT_ID) - 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) - - svc = token.add_service('volumev2') - svc.add_standard_endpoints( - admin=self.TEST_VOLUME.versions['v2'].service.admin, - public=self.TEST_VOLUME.versions['v2'].service.public, - internal=self.TEST_VOLUME.versions['v2'].service.internal, - region=region) - - svc = token.add_service('volumev3') - svc.add_standard_endpoints( - admin=self.TEST_VOLUME.versions['v3'].service.admin, - public=self.TEST_VOLUME.versions['v3'].service.public, - internal=self.TEST_VOLUME.versions['v3'].service.internal, - region=region) - - svc = token.add_service('baremetal') - svc.add_standard_endpoints( - internal=self.TEST_BAREMETAL_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) - - try: - kwargs.setdefault('tenant_id', kwargs.pop('project_id')) - except KeyError: - pass - - try: - kwargs.setdefault('tenant_name', kwargs.pop('project_name')) - except KeyError: - pass - - return identity.V2Password(**kwargs) - - def get_auth_data(self, **kwargs): - kwargs.setdefault('tenant_id', self.PROJECT_ID) - 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) - - svc = token.add_service('volumev2') - svc.add_endpoint( - admin=self.TEST_VOLUME.versions['v2'].service.admin, - public=self.TEST_VOLUME.versions['v2'].service.public, - internal=self.TEST_VOLUME.versions['v2'].service.internal, - region=region) - - svc = token.add_service('volumev3') - svc.add_endpoint( - admin=self.TEST_VOLUME.versions['v3'].service.admin, - public=self.TEST_VOLUME.versions['v3'].service.public, - internal=self.TEST_VOLUME.versions['v3'].service.internal, - region=region) - - svc = token.add_service('baremetal') - svc.add_endpoint( - public=None, admin=None, - internal=self.TEST_BAREMETAL_INTERNAL, - 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 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) - self.stub_url('GET', [], base_url=self.V2_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_returns_original_skipping_discovery(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) - - 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', - skip_discovery=True, - version=(3, 0)) - - self.assertEqual(self.V2_URL, endpoint) - - def test_endpoint_override_skips_discovery(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) - - 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(endpoint_override=self.OTHER_URL, - service_type=self.IDENTITY, - interface='public', - version=(3, 0)) - - self.assertEqual(self.OTHER_URL, endpoint) - - def test_endpoint_override_data_runs_discovery(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', max_version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_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) - - data = sess.get_endpoint_data(endpoint_override=self.OTHER_URL, - service_type=self.IDENTITY, - interface='public', - min_version=(2, 0), - max_version=(2, discover.LATEST)) - - self.assertTrue(common_m.called) - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - def test_forcing_discovery(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json={'version': v2_disc}) - - common_m = self.stub_url('GET', - [], - base_url=self.BASE_URL, - status_code=300, - json=common_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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type=self.IDENTITY, - discover_versions=True) - - # We should get the v2 document, but not the unversioned - self.assertTrue(v2_m.called) - self.assertFalse(common_m.called) - - # got v2 url - self.assertEqual(self.V2_URL, data.url) - - def test_forcing_discovery_list_returns_url(self): - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - # 2.0 doesn't usually return a list. This is testing that if - # the catalog url returns an endpoint that has a discovery document - # with more than one URL and that a different url would be returned - # by "return the latest" rules, that we get the info of the url from - # the catalog if we don't provide a version but do provide - # discover_versions - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json=common_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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - data = sess.get_endpoint_data(service_type=self.IDENTITY, - discover_versions=True) - - # We should make the one call - self.assertTrue(v2_m.called) - - # got v2 url - self.assertEqual(self.V2_URL, data.url) - - def test_latest_version_gets_latest_version(self): - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - # 2.0 doesn't usually return a list. But we're testing version matching - # rules, so it's nice to ensure that we don't fallback to something - v2_m = self.stub_url('GET', - base_url=self.BASE_URL, - status_code=200, - json=common_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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version='latest') - - # We should make the one call - self.assertTrue(v2_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - # Make sure latest logic works for min and max version - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - max_version='latest') - self.assertEqual(self.V3_URL, endpoint) - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='latest') - self.assertEqual(self.V3_URL, endpoint) - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='latest', - max_version='latest') - self.assertEqual(self.V3_URL, endpoint) - - self.assertRaises(ValueError, sess.get_endpoint, - service_type=self.IDENTITY, - min_version='latest', max_version='3.0') - - def test_version_range(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - def stub_urls(): - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json={'version': v2_disc}) - common_m = self.stub_url('GET', - base_url=self.BASE_URL, - status_code=200, - json=common_disc) - return v2_m, common_m - v2_m, common_m = stub_urls() - - 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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertFalse(v2_m.called) - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='1', max_version='2') - - # We should make no calls - we peek in the cache - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get the v2 url - self.assertEqual(self.V2_URL, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='4') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get no url - self.assertEqual(None, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='2') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - v2_m, common_m = stub_urls() - self.assertRaises(ValueError, sess.get_endpoint, - service_type=self.IDENTITY, version=3, - min_version='2') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - def test_get_endpoint_data(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', max_version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service('network') - service.add_endpoint(public=self.OTHER_URL, - admin=self.OTHER_URL, - internal=self.OTHER_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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type='network', - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - def test_get_endpoint_data_compute(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_nova_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service('compute') - service.add_endpoint(public=self.OTHER_URL, - admin=self.OTHER_URL, - internal=self.OTHER_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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type='compute', - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - 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) - - def test_setting_no_discover_hack(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json=v2_disc) - - common_m = self.stub_url('GET', - [], - base_url=self.BASE_URL, - status_code=300, - json=common_disc) - - resp_text = uuid.uuid4().hex - - resp_m = self.stub_url('GET', - ['v3', 'path'], - base_url=self.BASE_URL, - status_code=200, - text=resp_text) - - # it doesn't matter that we auth with v2 here, discovery hack is in - # base. All identity endpoints point to v2 urls. - 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) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # v3 endpoint with hack will strip v2 suffix and call root discovery - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version=(3, 0), - allow_version_hack=True) - - # got v3 url - self.assertEqual(self.V3_URL, endpoint) - - # only called root discovery. - self.assertFalse(v2_m.called) - self.assertTrue(common_m.called_once) - - # with hack turned off it calls v2 discovery and finds nothing - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version=(3, 0), - allow_version_hack=False) - self.assertIsNone(endpoint) - - # this one called v2 - self.assertTrue(v2_m.called_once) - self.assertTrue(common_m.called_once) - - # get_endpoint returning None raises EndpointNotFound when requesting - self.assertRaises(exceptions.EndpointNotFound, - sess.get, - '/path', - endpoint_filter={'service_type': 'identity', - 'version': (3, 0), - 'allow_version_hack': False}) - - self.assertFalse(resp_m.called) - - # works when allow_version_hack is set - resp = sess.get('/path', - endpoint_filter={'service_type': 'identity', - 'version': (3, 0), - 'allow_version_hack': True}) - - self.assertTrue(resp_m.called_once) - self.assertEqual(resp_text, resp.text) - - -class GenericPlugin(plugin.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() - 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) - - 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 - 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) - - # the cert and verify values passed to request are those that were - # returned from the auth plugin as connection params. - self.assertEqual(self.auth.cert, self.requests_mock.last_request.cert) - self.assertFalse(self.requests_mock.last_request.verify) - - 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)) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v2.py b/keystoneauth1/tests/unit/identity/test_identity_v2.py deleted file mode 100644 index 530b046..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v2.py +++ /dev/null @@ -1,369 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json -import uuid - -from keystoneauth1 import _utils as ksa_utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.identity import v2 -from keystoneauth1 import session -from keystoneauth1.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.TEST_RESPONSE_DICT = { - "access": { - "token": { - "expires": "2020-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) - self.assertFalse(a.has_scope_parameters) - 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) - self.assertFalse(a.has_scope_parameters) - 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.assertTrue(a.has_scope_parameters) - 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) - self.assertTrue(a.has_scope_parameters) - 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') - self.assertTrue(a.has_scope_parameters) - 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) - - self.assertEqual('token1', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) - - a.invalidate() - 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) - 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) - - def test_password_cache_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - trust_id = uuid.uuid4().hex - - a = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - trust_id=trust_id) - - b = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - trust_id=trust_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - tenant_id=trust_id) # same value different param - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) - - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - self.assertIsNone(c.get_auth_state()) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) - self.assertTrue(self.requests_mock.called) - - def test_password_change_auth_state(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - expired = ksa_utils.before_utcnow(days=2) - token = fixture.V2Token(expires=expired) - - auth_ref = access.create(body=token) - - a = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - tenant_id=uuid.uuid4().hex) - - initial_cache_id = a.get_cache_id() - - state = a.get_auth_state() - self.assertIsNone(state) - - state = json.dumps({'auth_token': auth_ref.auth_token, - 'body': auth_ref._data}) - a.set_auth_state(state) - - self.assertEqual(token.token_id, a.auth_ref.auth_token) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) # updates expired - self.assertEqual(initial_cache_id, a.get_cache_id()) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3.py b/keystoneauth1/tests/unit/identity/test_identity_v3.py deleted file mode 100644 index 5c88105..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3.py +++ /dev/null @@ -1,632 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json -import uuid - -from keystoneauth1 import _utils as ksa_utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import base as v3_base -from keystoneauth1 import session -from keystoneauth1.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" - }] - - TEST_SERVICE_PROVIDERS = [ - { - "auth_url": "https://sp1.com/v3/OS-FEDERATION/" - "identity_providers/acme/protocols/saml2/auth", - "id": "sp1", - "sp_url": "https://sp1.com/Shibboleth.sso/SAML2/ECP" - }, { - "auth_url": "https://sp2.com/v3/OS-FEDERATION/" - "identity_providers/acme/protocols/saml2/auth", - "id": "sp2", - "sp_url": "https://sp2.com/Shibboleth.sso/SAML2/ECP" - } - ] - - def setUp(self): - super(V3IdentityPlugin, self).setUp() - - self.TEST_DISCOVERY_RESPONSE = { - 'versions': {'values': [fixture.V3Discovery(self.TEST_URL)]}} - - self.TEST_RESPONSE_DICT = { - "token": { - "methods": [ - "token", - "password" - ], - - "expires_at": "2020-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, - "service_providers": self.TEST_SERVICE_PROVIDERS - }, - } - 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) - self.assertFalse(a.has_scope_parameters) - 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_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) - self.assertTrue(a.has_scope_parameters) - 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) - self.assertTrue(a.has_scope_parameters) - 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.create(body=d) - s = session.Session(auth=a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - self.assertEqual(a.auth_ref._data['token']['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.assertTrue(a.has_scope_parameters) - 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') - self.assertTrue(a.has_scope_parameters) - 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') - self.assertTrue(a.has_scope_parameters) - 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_service_providers_urls(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_ref = a.get_auth_ref(s) - - service_providers = auth_ref.service_providers - self.assertEqual('https://sp1.com/v3/OS-FEDERATION/' - 'identity_providers/acme/protocols/saml2/auth', - service_providers.get_auth_url('sp1')) - self.assertEqual('https://sp1.com/Shibboleth.sso/SAML2/ECP', - service_providers.get_sp_url('sp1')) - self.assertEqual('https://sp2.com/v3/OS-FEDERATION/' - 'identity_providers/acme/protocols/saml2/auth', - service_providers.get_auth_url('sp2')) - self.assertEqual('https://sp2.com/Shibboleth.sso/SAML2/ECP', - service_providers.get_sp_url('sp2')) - - def test_handle_missing_service_provider(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_ref = a.get_auth_ref(s) - - service_providers = auth_ref.service_providers - - self.assertRaises(exceptions.ServiceProviderNotFound, - service_providers._get_service_provider, - uuid.uuid4().hex) - - 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) - - self.assertEqual('token1', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) - a.invalidate() - 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) - 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_token() - - 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) - - 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('unscoped', body['auth']['scope']) - - 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) - - def test_password_cache_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - project_name = uuid.uuid4().hex - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_name=project_name) - - b = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_name=project_name) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_id=project_name) # same value different param - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) - - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - self.assertIsNone(c.get_auth_state()) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) - self.assertTrue(self.requests_mock.called) - - def test_password_change_auth_state(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - expired = ksa_utils.before_utcnow(days=2) - token = fixture.V3Token(expires=expired) - token_id = uuid.uuid4().hex - - state = json.dumps({'auth_token': token_id, 'body': token}) - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_id=uuid.uuid4().hex) - - initial_cache_id = a.get_cache_id() - - self.assertIsNone(a.get_auth_state()) - a.set_auth_state(state) - - self.assertEqual(token_id, a.auth_ref.auth_token) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) # updates expired - self.assertEqual(initial_cache_id, a.get_cache_id()) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py b/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py deleted file mode 100644 index e7e940f..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py +++ /dev/null @@ -1,292 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - -import six - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1.identity import v3 -from keystoneauth1 import session -from keystoneauth1.tests.unit import k2k_fixtures -from keystoneauth1.tests.unit import utils - - -class TesterFederationPlugin(v3.FederationBaseAuth): - - 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.create(resp=resp) - - -class V3FederatedPlugin(utils.TestCase): - - AUTH_URL = 'http://keystone/v3' - - def setUp(self): - super(V3FederatedPlugin, self).setUp() - - 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) - - -class K2KAuthPluginTest(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_ROOT_URL = 'https://sp1.com/v3' - SP_ID = 'sp1' - SP_URL = 'https://sp1.com/Shibboleth.sso/SAML2/ECP' - SP_AUTH_URL = (SP_ROOT_URL + - '/OS-FEDERATION/identity_providers' - '/testidp/protocols/saml2/auth') - - SERVICE_PROVIDER_DICT = { - 'id': SP_ID, - 'auth_url': SP_AUTH_URL, - 'sp_url': SP_URL - } - - def setUp(self): - super(K2KAuthPluginTest, 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.k2kplugin = self.get_plugin() - - 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, redirect_code=302): - # List versions available for auth - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - # The IdP should return a ECP wrapped assertion when requested - self.requests_mock.register_uri( - 'POST', - self.REQUEST_ECP_URL, - content=six.b(k2k_fixtures.ECP_ENVELOPE), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=200) - - # The SP should respond with a redirect (302 or 303) - self.requests_mock.register_uri( - 'POST', - self.SP_URL, - content=six.b(k2k_fixtures.TOKEN_BASED_ECP), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=redirect_code) - - # 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_remote_url(self): - remote_auth_url = self.k2kplugin._remote_auth_url(self.SP_AUTH_URL) - self.assertEqual(self.SP_ROOT_URL, remote_auth_url) - - def test_fail_getting_ecp_assertion(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - status_code=401) - - self.assertRaises(exceptions.AuthorizationFailure, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_get_ecp_assertion_empty_response(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - headers={'Content-Type': 'application/vnd.paos+xml'}, - content=six.b(''), status_code=200) - - self.assertRaises(exceptions.InvalidResponse, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_get_ecp_assertion_wrong_headers(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - headers={'Content-Type': uuid.uuid4().hex}, - content=six.b(''), status_code=200) - - self.assertRaises(exceptions.InvalidResponse, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_send_ecp_authn_response(self): - self._mock_k2k_flow_urls() - # Perform the request - response = self.k2kplugin._send_service_provider_ecp_authn_response( - self.session, self.SP_URL, self.SP_AUTH_URL) - - # Check the response - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - response.headers['X-Subject-Token']) - - def test_send_ecp_authn_response_303_redirect(self): - self._mock_k2k_flow_urls(redirect_code=303) - # Perform the request - response = self.k2kplugin._send_service_provider_ecp_authn_response( - self.session, self.SP_URL, self.SP_AUTH_URL) - - # Check the response - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - response.headers['X-Subject-Token']) - - def test_end_to_end_workflow(self): - self._mock_k2k_flow_urls() - auth_ref = self.k2kplugin.get_auth_ref(self.session) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - auth_ref.auth_token) - - def test_end_to_end_workflow_303_redirect(self): - self._mock_k2k_flow_urls(redirect_code=303) - auth_ref = self.k2kplugin.get_auth_ref(self.session) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - auth_ref.auth_token) - - def test_end_to_end_with_generic_password(self): - # List versions available for auth - self.requests_mock.get( - self.TEST_ROOT_URL, - json=fixture.DiscoveryList(self.TEST_ROOT_URL), - headers={'Content-Type': 'application/json'}) - - # The IdP should return a ECP wrapped assertion when requested - self.requests_mock.register_uri( - 'POST', - self.REQUEST_ECP_URL, - content=six.b(k2k_fixtures.ECP_ENVELOPE), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=200) - - # The SP should respond with a redirect (302 or 303) - self.requests_mock.register_uri( - 'POST', - self.SP_URL, - content=six.b(k2k_fixtures.TOKEN_BASED_ECP), - 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}) - - self.stub_url('POST', ['auth', 'tokens'], - headers={'X-Subject-Token': uuid.uuid4().hex}, - json=self.token_v3) - - plugin = identity.Password(self.TEST_ROOT_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id='default') - - k2kplugin = self.get_plugin(base_plugin=plugin) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - k2kplugin.get_token(self.session)) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py b/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py deleted file mode 100644 index 9bf1218..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py +++ /dev/null @@ -1,402 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 warnings - -from six.moves import urllib - -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import oidc -from keystoneauth1 import session -from keystoneauth1.tests.unit import oidc_fixtures -from keystoneauth1.tests.unit import utils - - -KEYSTONE_TOKEN_VALUE = uuid.uuid4().hex - - -class BaseOIDCTests(object): - - def setUp(self): - super(BaseOIDCTests, self).setUp() - self.session = session.Session() - - self.AUTH_URL = 'http://keystone:5000/v3' - self.IDENTITY_PROVIDER = 'bluepages' - self.PROTOCOL = 'oidc' - self.USER_NAME = 'oidc_user@example.com' - self.PROJECT_NAME = 'foo project' - self.PASSWORD = uuid.uuid4().hex - self.CLIENT_ID = uuid.uuid4().hex - self.CLIENT_SECRET = uuid.uuid4().hex - self.ACCESS_TOKEN = uuid.uuid4().hex - self.ACCESS_TOKEN_ENDPOINT = 'https://localhost:8020/oidc/token' - self.FEDERATION_AUTH_URL = '%s/%s' % ( - self.AUTH_URL, - 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') - self.REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob' - self.CODE = '4/M9TNz2G9WVwYxSjx0w9AgA1bOmryJltQvOhQMq0czJs.cnLNVAfqwG' - - self.DISCOVERY_URL = ('https://localhost:8020/oidc/.well-known/' - 'openid-configuration') - self.GRANT_TYPE = None - - def test_grant_type_and_plugin_missmatch(self): - self.assertRaises( - exceptions.OidcGrantTypeMissmatch, - self.plugin.__class__, - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - grant_type=uuid.uuid4().hex - ) - - def test_can_pass_grant_type_but_warning_is_issued(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - grant_type=self.GRANT_TYPE) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "grant_type" in str(w[-1].message) - - def test_discovery_not_found(self): - self.requests_mock.get("http://not.found", - status_code=404) - - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint="http://not.found") - - self.assertRaises(exceptions.http.NotFound, - plugin._get_discovery_document, - self.session) - - def test_no_discovery(self): - - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - ) - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, - plugin.access_token_endpoint) - - def test_load_discovery(self): - self.requests_mock.get(self.DISCOVERY_URL, - json=oidc_fixtures.DISCOVERY_DOCUMENT) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - self.assertEqual( - oidc_fixtures.DISCOVERY_DOCUMENT["token_endpoint"], - plugin._get_access_token_endpoint(self.session) - ) - - def test_no_access_token_endpoint(self): - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET) - - self.assertRaises(exceptions.OidcAccessTokenEndpointNotFound, - plugin._get_access_token_endpoint, - self.session) - - def test_invalid_discovery_document(self): - self.requests_mock.get(self.DISCOVERY_URL, - json={}) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - - self.assertRaises(exceptions.InvalidOidcDiscoveryDocument, - plugin._get_discovery_document, - self.session) - - def test_load_discovery_override_by_endpoints(self): - self.requests_mock.get(self.DISCOVERY_URL, - json=oidc_fixtures.DISCOVERY_DOCUMENT) - - access_token_endpoint = uuid.uuid4().hex - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL, - access_token_endpoint=access_token_endpoint - ) - self.assertEqual(access_token_endpoint, - plugin._get_access_token_endpoint(self.session)) - - def test_wrong_grant_type(self): - self.requests_mock.get(self.DISCOVERY_URL, - json={"grant_types_supported": ["foo", "bar"]}) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - - self.assertRaises(exceptions.OidcPluginNotSupported, - plugin.get_unscoped_auth_ref, - self.session) - - -class OIDCClientCredentialsTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCClientCredentialsTests, self).setUp() - - self.GRANT_TYPE = 'client_credentials' - - self.plugin = oidc.OidcClientCredentials( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - project_name=self.PROJECT_NAME) - - 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=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Prep all the values and send the request - scope = 'profile email' - payload = {'grant_type': self.GRANT_TYPE, 'scope': scope} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.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=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - res = self.plugin._get_keystone_token(self.session, - self.ACCESS_TOKEN) - - # Verify the request matches the expected structure - self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) - self.assertEqual('POST', res.request.method) - - headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN} - 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=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) - - -class OIDCPasswordTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCPasswordTests, self).setUp() - - self.GRANT_TYPE = 'password' - - self.plugin = oidc.OidcPassword( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - project_name=self.PROJECT_NAME, - username=self.USER_NAME, - password=self.PASSWORD) - - 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=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Prep all the values and send the request - grant_type = 'password' - scope = 'profile email' - payload = {'grant_type': grant_type, 'username': self.USER_NAME, - 'password': self.PASSWORD, 'scope': scope} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.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=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - res = self.plugin._get_keystone_token(self.session, - self.ACCESS_TOKEN) - - # Verify the request matches the expected structure - self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) - self.assertEqual('POST', res.request.method) - - headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN} - 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=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) - - -class OIDCAuthorizationGrantTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCAuthorizationGrantTests, self).setUp() - - self.GRANT_TYPE = 'authorization_code' - - self.plugin = oidc.OidcAuthorizationCode( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - redirect_uri=self.REDIRECT_URL, - project_name=self.PROJECT_NAME, - code=self.CODE) - - 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=oidc_fixtures.ACCESS_TOKEN_VIA_AUTH_GRANT_RESP) - - # Prep all the values and send the request - grant_type = 'authorization_code' - payload = {'grant_type': grant_type, - 'redirect_uri': self.REDIRECT_URL, - 'code': self.CODE} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.body) - - -# NOTE(aloga): This is a special case, as we do not need all the other openid -# parameters, like client_id, client_secret, access_token_endpoint and so on, -# therefore we do not inherit from the base oidc test class, but from the base -# TestCase -class OIDCTokenTests(utils.TestCase): - def setUp(self): - super(OIDCTokenTests, self).setUp() - - self.session = session.Session() - - self.AUTH_URL = 'http://keystone:5000/v3' - self.IDENTITY_PROVIDER = 'bluepages' - self.PROTOCOL = 'oidc' - self.PROJECT_NAME = 'foo project' - self.ACCESS_TOKEN = uuid.uuid4().hex - - self.FEDERATION_AUTH_URL = '%s/%s' % ( - self.AUTH_URL, - 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') - - self.plugin = oidc.OidcAccessToken( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - access_token=self.ACCESS_TOKEN, - project_name=self.PROJECT_NAME) - - def test_end_to_end_workflow(self): - """Test full OpenID Connect workflow.""" - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) diff --git a/keystoneauth1/tests/unit/identity/test_password.py b/keystoneauth1/tests/unit/identity/test_password.py deleted file mode 100644 index 7c91018..0000000 --- a/keystoneauth1/tests/unit/identity/test_password.py +++ /dev/null @@ -1,106 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.identity.generic import password -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import password as v3_password -from keystoneauth1.tests.unit.identity 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_v3_domain_params_v2_url(self): - self.stub_discovery(v3=False) - self.assertDiscoveryFailure(domain_id=uuid.uuid4().hex) - - def test_v3_disocovery_failure_v2_url(self): - auth_url = self.TEST_URL + 'v2.0' - self.stub_url('GET', json={}, base_url='/v2.0', status_code=500) - self.assertDiscoveryFailure(domain_id=uuid.uuid4().hex, - auth_url=auth_url) - - def test_symbols(self): - self.assertIs(v3.Password, v3_password.Password) - self.assertIs(v3.PasswordMethod, v3_password.PasswordMethod) - - def test_default_domain_id_with_v3(self): - default_domain_id = uuid.uuid4().hex - - p = super(PasswordTests, self).test_default_domain_id_with_v3( - default_domain_id=default_domain_id) - - self.assertEqual(default_domain_id, - p._plugin.auth_methods[0].user_domain_id) - - def test_default_domain_name_with_v3(self): - default_domain_name = uuid.uuid4().hex - - p = super(PasswordTests, self).test_default_domain_name_with_v3( - default_domain_name=default_domain_name) - - self.assertEqual(default_domain_name, - p._plugin.auth_methods[0].user_domain_name) - - def test_password_cache_id(self): - username = uuid.uuid4().hex - the_password = uuid.uuid4().hex - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - a = password.Password(self.TEST_URL, - username=username, - password=the_password, - project_name=project_name, - default_domain_id=default_domain_id) - - b = password.Password(self.TEST_URL, - username=username, - password=the_password, - project_name=project_name, - default_domain_id=default_domain_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = password.Password(self.TEST_URL, - username=username, - password=uuid.uuid4().hex, # different - project_name=project_name, - default_domain_id=default_domain_id) - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) diff --git a/keystoneauth1/tests/unit/identity/test_token.py b/keystoneauth1/tests/unit/identity/test_token.py deleted file mode 100644 index 0303fb1..0000000 --- a/keystoneauth1/tests/unit/identity/test_token.py +++ /dev/null @@ -1,63 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.identity.generic import token -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import token as v3_token -from keystoneauth1.tests.unit.identity 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_symbols(self): - self.assertIs(v3.Token, v3_token.Token) - self.assertIs(v3.TokenMethod, v3_token.TokenMethod) - - def test_token_cache_id(self): - the_token = uuid.uuid4().hex - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - a = token.Token(self.TEST_URL, - token=the_token, - project_name=project_name, - default_domain_id=default_domain_id) - - b = token.Token(self.TEST_URL, - token=the_token, - project_name=project_name, - default_domain_id=default_domain_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = token.Token(self.TEST_URL, - token=the_token, - project_name=uuid.uuid4().hex, # different - default_domain_id=default_domain_id) - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) diff --git a/keystoneauth1/tests/unit/identity/test_tokenless_auth.py b/keystoneauth1/tests/unit/identity/test_tokenless_auth.py deleted file mode 100644 index 160291f..0000000 --- a/keystoneauth1/tests/unit/identity/test_tokenless_auth.py +++ /dev/null @@ -1,105 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.identity.v3 import tokenless_auth -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class TokenlessAuthTest(utils.TestCase): - - TEST_URL = 'http://server/prefix' - - def create(self, auth_url, - domain_id=None, - domain_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None): - self.requests_mock.get(self.TEST_URL) - auth = tokenless_auth.TokenlessAuth( - auth_url=self.TEST_URL, - 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 auth, session.Session(auth=auth) - - def test_domain_id_scope_header_pass(self): - domain_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - domain_id=domain_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Domain-Id', domain_id) - - def test_domain_name_scope_header_pass(self): - domain_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - domain_name=domain_name) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Domain-Name', domain_name) - - def test_project_id_scope_header_pass(self): - project_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_id=project_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Id', project_id) - - def test_project_of_domain_id_scope_header_pass(self): - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name, - project_domain_id=project_domain_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Name', project_name) - self.assertRequestHeaderEqual('X-Project-Domain-Id', project_domain_id) - - def test_project_of_domain__name_scope_header_pass(self): - project_name = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name, - project_domain_name=project_domain_name) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Name', project_name) - self.assertRequestHeaderEqual('X-Project-Domain-Name', - project_domain_name) - - def test_no_scope_header_fail(self): - auth, session = self.create(auth_url=self.TEST_URL) - self.assertIsNone(auth.get_headers(session)) - msg = 'No valid authentication is available' - self.assertRaisesRegex(exceptions.AuthorizationFailure, - msg, - session.get, - self.TEST_URL, - authenticated=True) - - def test_project_name_scope_only_header_fail(self): - project_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name) - self.assertIsNone(auth.get_headers(session)) - msg = 'No valid authentication is available' - self.assertRaisesRegex(exceptions.AuthorizationFailure, - msg, - session.get, - self.TEST_URL, - authenticated=True) diff --git a/keystoneauth1/tests/unit/identity/utils.py b/keystoneauth1/tests/unit/identity/utils.py deleted file mode 100644 index 89883b5..0000000 --- a/keystoneauth1/tests/unit/identity/utils.py +++ /dev/null @@ -1,180 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -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.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_create_plugin_no_reauthenticate(self): - self.stub_discovery() - self.assertCreateV2(reauthenticate=False) - self.assertCreateV3(domain_id=uuid.uuid4().hex, reauthenticate=False) - - 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() - self.assertIn(self.TEST_URL, self.logger.output) - - 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() - - def test_default_domain_id_with_v3(self, **kwargs): - self.stub_discovery() - project_name = uuid.uuid4().hex - default_domain_id = kwargs.setdefault('default_domain_id', - uuid.uuid4().hex) - - p = self.assertCreateV3(project_name=project_name, **kwargs) - - self.assertEqual(default_domain_id, p._plugin.project_domain_id) - self.assertEqual(project_name, p._plugin.project_name) - - return p - - def test_default_domain_id_no_v3(self): - self.stub_discovery(v3=False) - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - p = self.assertCreateV2(project_name=project_name, - default_domain_id=default_domain_id) - - self.assertEqual(project_name, p._plugin.tenant_name) - - def test_default_domain_name_with_v3(self, **kwargs): - self.stub_discovery() - project_name = uuid.uuid4().hex - default_domain_name = kwargs.setdefault('default_domain_name', - uuid.uuid4().hex) - - p = self.assertCreateV3(project_name=project_name, **kwargs) - - self.assertEqual(default_domain_name, p._plugin.project_domain_name) - self.assertEqual(project_name, p._plugin.project_name) - - return p - - def test_default_domain_name_no_v3(self): - self.stub_discovery(v3=False) - project_name = uuid.uuid4().hex - default_domain_name = uuid.uuid4().hex - - p = self.assertCreateV2(project_name=project_name, - default_domain_name=default_domain_name) - - self.assertEqual(project_name, p._plugin.tenant_name) diff --git a/keystoneauth1/tests/unit/k2k_fixtures.py b/keystoneauth1/tests/unit/k2k_fixtures.py deleted file mode 100644 index f78cb0e..0000000 --- a/keystoneauth1/tests/unit/k2k_fixtures.py +++ /dev/null @@ -1,148 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -UNSCOPED_TOKEN_HEADER = 'UNSCOPED_TOKEN' - -UNSCOPED_TOKEN = { - "token": { - "issued_at": "2014-06-09T09:48:59.643406Z", - "extras": {}, - "methods": ["token"], - "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" - } - } -} - - -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]) diff --git a/keystoneauth1/tests/unit/keystoneauth_fixtures.py b/keystoneauth1/tests/unit/keystoneauth_fixtures.py deleted file mode 100644 index 11f9697..0000000 --- a/keystoneauth1/tests/unit/keystoneauth_fixtures.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - - -class HackingCode(fixtures.Fixture): - """A fixture to house the various code examples. - - Examples contains various keystoneauth hacking style checks. - """ - - oslo_namespace_imports = { - 'code': """ - import oslo.utils - import oslo_utils - import oslo.utils.encodeutils - import oslo_utils.encodeutils - from oslo import utils - from oslo.utils import encodeutils - from oslo_utils import encodeutils - - import oslo.serialization - import oslo_serialization - import oslo.serialization.jsonutils - import oslo_serialization.jsonutils - from oslo import serialization - from oslo.serialization import jsonutils - from oslo_serialization import jsonutils - - import oslo.config - import oslo_config - import oslo.config.cfg - import oslo_config.cfg - from oslo import config - from oslo.config import cfg - from oslo_config import cfg - - import oslo.i18n - import oslo_i18n - import oslo.i18n.log - import oslo_i18n.log - from oslo import i18n - from oslo.i18n import log - from oslo_i18n import log - """, - 'expected_errors': [ - (1, 0, 'K333'), - (3, 0, 'K333'), - (5, 0, 'K333'), - (6, 0, 'K333'), - (9, 0, 'K333'), - (11, 0, 'K333'), - (13, 0, 'K333'), - (14, 0, 'K333'), - (17, 0, 'K333'), - (19, 0, 'K333'), - (21, 0, 'K333'), - (22, 0, 'K333'), - (25, 0, 'K333'), - (27, 0, 'K333'), - (29, 0, 'K333'), - (30, 0, 'K333'), - ], - } diff --git a/keystoneauth1/tests/unit/loading/__init__.py b/keystoneauth1/tests/unit/loading/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/loading/test_adapter.py b/keystoneauth1/tests/unit/loading/test_adapter.py deleted file mode 100644 index 3c7d008..0000000 --- a/keystoneauth1/tests/unit/loading/test_adapter.py +++ /dev/null @@ -1,172 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_config import cfg -from oslo_config import fixture as config - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class ConfLoadingTests(utils.TestCase): - - GROUP = 'adaptergroup' - - def setUp(self): - super(ConfLoadingTests, self).setUp() - - self.conf_fx = self.useFixture(config.Config()) - loading.register_adapter_conf_options(self.conf_fx.conf, self.GROUP) - - def test_load(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_valid_interfaces_list(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces=['internal', 'public'], - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal', 'public'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_valid_interfaces_comma_list(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal,public', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal', 'public'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_old_interface(self): - self.conf_fx.config( - service_type='type', service_name='name', - interface='internal', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual('internal', adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_bad_valid_interfaces_value(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='bad', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_load_version_range(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal', - region_name='region', endpoint_override='endpoint', - min_version='2.0', max_version='3.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertIsNone(adap.version) - self.assertEqual('2.0', adap.min_version) - self.assertEqual('3.0', adap.max_version) - - def test_interface_conflict(self): - self.conf_fx.config( - service_type='type', service_name='name', interface='iface', - valid_interfaces='internal,public', - region_name='region', endpoint_override='endpoint', - group=self.GROUP) - - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_load_bad_version(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='iface', - region_name='region', endpoint_override='endpoint', - version='2.0', min_version='2.0', max_version='3.0', - group=self.GROUP) - - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_get_conf_options(self): - opts = loading.get_adapter_conf_options() - for opt in opts: - if opt.name != 'valid-interfaces': - self.assertIsInstance(opt, cfg.StrOpt) - else: - self.assertIsInstance(opt, cfg.ListOpt) - self.assertEqual({'service-type', 'service-name', - 'interface', 'valid-interfaces', - 'region-name', 'endpoint-override', 'version', - 'min-version', 'max-version'}, - {opt.name for opt in opts}) diff --git a/keystoneauth1/tests/unit/loading/test_cli.py b/keystoneauth1/tests/unit/loading/test_cli.py deleted file mode 100644 index 5bbc32c..0000000 --- a/keystoneauth1/tests/unit/loading/test_cli.py +++ /dev/null @@ -1,217 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 uuid - -import fixtures -import mock - -from keystoneauth1 import loading -from keystoneauth1.loading import cli -from keystoneauth1.tests.unit.loading import utils - - -TesterPlugin, TesterLoader = utils.create_plugin( - opts=[ - loading.Opt('test-opt', - help='tester', - deprecated=[loading.Opt('test-other')]) - ] -) - - -class CliTests(utils.TestCase): - - def setUp(self): - super(CliTests, self).setUp() - 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 = loading.register_auth_argparse_arguments(self.p, []) - self.assertIsNone(ret) - self.assertIn('--os-auth-type', self.p.format_usage()) - - def test_load_with_nothing(self): - loading.register_auth_argparse_arguments(self.p, []) - opts = self.p.parse_args([]) - self.assertIsNone(loading.load_auth_from_argparse_arguments(opts)) - - @utils.mock_plugin() - def test_basic_params_added(self, m): - name = uuid.uuid4().hex - argv = ['--os-auth-plugin', name] - ret = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(ret, utils.MockLoader) - - 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-type', name, - '--os-a-int', str(self.a_int), - '--os-a-float', str(self.a_float), - '--os-a-bool', str(self.a_bool)] - - klass = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(klass, utils.MockLoader) - - opts = self.p.parse_args(argv) - self.assertEqual(name, opts.os_auth_type) - - a = loading.load_auth_from_argparse_arguments(opts) - self.assertTestVals(a) - - self.assertEqual(name, opts.os_auth_type) - 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-type', name, - '--os-a-float', str(self.a_float)] - - klass = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(klass, utils.MockLoader) - - opts = self.p.parse_args(argv) - self.assertEqual(name, opts.os_auth_type) - - a = loading.load_auth_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 = loading.register_auth_argparse_arguments(self.p, - [], - default=name) - self.assertIsInstance(klass, utils.MockLoader) - 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-type', name] - klass = loading.register_auth_argparse_arguments(self.p, - argv, - default=default) - self.assertIsInstance(klass, utils.MockLoader) - m.assert_called_once_with(name) - - @utils.mock_plugin() - def test_with_default_type_value(self, m): - default = utils.MockLoader() - klass = loading.register_auth_argparse_arguments(self.p, - [], - default=default) - self.assertIsInstance(klass, utils.MockLoader) - 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 TestLoader(object): - pass - name = uuid.uuid4().hex - argv = ['--os-auth-type', name] - klass = loading.register_auth_argparse_arguments(self.p, - argv, - default=TestLoader) - self.assertIsInstance(klass, utils.MockLoader) - 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 = loading.register_auth_argparse_arguments(self.p, - [], - default=name) - self.assertIsInstance(klass, utils.MockLoader) - opts = self.p.parse_args([]) - a = loading.load_auth_from_argparse_arguments(opts) - - self.assertEqual(val, a['a_str']) - - def test_deprecated_cli_options(self): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - 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): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - 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}): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - - 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}): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - - opts = self.p.parse_args([]) - self.assertEqual(val1, opts.os_test_opt) - - def test_adapter_service_type(self): - argv = ['--os-service-type', 'compute'] - - loading.register_adapter_argparse_arguments(self.p, 'compute') - - opts = self.p.parse_args(argv) - self.assertEqual('compute', opts.os_service_type) - self.assertFalse(hasattr(opts, 'os_compute_service_type')) - - def test_adapter_service_type_per_service(self): - argv = ['--os-compute-service-type', 'weirdness'] - - loading.register_adapter_argparse_arguments(self.p, 'compute') - loading.register_service_adapter_argparse_arguments(self.p, 'compute') - - opts = self.p.parse_args(argv) - self.assertEqual('compute', opts.os_service_type) - self.assertEqual('weirdness', opts.os_compute_service_type) diff --git a/keystoneauth1/tests/unit/loading/test_conf.py b/keystoneauth1/tests/unit/loading/test_conf.py deleted file mode 100644 index ce3e9ab..0000000 --- a/keystoneauth1/tests/unit/loading/test_conf.py +++ /dev/null @@ -1,207 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import uuid - -import mock -from oslo_config import cfg -from oslo_config import fixture as config -import stevedore - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.loading._plugins.identity import v2 -from keystoneauth1.loading._plugins.identity import v3 -from keystoneauth1.tests.unit.loading import utils - - -class ConfTests(utils.TestCase): - - def setUp(self): - super(ConfTests, self).setUp() - 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. - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - def test_loading_v2(self): - section = uuid.uuid4().hex - auth_url = uuid.uuid4().hex - username = uuid.uuid4().hex - password = uuid.uuid4().hex - trust_id = uuid.uuid4().hex - tenant_id = uuid.uuid4().hex - - self.conf_fixture.config(auth_section=section, group=self.GROUP) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(v2.Password()) - self.conf_fixture.register_opts(opts, group=section) - - self.conf_fixture.config(auth_type=self.V2PASS, - auth_url=auth_url, - username=username, - password=password, - trust_id=trust_id, - tenant_id=tenant_id, - group=section) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(auth_url, a.auth_url) - self.assertEqual(username, a.username) - self.assertEqual(password, a.password) - self.assertEqual(trust_id, a.trust_id) - self.assertEqual(tenant_id, a.tenant_id) - - def test_loading_v3(self): - section = uuid.uuid4().hex - auth_url = uuid.uuid4().hex, - token = uuid.uuid4().hex - trust_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - - self.conf_fixture.config(auth_section=section, group=self.GROUP) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(v3.Token()) - self.conf_fixture.register_opts(opts, group=section) - - self.conf_fixture.config(auth_type=self.V3TOKEN, - auth_url=auth_url, - token=token, - trust_id=trust_id, - project_id=project_id, - project_domain_name=project_domain_name, - group=section) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(token, a.auth_methods[0].token) - self.assertEqual(trust_id, a.trust_id) - self.assertEqual(project_id, a.project_id) - self.assertEqual(project_domain_name, a.project_domain_name) - - def test_loading_invalid_plugin(self): - auth_type = uuid.uuid4().hex - self.conf_fixture.config(auth_type=auth_type, - group=self.GROUP) - - e = self.assertRaises(exceptions.NoMatchingPlugin, - loading.load_auth_from_conf_options, - self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(auth_type, e.name) - - def test_loading_with_no_data(self): - l = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertIsNone(l) - - @mock.patch('stevedore.DriverManager') - def test_other_params(self, m): - m.return_value = utils.MockManager(utils.MockLoader()) - driver_name = uuid.uuid4().hex - - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=self.GROUP) - self.conf_fixture.config(auth_type=driver_name, - group=self.GROUP, - **self.TEST_VALS) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertTestVals(a) - - m.assert_called_once_with(namespace=loading.PLUGIN_NAMESPACE, - name=driver_name, - invoke_on_load=True) - - @utils.mock_plugin() - def test_same_section(self, m): - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=self.GROUP) - - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - self.conf_fixture.config(auth_type=uuid.uuid4().hex, - group=self.GROUP, - **self.TEST_VALS) - - a = loading.load_auth_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) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=section) - self.conf_fixture.config(group=section, - auth_type=uuid.uuid4().hex, - **self.TEST_VALS) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertTestVals(a) - - def test_plugins_are_all_opts(self): - manager = stevedore.ExtensionManager(loading.PLUGIN_NAMESPACE, - propagate_map_exceptions=True) - - def inner(driver): - for p in driver.plugin().get_options(): - self.assertIsInstance(p, loading.Opt) - - manager.map(inner) - - def test_get_common(self): - opts = loading.get_auth_common_conf_options() - for opt in opts: - self.assertIsInstance(opt, cfg.Opt) - self.assertEqual(2, len(opts)) - - def test_get_named(self): - loaded_opts = loading.get_plugin_options('v2password') - plugin_opts = v2.Password().get_options() - - loaded_names = set([o.name for o in loaded_opts]) - plugin_names = set([o.name for o in plugin_opts]) - - self.assertEqual(plugin_names, loaded_names) - - def test_register_cfg(self): - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - def test_common_conf_options(self): - opts = loading.get_auth_common_conf_options() - - self.assertEqual(2, len(opts)) - auth_type = [o for o in opts if o.name == 'auth_type'][0] - self.assertEqual(1, len(auth_type.deprecated_opts)) - self.assertIsInstance(auth_type.deprecated_opts[0], cfg.DeprecatedOpt) diff --git a/keystoneauth1/tests/unit/loading/test_entry_points.py b/keystoneauth1/tests/unit/loading/test_entry_points.py deleted file mode 100644 index 49968ef..0000000 --- a/keystoneauth1/tests/unit/loading/test_entry_points.py +++ /dev/null @@ -1,35 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import stevedore - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class EntryPointTests(utils.TestCase): - """Simple test that will check that all entry points are loadable.""" - - def test_all_entry_points_are_valid(self): - errors = [] - - def raise_exception_callback(manager, entrypoint, exc): - error = ("Cannot load '%(entrypoint)s' entry_point: %(error)s'" % - {"entrypoint": entrypoint, "error": exc}) - errors.append(error) - - stevedore.ExtensionManager( - namespace=loading.PLUGIN_NAMESPACE, - on_load_failure_callback=raise_exception_callback - ) - - self.assertEqual([], errors) diff --git a/keystoneauth1/tests/unit/loading/test_generic.py b/keystoneauth1/tests/unit/loading/test_generic.py deleted file mode 100644 index 3973e6b..0000000 --- a/keystoneauth1/tests/unit/loading/test_generic.py +++ /dev/null @@ -1,86 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 identity -from keystoneauth1.loading._plugins.identity import generic -from keystoneauth1 import session -from keystoneauth1.tests.unit.loading import utils - - -class PasswordTests(utils.TestCase): - - def test_options(self): - opts = [o.name for o in generic.Password().get_options()] - - allowed_opts = ['username', - 'user-domain-id', - 'user-domain-name', - 'user-id', - 'password', - - 'domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - 'default-domain-id', - 'default-domain-name', - ] - - self.assertEqual(set(allowed_opts), set(opts)) - self.assertEqual(len(allowed_opts), len(opts)) - - def test_loads_v3_with_user_domain(self): - auth_url = 'http://keystone.test:5000' - disc = fixture.DiscoveryList(href=auth_url) - sess = session.Session() - self.requests_mock.get(auth_url, json=disc) - - plugin = generic.Password().load_from_options( - auth_url=auth_url, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex) - - inner_plugin = plugin._do_create_plugin(sess) - - self.assertIsInstance(inner_plugin, identity.V3Password) - self.assertEqual(inner_plugin.auth_url, auth_url + '/v3') - - -class TokenTests(utils.TestCase): - - def test_options(self): - opts = [o.name for o in generic.Token().get_options()] - - allowed_opts = ['token', - 'domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - 'default-domain-id', - 'default-domain-name', - ] - - self.assertEqual(set(allowed_opts), set(opts)) - self.assertEqual(len(allowed_opts), len(opts)) diff --git a/keystoneauth1/tests/unit/loading/test_loading.py b/keystoneauth1/tests/unit/loading/test_loading.py deleted file mode 100644 index ce07c1f..0000000 --- a/keystoneauth1/tests/unit/loading/test_loading.py +++ /dev/null @@ -1,145 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 testtools import matchers - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class PluginA(object): - - def __init__(self, a): - self.val = a - - -class PluginB(object): - - def __init__(self, b): - self.val = b - - -class TestSplitLoader(loading.BaseLoader): - - def get_options(self): - opts = super(TestSplitLoader, self).get_options() - opts += [loading.Opt('a'), loading.Opt('b')] - return opts - - def create_plugin(self, a=None, b=None, **kwargs): - if a: - return PluginA(a) - if b: - return PluginB(b) - - raise AssertionError('Expected A or B') - - -class LoadingTests(utils.TestCase): - - def test_required_values(self): - opts = [loading.Opt('a', required=False), - loading.Opt('b', required=True)] - - Plugin, Loader = utils.create_plugin(opts=opts) - - l = Loader() - v = uuid.uuid4().hex - - p1 = l.load_from_options(b=v) - self.assertEqual(v, p1['b']) - - e = self.assertRaises(exceptions.MissingRequiredOptions, - l.load_from_options, - a=v) - - self.assertEqual(1, len(e.options)) - - for o in e.options: - self.assertIsInstance(o, loading.Opt) - - self.assertEqual('b', e.options[0].name) - - def test_loaders(self): - loaders = loading.get_available_plugin_loaders() - self.assertThat(len(loaders), matchers.GreaterThan(0)) - - for l in loaders.values(): - self.assertIsInstance(l, loading.BaseLoader) - - 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.MockLoader().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']) - - def test_loading_getter_with_kwargs(self): - called_opts = set() - - vals = {'a-bool': False, - 'a-float': 99.99} - - def _getter(opt): - called_opts.add(opt.name) - # return str because oslo.config should convert them back - return str(vals[opt.name]) - - p = utils.MockLoader().load_from_options_getter(_getter, - a_int=66, - a_str='another') - - # only the options not passed by kwargs should get passed to getter - self.assertEqual(set(('a-bool', 'a-float')), called_opts) - - self.assertFalse(p['a_bool']) - self.assertEqual(99.99, p['a_float']) - self.assertEqual('another', p['a_str']) - self.assertEqual(66, p['a_int']) - - def test_create_plugin_loader(self): - val_a = uuid.uuid4().hex - val_b = uuid.uuid4().hex - - loader = TestSplitLoader() - - plugin_a = loader.load_from_options(a=val_a) - plugin_b = loader.load_from_options(b=val_b) - - self.assertIsInstance(plugin_a, PluginA) - self.assertIsInstance(plugin_b, PluginB) - - self.assertEqual(val_a, plugin_a.val) - self.assertEqual(val_b, plugin_b.val) diff --git a/keystoneauth1/tests/unit/loading/test_session.py b/keystoneauth1/tests/unit/loading/test_session.py deleted file mode 100644 index 253ec34..0000000 --- a/keystoneauth1/tests/unit/loading/test_session.py +++ /dev/null @@ -1,114 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 uuid - -from oslo_config import cfg -from oslo_config import fixture as config -from testtools import matchers - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class ConfLoadingTests(utils.TestCase): - - GROUP = 'sessiongroup' - - def setUp(self): - super(ConfLoadingTests, self).setUp() - - self.conf_fixture = self.useFixture(config.Config()) - loading.register_session_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): - return loading.load_session_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 = loading.get_session_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() - loading.register_session_argparse_arguments(self.parser) - - def get_session(self, val, **kwargs): - args = self.parser.parse_args(val.split()) - return loading.load_session_from_argparse_arguments(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) diff --git a/keystoneauth1/tests/unit/loading/test_v3.py b/keystoneauth1/tests/unit/loading/test_v3.py deleted file mode 100644 index 516c0ec..0000000 --- a/keystoneauth1/tests/unit/loading/test_v3.py +++ /dev/null @@ -1,365 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 random -import uuid - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class V3PasswordTests(utils.TestCase): - - def setUp(self): - super(V3PasswordTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3password') - return loader.load_from_options(**kwargs) - - def test_basic(self): - username = uuid.uuid4().hex - user_domain_id = uuid.uuid4().hex - password = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - - p = self.create(username=username, - user_domain_id=user_domain_id, - project_name=project_name, - project_domain_id=project_domain_id, - password=password) - - pw_method = p.auth_methods[0] - - self.assertEqual(username, pw_method.username) - self.assertEqual(user_domain_id, pw_method.user_domain_id) - self.assertEqual(password, pw_method.password) - - self.assertEqual(project_name, p.project_name) - self.assertEqual(project_domain_id, p.project_domain_id) - - def test_without_user_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - def test_without_project_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex, - project_name=uuid.uuid4().hex) - - -class TOTPTests(utils.TestCase): - - def setUp(self): - super(TOTPTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3totp') - return loader.load_from_options(**kwargs) - - def test_basic(self): - username = uuid.uuid4().hex - user_domain_id = uuid.uuid4().hex - # passcode is 6 digits - passcode = ''.join(str(random.randint(0, 9)) for x in range(6)) - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - - p = self.create(username=username, - user_domain_id=user_domain_id, - project_name=project_name, - project_domain_id=project_domain_id, - passcode=passcode) - - totp_method = p.auth_methods[0] - - self.assertEqual(username, totp_method.username) - self.assertEqual(user_domain_id, totp_method.user_domain_id) - self.assertEqual(passcode, totp_method.passcode) - - self.assertEqual(project_name, p.project_name) - self.assertEqual(project_domain_id, p.project_domain_id) - - def test_without_user_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - passcode=uuid.uuid4().hex) - - def test_without_project_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - passcode=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex, - project_name=uuid.uuid4().hex) - - -class OpenIDConnectBaseTests(object): - - plugin_name = None - - def setUp(self): - super(OpenIDConnectBaseTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader(self.plugin_name) - return loader.load_from_options(**kwargs) - - def test_base_options_are_there(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['client-id', 'client-secret', 'access-token-endpoint', - 'access-token-type', 'openid-scope', - 'discovery-endpoint']).issubset( - set([o.name for o in options])) - ) - # openid-scope gets renamed into "scope" - self.assertIn('scope', [o.dest for o in options]) - - -class OpenIDConnectClientCredentialsTests(OpenIDConnectBaseTests, - utils.TestCase): - - plugin_name = "v3oidcclientcredentials" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['openid-scope']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - scope = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectPasswordTests(OpenIDConnectBaseTests, utils.TestCase): - - plugin_name = "v3oidcpassword" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['username', 'password', 'openid-scope']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - username = uuid.uuid4().hex - password = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - scope = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(username=username, - password=password, - identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(username, oidc.username) - self.assertEqual(password, oidc.password) - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectAuthCodeTests(OpenIDConnectBaseTests, utils.TestCase): - - plugin_name = "v3oidcauthcode" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['redirect-uri', 'code']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - redirect_uri = uuid.uuid4().hex - authorization_code = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(code=authorization_code, - redirect_uri=redirect_uri, - identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(redirect_uri, oidc.redirect_uri) - self.assertEqual(authorization_code, oidc.code) - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectAccessToken(utils.TestCase): - - plugin_name = "v3oidcaccesstoken" - - def setUp(self): - super(OpenIDConnectAccessToken, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader(self.plugin_name) - return loader.load_from_options(**kwargs) - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['access-token']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - - oidc = self.create(access_token=access_token, - identity_provider=identity_provider, - protocol=protocol) - - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token, oidc.access_token) - - -class V3TokenlessAuthTests(utils.TestCase): - - def setUp(self): - super(V3TokenlessAuthTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3tokenlessauth') - return loader.load_from_options(**kwargs) - - def test_basic(self): - domain_id = uuid.uuid4().hex - domain_name = uuid.uuid4().hex - project_id = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - - tla = self.create(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) - - self.assertEqual(domain_id, tla.domain_id) - self.assertEqual(domain_name, tla.domain_name) - self.assertEqual(project_id, tla.project_id) - self.assertEqual(project_name, tla.project_name) - self.assertEqual(project_domain_id, tla.project_domain_id) - self.assertEqual(project_domain_name, tla.project_domain_name) - - def test_missing_parameters(self): - self.assertRaises(exceptions.OptionError, - self.create, - domain_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - domain_name=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_name=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_name=None) - # only when a project_name is provided, project_domain_id will - # be use to uniquely identify the project. It's an invalid - # option when it's just by itself. - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_id=uuid.uuid4().hex) - # only when a project_name is provided, project_domain_name will - # be use to uniquely identify the project. It's an invalid - # option when it's just by itself. - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_name=uuid.uuid4().hex) - self.assertRaises(exceptions.OptionError, - self.create, - project_name=uuid.uuid4().hex) diff --git a/keystoneauth1/tests/unit/loading/utils.py b/keystoneauth1/tests/unit/loading/utils.py deleted file mode 100644 index 0e3c2b9..0000000 --- a/keystoneauth1/tests/unit/loading/utils.py +++ /dev/null @@ -1,123 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 -import uuid - -import mock - -from keystoneauth1 import loading -from keystoneauth1.loading import base -from keystoneauth1 import plugin -from keystoneauth1.tests.unit import utils - - -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]) - - -def create_plugin(opts=[], token=None, endpoint=None): - - class Plugin(plugin.BaseAuthPlugin): - - def __init__(self, **kwargs): - self._data = kwargs - - def __getitem__(self, key): - return self._data[key] - - def get_token(self, *args, **kwargs): - return token - - def get_endpoint(self, *args, **kwargs): - return endpoint - - class Loader(loading.BaseLoader): - - @property - def plugin_class(self): - return Plugin - - def get_options(self): - return opts - - return Plugin, Loader - - -class BoolType(object): - - def __eq__(self, other): - """Define equiality for many bool types.""" - # hack around oslo.config equality comparison - return type(self) == type(other) - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequiality for many bool types.""" - return not self.__eq__(other) - - def __call__(self, value): - return str(value).lower() in ('1', 'true', 't', 'yes', 'y') - - -INT_DESC = 'test int' -FLOAT_DESC = 'test float' -BOOL_DESC = 'test bool' -STR_DESC = 'test str' -STR_DEFAULT = uuid.uuid4().hex - - -MockPlugin, MockLoader = create_plugin( - endpoint='http://test', - token='aToken', - opts=[ - loading.Opt('a-int', default=3, type=int, help=INT_DESC), - loading.Opt('a-bool', type=BoolType(), help=BOOL_DESC), - loading.Opt('a-float', type=float, help=FLOAT_DESC), - loading.Opt('a-str', help=STR_DESC, default=STR_DEFAULT), - ] -) - - -class MockManager(object): - - def __init__(self, driver): - self.driver = driver - - -def mock_plugin(loader=MockLoader): - def _wrapper(f): - @functools.wraps(f) - def inner(*args, **kwargs): - with mock.patch.object(base, 'get_plugin_loader') as m: - m.return_value = loader() - args = list(args) + [m] - return f(*args, **kwargs) - - return inner - return _wrapper diff --git a/keystoneauth1/tests/unit/matchers.py b/keystoneauth1/tests/unit/matchers.py deleted file mode 100644 index 6322850..0000000 --- a/keystoneauth1/tests/unit/matchers.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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. - -from lxml import etree -from testtools import matchers - - -class XMLEquals(object): - """Parses two XML documents from strings and compares the results.""" - - def __init__(self, expected): - self.expected = expected - - def __str__(self): - """Return string representation of xml document info.""" - return "%s(%r)" % (self.__class__.__name__, self.expected) - - def match(self, other): - def xml_element_equals(expected_doc, observed_doc): - """Test whether two XML documents are equivalent. - - This is a recursive algorithm that operates on each element in - the hierarchy. Siblings are sorted before being checked to - account for two semantically equivalent documents where siblings - appear in different document order. - - The sorting algorithm is a little weak in that it could fail for - documents where siblings at a given level are the same, but have - different children. - - """ - if expected_doc.tag != observed_doc.tag: - return False - - if expected_doc.attrib != observed_doc.attrib: - return False - - def _sorted_children(doc): - return sorted(doc.getchildren(), key=lambda el: el.tag) - - expected_children = _sorted_children(expected_doc) - observed_children = _sorted_children(observed_doc) - - if len(expected_children) != len(observed_children): - return False - - for expected_el, observed_el in zip(expected_children, - observed_children): - if not xml_element_equals(expected_el, observed_el): - return False - - return True - - parser = etree.XMLParser(remove_blank_text=True) - expected_doc = etree.fromstring(self.expected.strip(), parser) - observed_doc = etree.fromstring(other.strip(), parser) - - if xml_element_equals(expected_doc, observed_doc): - return - - return XMLMismatch(self.expected, other) - - -class XMLMismatch(matchers.Mismatch): - - def __init__(self, expected, other): - self.expected = expected - self.other = other - - def describe(self): - def pretty_xml(xml): - parser = etree.XMLParser(remove_blank_text=True) - doc = etree.fromstring(xml.strip(), parser) - return (etree.tostring(doc, encoding='utf-8', pretty_print=True) - .decode('utf-8')) - - return 'expected =\n%s\nactual =\n%s' % ( - pretty_xml(self.expected), pretty_xml(self.other)) diff --git a/keystoneauth1/tests/unit/oidc_fixtures.py b/keystoneauth1/tests/unit/oidc_fixtures.py deleted file mode 100644 index df81254..0000000 --- a/keystoneauth1/tests/unit/oidc_fixtures.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -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" - } - } -} - -ACCESS_TOKEN_VIA_PASSWORD_RESP = { - "access_token": "z5H1ITZLlJVDHQXqJun", - "token_type": "bearer", - "expires_in": 3599, - "scope": "openid profile", - "refresh_token": "DCERsh83IAhu9bhavrp" -} - -ACCESS_TOKEN_VIA_AUTH_GRANT_RESP = { - "access_token": "ya29.jgGIjfVrBPWLStWSU3eh8ioE6hG06QQ", - "token_type": "Bearer", - "expires_in": 3600, - "refresh_token": "1/ySXNO9XISBMIgOrJDtdun6zK6XiATCKT", - "id_token": "eyJhbGciOiJSUzI1Ni8hOYHuZT8dt_yynmJVhcU" -} - -DISCOVERY_DOCUMENT = { - "authorization_endpoint": "https://localhost:8020/oidc/authorize", - "claims_supported": [ - "sub", - "name", - "preferred_username", - "given_name", - "family_name", - "middle_name", - "nickname", - "profile", - "picture", - "website", - "gender", - "zoneinfo", - "locale", - "updated_at", - "birthdate", - "email", - "email_verified", - "phone_number", - "phone_number_verified", - "address" - ], - "grant_types_supported": [ - "authorization_code", - "password", - ], - "introspection_endpoint": "https://localhost:8020/oidc/introspect", - "issuer": "https://localhost:8020/oidc/", - "jwks_uri": "https://localhost:8020/oidc/jwk", - "op_policy_uri": "https://localhost:8020/oidc/about", - "op_tos_uri": "https://localhost:8020/oidc/about", - "registration_endpoint": "https://localhost:8020/oidc/register", - "revocation_endpoint": "https://localhost:8020/oidc/revoke", - "service_documentation": "https://localhost:8020/oidc/about", - "token_endpoint": "https://localhost:8020/oidc/token", - "userinfo_endpoint": "https://localhost:8020/oidc/userinfo", - "token_endpoint_auth_methods_supported": [ - "client_secret_post", - "client_secret_basic", - "client_secret_jwt", - "private_key_jwt", - "none" - ], -} diff --git a/keystoneauth1/tests/unit/test_betamax_fixture.py b/keystoneauth1/tests/unit/test_betamax_fixture.py deleted file mode 100644 index b537821..0000000 --- a/keystoneauth1/tests/unit/test_betamax_fixture.py +++ /dev/null @@ -1,125 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - -import betamax -from betamax import exceptions -import mock - -from keystoneauth1.fixture import keystoneauth_betamax -from keystoneauth1.fixture import serializer -from keystoneauth1.fixture import v2 as v2Fixtures -from keystoneauth1.identity import v2 -from keystoneauth1 import session - - -class TestBetamaxFixture(testtools.TestCase): - - TEST_USERNAME = 'test_user_name' - TEST_PASSWORD = 'test_password' - TEST_TENANT_NAME = 'test_tenant_name' - TEST_AUTH_URL = 'http://keystoneauth-betamax.test/v2.0/' - - V2_TOKEN = v2Fixtures.Token(tenant_name=TEST_TENANT_NAME, - user_name=TEST_USERNAME) - - def setUp(self): - super(TestBetamaxFixture, self).setUp() - self.ksa_betamax_fixture = self.useFixture( - keystoneauth_betamax.BetamaxFixture( - cassette_name='ksa_betamax_test_cassette', - cassette_library_dir='keystoneauth1/tests/unit/data/', - record=False)) - - def _replay_cassette(self): - plugin = v2.Password( - auth_url=self.TEST_AUTH_URL, - password=self.TEST_PASSWORD, - username=self.TEST_USERNAME, - tenant_name=self.TEST_TENANT_NAME) - s = session.Session() - s.get_token(auth=plugin) - - def test_keystoneauth_betamax_fixture(self): - self._replay_cassette() - - def test_replay_of_bad_url_fails(self): - plugin = v2.Password( - auth_url='http://invalid-auth-url/v2.0/', - password=self.TEST_PASSWORD, - username=self.TEST_USERNAME, - tenant_name=self.TEST_TENANT_NAME) - s = session.Session() - self.assertRaises(exceptions.BetamaxError, s.get_token, auth=plugin) - - -class TestBetamaxFixtureSerializerBehaviour(testtools.TestCase): - """Test the fixture's logic, not its monkey-patching. - - The setUp method of our BetamaxFixture monkey-patches the function to - construct a session. We don't need to test that particular bit of logic - here so we do not need to call useFixture in our setUp method. - """ - - @mock.patch.object(betamax.Betamax, 'register_serializer') - def test_can_pass_custom_serializer(self, register_serializer): - serializer = mock.Mock() - serializer.name = 'mocked-serializer' - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - serializer=serializer, - ) - - register_serializer.assert_called_once_with(serializer) - self.assertIs(serializer, fixture.serializer) - self.assertEqual('mocked-serializer', fixture.serializer_name) - - def test_can_pass_serializer_name(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - serializer_name='json', - ) - - self.assertIsNone(fixture.serializer) - self.assertEqual('json', fixture.serializer_name) - - def test_no_serializer_options_provided(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - ) - - self.assertIs(serializer.YamlJsonSerializer, fixture.serializer) - self.assertEqual('yamljson', fixture.serializer_name) - - def test_no_request_matchers_provided(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - ) - - self.assertDictEqual({}, fixture.use_cassette_kwargs) - - def test_request_matchers(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - request_matchers=['method', 'uri', 'json-body'], - ) - - self.assertDictEqual( - {'match_requests_on': ['method', 'uri', 'json-body']}, - fixture.use_cassette_kwargs, - ) diff --git a/keystoneauth1/tests/unit/test_betamax_hooks.py b/keystoneauth1/tests/unit/test_betamax_hooks.py deleted file mode 100644 index 9eb78c6..0000000 --- a/keystoneauth1/tests/unit/test_betamax_hooks.py +++ /dev/null @@ -1,198 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 betamax -import json -import mock -from requests import models -import testtools - -try: - from requests.packages.urllib3._collections import HTTPHeaderDict -except ImportError: - from betamax.headers import HTTPHeaderDict - -from keystoneauth1.fixture import hooks - - -class TestBetamaxHooks(testtools.TestCase): - - def test_pre_record_hook_v3(self): - fixtures_path = 'keystoneauth1/tests/unit/data' - - with betamax.Betamax.configure() as config: - config.before_record(callback=hooks.pre_record_hook) - - cassette = betamax.cassette.Cassette( - 'test_pre_record_hook', 'json', record_mode=None, - cassette_library_dir=fixtures_path) - - # Create a new object to serialize - r = models.Response() - r.status_code = 200 - r.reason = 'OK' - r.encoding = 'utf-8' - r.headers = {} - r.url = 'http://localhost:35357/' - - # load request and response - with open('%s/keystone_v3_sample_response.json' % fixtures_path) as f: - response_content = json.loads(f.read()) - with open('%s/keystone_v3_sample_request.json' % fixtures_path) as f: - request_content = json.loads(f.read()) - - body_content = { - 'body': { - 'string': json.dumps(response_content), - 'encoding': 'utf-8', - } - } - - betamax.util.add_urllib3_response( - body_content, r, - HTTPHeaderDict({'Accept': 'application/json'})) - response = r - - # Create an associated request - r = models.Request() - r.method = 'GET' - r.url = 'http://localhost:35357/' - r.headers = {} - r.data = {} - response.request = r.prepare() - response.request.headers.update( - {'User-Agent': 'betamax/test header'} - ) - - response.request.body = json.dumps(request_content) - - interaction = cassette.save_interaction(response, response.request) - - # check that all values have been masked - response_content = json.loads( - interaction.data['response']['body']['string']) - self.assertEqual( - response_content['token']['expires_at'], - u'9999-12-31T23:59:59Z') - self.assertEqual( - response_content['token']['project']['domain']['id'], - u'dummy') - self.assertEqual( - response_content['token']['user']['domain']['id'], - u'dummy') - self.assertEqual( - response_content['token']['user']['name'], u'dummy') - - request_content = json.loads( - interaction.data['request']['body']['string']) - self.assertEqual( - request_content['auth']['identity']['password'] - ['user']['domain']['id'], u'dummy') - self.assertEqual( - request_content['auth']['identity']['password'] - ['user']['password'], u'********') - - def test_pre_record_hook_v2(self): - fixtures_path = 'keystoneauth1/tests/unit/data' - - with betamax.Betamax.configure() as config: - config.before_record(callback=hooks.pre_record_hook) - - cassette = betamax.cassette.Cassette( - 'test_pre_record_hook', 'json', record_mode=None, - cassette_library_dir=fixtures_path) - - # Create a new object to serialize - r = models.Response() - r.status_code = 200 - r.reason = 'OK' - r.encoding = 'utf-8' - r.headers = {} - r.url = 'http://localhost:35357/' - - # load request and response - with open('%s/keystone_v2_sample_response.json' % fixtures_path) as f: - response_content = json.loads(f.read()) - with open('%s/keystone_v2_sample_request.json' % fixtures_path) as f: - request_content = json.loads(f.read()) - - body_content = { - 'body': { - 'string': json.dumps(response_content), - 'encoding': 'utf-8', - } - } - - betamax.util.add_urllib3_response( - body_content, r, - HTTPHeaderDict({'Accept': 'application/json'})) - response = r - - # Create an associated request - r = models.Request() - r.method = 'GET' - r.url = 'http://localhost:35357/' - r.headers = {} - r.data = {} - response.request = r.prepare() - response.request.headers.update( - {'User-Agent': 'betamax/test header'} - ) - - response.request.body = json.dumps(request_content) - - interaction = cassette.save_interaction(response, response.request) - - # check that all values have been masked - response_content = json.loads( - interaction.data['response']['body']['string']) - self.assertEqual( - response_content['access']['token']['expires'], - u'9999-12-31T23:59:59Z') - self.assertEqual( - response_content['access']['token']['tenant']['name'], - u'dummy') - self.assertEqual( - response_content['access']['user']['name'], - u'dummy') - - request_content = json.loads( - interaction.data['request']['body']['string']) - self.assertEqual( - request_content['auth']['passwordCredentials']['password'], - u'********') - self.assertEqual( - request_content['auth']['passwordCredentials']['username'], - u'dummy') - self.assertEqual( - request_content['auth']['tenantName'], u'dummy') - - @mock.patch('keystoneauth1.fixture.hooks.mask_fixture_values') - def test_pre_record_hook_empty_body(self, mask_fixture_values): - interaction = mock.Mock() - interaction.data = { - 'request': { - 'body': { - 'encoding': 'utf-8', - 'string': '', - }, - }, - 'response': { - 'body': { - 'encoding': 'utf-8', - 'string': '', - }, - }, - } - - hooks.pre_record_hook(interaction, mock.Mock()) - self.assertFalse(mask_fixture_values.called) diff --git a/keystoneauth1/tests/unit/test_betamax_serializer.py b/keystoneauth1/tests/unit/test_betamax_serializer.py deleted file mode 100644 index a69318d..0000000 --- a/keystoneauth1/tests/unit/test_betamax_serializer.py +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json -import os - -import testtools -import yaml - -from keystoneauth1.fixture import serializer - - -class TestBetamaxSerializer(testtools.TestCase): - - TEST_FILE = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'data', 'ksa_betamax_test_cassette.yaml') - TEST_JSON = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'data', 'ksa_serializer_data.json') - - def setUp(self): - super(TestBetamaxSerializer, self).setUp() - self.serializer = serializer.YamlJsonSerializer() - - def test_deserialize(self): - data = self.serializer.deserialize(open(self.TEST_FILE, 'r').read()) - request = data['http_interactions'][0]['request'] - self.assertEqual( - 'http://keystoneauth-betamax.test/v2.0/tokens', - request['uri']) - payload = json.loads(request['body']['string']) - self.assertEqual('test_tenant_name', payload['auth']['tenantName']) - - def test_serialize(self): - data = json.loads(open(self.TEST_JSON, 'r').read()) - serialized = self.serializer.serialize(data) - data = yaml.safe_load(serialized) - request = data['http_interactions'][0]['request'] - self.assertEqual( - 'http://keystoneauth-betamax.test/v2.0/tokens', - request['uri']) - payload = json.loads(request['body']['string']) - self.assertEqual('test_tenant_name', payload['auth']['tenantName']) diff --git a/keystoneauth1/tests/unit/test_discovery.py b/keystoneauth1/tests/unit/test_discovery.py deleted file mode 100644 index ec5a15c..0000000 --- a/keystoneauth1/tests/unit/test_discovery.py +++ /dev/null @@ -1,951 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json -import mock -import re - -from testtools import matchers - -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -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 = json.dumps({ - "access": { - "token": { - "expires": "2020-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_AUTH_RESPONSE = json.dumps({ - "token": { - "methods": [ - "token", - "password" - ], - - "expires_at": "2020-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" - } - ] - }, - { - "status": "CURRENT", - "updated": "2012-11-21T11:33:21Z", - "id": "v3.0", - "version": "3.27", - "min_version": "3.0", - "next_min_version": "3.4", - "not_before": "2019-12-31", - "links": [ - { - "href": BASE_URL, - "rel": "collection" - }, - { - "href": "%sv3/" % 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 {'versions': {'values': versions}} - - -def _create_single_version(version): - return {'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 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('3.20', (3, 20)) - assertVersion((6, 1), (6, 1)) - assertVersion([1, 40], (1, 40)) - assertVersion((1,), (1, 0)) - assertVersion(['1'], (1, 0)) - assertVersion('latest', (discover.LATEST, discover.LATEST)) - assertVersion(['latest'], (discover.LATEST, discover.LATEST)) - assertVersion(discover.LATEST, (discover.LATEST, discover.LATEST)) - assertVersion((discover.LATEST,), (discover.LATEST, discover.LATEST)) - assertVersion('10.latest', (10, discover.LATEST)) - assertVersion((10, 'latest'), (10, discover.LATEST)) - assertVersion((10, discover.LATEST), (10, discover.LATEST)) - - versionRaises(None) - versionRaises('hello') - versionRaises('1.a') - versionRaises('vacuum') - versionRaises('') - versionRaises(('1', 'a')) - - def test_version_args(self): - """Validate _normalize_version_args.""" - def assert_min_max(in_ver, in_min, in_max, out_min, out_max): - self.assertEqual( - (out_min, out_max), - discover._normalize_version_args(in_ver, in_min, in_max)) - - def normalize_raises(ver, min, max): - self.assertRaises(ValueError, - discover._normalize_version_args, ver, min, max) - - assert_min_max(None, None, None, - None, None) - assert_min_max(None, None, 'v1.2', - None, (1, 2)) - assert_min_max(None, 'v1.2', 'latest', - (1, 2), (discover.LATEST, discover.LATEST)) - assert_min_max(None, 'v1.2', '1.6', - (1, 2), (1, 6)) - assert_min_max(None, 'v1.2', '1.latest', - (1, 2), (1, discover.LATEST)) - assert_min_max(None, 'latest', 'latest', - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - assert_min_max(None, 'latest', None, - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - assert_min_max(None, (1, 2), None, - (1, 2), (discover.LATEST, discover.LATEST)) - assert_min_max('', ('1', '2'), (1, 6), - (1, 2), (1, 6)) - assert_min_max(None, ('1', '2'), (1, discover.LATEST), - (1, 2), (1, discover.LATEST)) - assert_min_max('v1.2', '', None, - (1, 2), (1, discover.LATEST)) - assert_min_max('1.latest', None, '', - (1, discover.LATEST), (1, discover.LATEST)) - assert_min_max('v1', None, None, - (1, 0), (1, discover.LATEST)) - assert_min_max('latest', None, None, - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - - normalize_raises('v1', 'v2', None) - normalize_raises('v1', None, 'v2') - normalize_raises(None, 'latest', 'v1') - normalize_raises(None, 'v1.2', 'v1.1') - normalize_raises(None, 'v1.2', 1) - - def test_version_to_string(self): - def assert_string(inp, out): - self.assertEqual(out, discover.version_to_string(inp)) - - assert_string((discover.LATEST,), 'latest') - assert_string((discover.LATEST, discover.LATEST), 'latest') - assert_string((discover.LATEST, discover.LATEST, discover.LATEST), - 'latest') - assert_string((1,), '1') - assert_string((1, 2), '1.2') - assert_string((1, discover.LATEST), '1.latest') - - def test_version_between(self): - def good(minver, maxver, cand): - self.assertTrue(discover._version_between(minver, maxver, cand)) - - def bad(minver, maxver, cand): - self.assertFalse(discover._version_between(minver, maxver, cand)) - - def exc(minver, maxver, cand): - self.assertRaises(ValueError, - discover._version_between, minver, maxver, cand) - - good((1, 0), (1, 0), (1, 0)) - good((1, 0), (1, 10), (1, 2)) - good(None, (1, 10), (1, 2)) - good((1, 20), (2, 0), (1, 21)) - good((1, 0), (2, discover.LATEST), (1, 21)) - good((1, 0), (2, discover.LATEST), (1, discover.LATEST)) - good((1, 50), (2, discover.LATEST), (2, discover.LATEST)) - - bad((discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST), (1, 0)) - bad(None, None, (1, 0)) - bad((1, 50), (2, discover.LATEST), (3, 0)) - bad((1, 50), (2, discover.LATEST), (3, discover.LATEST)) - bad((1, 50), (2, 5), (2, discover.LATEST)) - - exc((1, 0), (1, 0), None) - exc('v1.0', (1, 0), (1, 0)) - exc((1, 0), 'v1.0', (1, 0)) - exc((1, 0), (1, 0), 'v1.0') - exc((1, 0), None, (1, 0)) - - -class VersionDataTests(utils.TestCase): - - def setUp(self): - super(VersionDataTests, self).setUp() - self.session = session.Session() - - def test_version_data_basics(self): - examples = {'keystone': V3_VERSION_LIST, - 'cinder': CINDER_EXAMPLES, - 'glance': GLANCE_EXAMPLES} - - for path, data in examples.items(): - url = "%s%s" % (BASE_URL, path) - - mock = self.requests_mock.get(url, status_code=300, json=data) - - disc = discover.Discover(self.session, url) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - for v in raw_data: - for n in ('id', 'status', 'links'): - msg = '%s missing from %s version data' % (n, path) - self.assertThat(v, matchers.Annotate(msg, - matchers.Contains(n))) - - for v in clean_data: - for n in ('version', 'url', 'raw_status'): - msg = '%s missing from %s version data' % (n, path) - self.assertThat(v, matchers.Annotate(msg, - matchers.Contains(n))) - - self.assertTrue(mock.called_once) - - def test_version_data_individual(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - for v in raw_data: - self.assertEqual(v['id'], 'v3.0') - self.assertEqual(v['status'], 'stable') - self.assertIn('media-types', v) - self.assertIn('links', v) - - for v in clean_data: - self.assertEqual(v['version'], (3, 0)) - self.assertEqual(v['raw_status'], 'stable') - self.assertEqual(v['url'], V3_URL) - - self.assertTrue(mock.called_once) - - def test_version_data_microversions(self): - """Validate [min_|max_]version conversion to {min|max}_microversion.""" - def setup_mock(versions_in): - # Set up the test data with the input version data - jsondata = { - "versions": [ - dict( - { - "status": "CURRENT", - "id": "v2.2", - "links": [ - { - "href": V3_URL, - "rel": "self" - } - ] - }, - **versions_in - ) - ] - } - self.requests_mock.get( - V3_URL, status_code=200, json=jsondata) - - def test_ok(versions_in, versions_out): - setup_mock(versions_in) - # Ensure the output contains the expected microversions - self.assertEqual( - [ - dict( - { - 'collection': None, - 'version': (2, 2), - 'url': V3_URL, - 'raw_status': 'CURRENT', - }, - **versions_out - ) - ], - discover.Discover(self.session, V3_URL).version_data()) - - def test_exc(versions_in): - setup_mock(versions_in) - # Ensure TypeError is raised - self.assertRaises( - TypeError, - discover.Discover(self.session, V3_URL).version_data) - - # no version info in input - test_ok({}, - {'min_microversion': None, 'max_microversion': None, - 'next_min_version': None, 'not_before': None}) - - # version => max_microversion - test_ok({'version': '2.2'}, - {'min_microversion': None, 'max_microversion': (2, 2), - 'next_min_version': None, 'not_before': None}) - - # max_version supersedes version (even if malformed). min_version & - # normalization. - test_ok({'min_version': '2', 'version': 'foo', 'max_version': '2.2'}, - {'min_microversion': (2, 0), 'max_microversion': (2, 2), - 'next_min_version': None, 'not_before': None}) - - # Edge case: min/max_version ignored if present but "empty"; version - # used for max_microversion. - test_ok({'min_version': '', 'version': '2.1', 'max_version': ''}, - {'min_microversion': None, 'max_microversion': (2, 1), - 'next_min_version': None, 'not_before': None}) - - # next_min_version set - test_ok({'min_version': '2', 'max_version': '2.2', - 'next_min_version': '2.1', 'not_before': '2019-07-01'}, - {'min_microversion': (2, 0), 'max_microversion': (2, 2), - 'next_min_version': (2, 1), 'not_before': '2019-07-01'}) - - # Badly-formatted min_version - test_exc({'min_version': 'foo', 'max_version': '2.1'}) - - # Badly-formatted max_version - test_exc({'min_version': '2.1', 'max_version': 'foo'}) - - # Badly-formatted version (when max_version omitted) - test_exc({'min_version': '2.1', 'version': 'foo'}) - - # Badly-formatted next_min_version - test_exc({'next_min_version': 'bogus', 'not_before': '2019-07-01'}) - - def test_data_for_url(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - for url in (V3_URL, V3_URL + '/'): - data = disc.versioned_data_for(url=url) - self.assertEqual(data['version'], (3, 0)) - self.assertEqual(data['raw_status'], 'stable') - self.assertEqual(data['url'], V3_URL) - - self.assertTrue(mock.called_once) - - def test_data_for_no_version(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - - data = disc.versioned_data_for() - self.assertEqual(data['version'], (3, 0)) - self.assertEqual(data['raw_status'], 'stable') - self.assertEqual(data['url'], V3_URL) - self.assertRaises(TypeError, disc.data_for, version=None) - - self.assertTrue(mock.called_once) - - def test_keystone_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=300, - json=V3_VERSION_LIST) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(2, len(raw_data)) - self.assertEqual(2, len(clean_data)) - - for v in raw_data: - 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) - - for v in clean_data: - self.assertIn(v['version'], ((2, 0), (3, 0))) - self.assertEqual(v['raw_status'], 'stable') - - for version in (disc.data_for('v3.0'), - disc.data_for('3.latest'), - disc.data_for('latest'), - disc.versioned_data_for( - min_version='v3.0', max_version='v3.latest'), - disc.versioned_data_for(min_version='3'), - disc.versioned_data_for(min_version='3.latest'), - disc.versioned_data_for(min_version='latest'), - disc.versioned_data_for(min_version='3.latest', - max_version='latest'), - disc.versioned_data_for(min_version='latest', - max_version='latest'), - disc.versioned_data_for(min_version=2), - disc.versioned_data_for(min_version='2.latest')): - self.assertEqual((3, 0), version['version']) - self.assertEqual('stable', version['raw_status']) - self.assertEqual(V3_URL, version['url']) - - for version in (disc.data_for(2), - disc.data_for('2.latest'), - disc.versioned_data_for( - min_version=2, max_version=(2, discover.LATEST)), - disc.versioned_data_for( - min_version='2.latest', max_version='2.latest')): - 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.assertIsNone(disc.versioned_url_for( - min_version='v4', max_version='v4.latest')) - self.assertEqual(V3_URL, disc.url_for('v3')) - self.assertEqual(V3_URL, disc.versioned_url_for( - min_version='v3', max_version='v3.latest')) - self.assertEqual(V2_URL, disc.url_for('v2')) - self.assertEqual(V2_URL, disc.versioned_url_for( - min_version='v2', max_version='v2.latest')) - - self.assertTrue(mock.called_once) - - def test_cinder_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=300, - json=CINDER_EXAMPLES) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(3, len(raw_data)) - - for v in raw_data: - 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') - elif v['id'] == 'v3.0': - self.assertEqual(v['updated'], '2012-11-21T11:33:21Z') - else: - self.fail("Invalid version found") - - v1_url = "%sv1/" % BASE_URL - v2_url = "%sv2/" % BASE_URL - v3_url = "%sv3/" % BASE_URL - - self.assertEqual(clean_data, [ - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 0), - 'url': v1_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 0), - 'url': v2_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': BASE_URL, - 'max_microversion': (3, 27), - 'min_microversion': (3, 0), - 'next_min_version': (3, 4), - 'not_before': u'2019-12-31', - 'version': (3, 0), - 'url': v3_url, - 'raw_status': 'CURRENT', - }, - ]) - - for version in (disc.data_for('v2.0'), - disc.versioned_data_for(min_version='v2.0', - max_version='v2.latest')): - self.assertEqual((2, 0), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v2_url, version['url']) - - for version in (disc.data_for(1), - disc.versioned_data_for( - min_version=(1,), - max_version=(1, discover.LATEST))): - self.assertEqual((1, 0), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v1_url, version['url']) - - self.assertIsNone(disc.url_for('v4')) - self.assertIsNone(disc.versioned_url_for(min_version='v4', - max_version='v4.latest')) - self.assertEqual(v3_url, disc.url_for('v3')) - self.assertEqual(v3_url, disc.versioned_url_for( - min_version='v3', max_version='v3.latest')) - self.assertEqual(v2_url, disc.url_for('v2')) - self.assertEqual(v2_url, disc.versioned_url_for( - min_version='v2', max_version='v2.latest')) - self.assertEqual(v1_url, disc.url_for('v1')) - self.assertEqual(v1_url, disc.versioned_url_for( - min_version='v1', max_version='v1.latest')) - - self.assertTrue(mock.called_once) - - def test_glance_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=200, - json=GLANCE_EXAMPLES) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(5, len(raw_data)) - - for v in raw_data: - 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") - - v1_url = '%sv1/' % BASE_URL - v2_url = '%sv2/' % BASE_URL - - self.assertEqual(clean_data, [ - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 0), - 'url': v1_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 1), - 'url': v1_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 0), - 'url': v2_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 1), - 'url': v2_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 2), - 'url': v2_url, - 'raw_status': 'CURRENT', - }, - ]) - - for ver in (2, 2.1, 2.2): - for version in (disc.data_for(ver), - disc.versioned_data_for( - min_version=ver, - max_version=(2, discover.LATEST))): - 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): - for version in (disc.data_for(ver), - disc.versioned_data_for( - min_version=ver, - max_version=(1, discover.LATEST))): - 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.versioned_url_for(min_version='v3', - max_version='v3.latest')) - self.assertIsNone(disc.url_for('v2.3')) - self.assertIsNone(disc.versioned_url_for(min_version='v2.3', - max_version='v2.latest')) - - self.assertTrue(mock.called_once) - - 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}] - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, 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}] - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, 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) - - disc = discover.Discover(self.session, 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_links(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'}], - }] - - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, 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 EndpointDataTests(utils.TestCase): - @mock.patch('keystoneauth1.discover.get_discovery') - @mock.patch('keystoneauth1.discover.EndpointData.' - '_get_discovery_url_choices') - def test_run_discovery_cache(self, mock_url_choices, mock_get_disc): - # get_discovery raises so we keep looping - mock_get_disc.side_effect = exceptions.DiscoveryFailure() - # Duplicate 'url1' in here to validate the cache behavior - mock_url_choices.return_value = ('url1', 'url2', 'url1', 'url3') - epd = discover.EndpointData() - epd._run_discovery( - session='sess', cache='cache', min_version='min', - max_version='max', project_id='projid', - allow_version_hack='allow_hack', discover_versions='disc_vers') - # Only one call with 'url1' - self.assertEqual(3, mock_get_disc.call_count) - mock_get_disc.assert_has_calls( - [mock.call('sess', url, cache='cache', authenticated=False) - for url in ('url1', 'url2', 'url3')]) diff --git a/keystoneauth1/tests/unit/test_fixtures.py b/keystoneauth1/tests/unit/test_fixtures.py deleted file mode 100644 index cded4ca..0000000 --- a/keystoneauth1/tests/unit/test_fixtures.py +++ /dev/null @@ -1,342 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.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']) - - def test_token_bind(self): - name1 = uuid.uuid4().hex - data1 = uuid.uuid4().hex - name2 = uuid.uuid4().hex - data2 = {uuid.uuid4().hex: uuid.uuid4().hex} - - token = fixture.V2Token() - token.set_bind(name1, data1) - token.set_bind(name2, data2) - - self.assertEqual({name1: data1, name2: data2}, - token['access']['token']['bind']) - - -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']) - - self.assertIsNone(token.get('token', {}).get('is_domain')) - - 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_project_as_domain_scoped(self): - project_id = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - project_is_domain = True - - token = fixture.V3Token(project_id=project_id, - project_name=project_name, - project_domain_id=project_domain_id, - project_domain_name=project_domain_name, - project_is_domain=project_is_domain) - - 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']) - self.assertEqual(project_is_domain, token['token']['is_domain']) - - 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']) - - def test_empty_default_service_providers(self): - token = fixture.V3Token() - self.assertIsNone(token.service_providers) - - def test_service_providers(self): - def new_sp(): - return { - 'id': uuid.uuid4().hex, - 'sp_url': uuid.uuid4().hex, - 'auth_url': uuid.uuid4().hex - } - ref_service_providers = [new_sp(), new_sp()] - token = fixture.V3Token() - for sp in ref_service_providers: - token.add_service_provider(sp['id'], - sp['auth_url'], - sp['sp_url']) - self.assertEqual(ref_service_providers, token.service_providers) - self.assertEqual(ref_service_providers, - token['token']['service_providers']) - - def test_token_bind(self): - name1 = uuid.uuid4().hex - data1 = uuid.uuid4().hex - name2 = uuid.uuid4().hex - data2 = {uuid.uuid4().hex: uuid.uuid4().hex} - - token = fixture.V3Token() - token.set_bind(name1, data1) - token.set_bind(name2, data2) - - self.assertEqual({name1: data1, name2: data2}, - token['token']['bind']) - - def test_is_admin_project(self): - token = fixture.V3Token() - self.assertIsNone(token.is_admin_project) - self.assertNotIn('is_admin_project', token['token']) - - token.is_admin_project = True - self.assertIs(True, token.is_admin_project) - self.assertIs(True, token['token']['is_admin_project']) - - token.is_admin_project = False - self.assertIs(False, token.is_admin_project) - self.assertIs(False, token['token']['is_admin_project']) - - del token.is_admin_project - self.assertIsNone(token.is_admin_project) - self.assertNotIn('is_admin_project', token['token']) diff --git a/keystoneauth1/tests/unit/test_hacking_checks.py b/keystoneauth1/tests/unit/test_hacking_checks.py deleted file mode 100644 index b81ede7..0000000 --- a/keystoneauth1/tests/unit/test_hacking_checks.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 textwrap - -import mock -import pep8 -import testtools - -from keystoneauth1.hacking import checks -from keystoneauth1.tests.unit import keystoneauth_fixtures - - -class TestCheckOsloNamespaceImports(testtools.TestCase): - - # We are patching pep8 so that only the check under test is actually - # installed. - @mock.patch('pep8._checks', - {'physical_line': {}, 'logical_line': {}, 'tree': {}}) - def run_check(self, code): - pep8.register_check(checks.check_oslo_namespace_imports) - - lines = textwrap.dedent(code).strip().splitlines(True) - - checker = pep8.Checker(lines=lines) - checker.check_all() - checker.report._deferred_print.sort() - return checker.report._deferred_print - - def assert_has_errors(self, code, expected_errors=None): - actual_errors = [e[:3] for e in self.run_check(code)] - self.assertEqual(expected_errors or [], actual_errors) - - def test(self): - code_ex = self.useFixture(keystoneauth_fixtures.HackingCode()) - code = code_ex.oslo_namespace_imports['code'] - errors = code_ex.oslo_namespace_imports['expected_errors'] - self.assert_has_errors(code, expected_errors=errors) diff --git a/keystoneauth1/tests/unit/test_matchers.py b/keystoneauth1/tests/unit/test_matchers.py deleted file mode 100644 index 4e704b6..0000000 --- a/keystoneauth1/tests/unit/test_matchers.py +++ /dev/null @@ -1,93 +0,0 @@ -# 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 testtools -from testtools import matchers as tt_matchers - -from keystoneauth1.tests.unit import matchers as ks_matchers - -# NOTE(jamielennox): The tests in this file are copied form the non-public -# testtools.tests.matchers.helpers.TestMatchersInterface. - - -class TestXMLEquals(testtools.TestCase): - matches_xml = b""" - - - - -""" - equivalent_xml = b""" - - - - -""" - mismatches_xml = b""" - - - -""" - mismatches_description = """expected = - - - - - -actual = - - - -""" - - matches_matcher = ks_matchers.XMLEquals(matches_xml) - matches_matches = [matches_xml, equivalent_xml] - matches_mismatches = [mismatches_xml] - describe_examples = [ - (mismatches_description, mismatches_xml, matches_matcher), - ] - str_examples = [('XMLEquals(%r)' % matches_xml, matches_matcher)] - - def test_matches_match(self): - matcher = self.matches_matcher - matches = self.matches_matches - mismatches = self.matches_mismatches - for candidate in matches: - self.assertIsNone(matcher.match(candidate)) - for candidate in mismatches: - mismatch = matcher.match(candidate) - self.assertIsNotNone(mismatch) - self.assertIsNotNone(getattr(mismatch, 'describe', None)) - - def test__str__(self): - # [(expected, object to __str__)]. - examples = self.str_examples - for expected, matcher in examples: - self.assertThat(matcher, tt_matchers.DocTestMatches(expected)) - - def test_describe_difference(self): - # [(expected, matchee, matcher), ...] - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - self.assertEqual(difference, mismatch.describe()) - - def test_mismatch_details(self): - # The mismatch object must provide get_details, which must return a - # dictionary mapping names to Content objects. - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - details = mismatch.get_details() - self.assertEqual(dict(details), details) diff --git a/keystoneauth1/tests/unit/test_noauth.py b/keystoneauth1/tests/unit/test_noauth.py deleted file mode 100644 index 8050b69..0000000 --- a/keystoneauth1/tests/unit/test_noauth.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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.loading._plugins import noauth as loader -from keystoneauth1 import noauth -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class NoAuthTest(utils.TestCase): - - NOAUTH_TOKEN = 'notused' - TEST_URL = 'http://server/prefix' - - def test_basic_case(self): - self.requests_mock.get(self.TEST_URL, text='body') - - a = noauth.NoAuth() - s = session.Session(auth=a) - - data = s.get(self.TEST_URL, authenticated=True) - - self.assertEqual(data.text, 'body') - self.assertRequestHeaderEqual('X-Auth-Token', self.NOAUTH_TOKEN) - self.assertIsNone(a.get_endpoint(s)) - - def test_noauth_options(self): - self.assertEqual([], loader.NoAuth().get_options()) diff --git a/keystoneauth1/tests/unit/test_service_token.py b/keystoneauth1/tests/unit/test_service_token.py deleted file mode 100644 index 1fe2056..0000000 --- a/keystoneauth1/tests/unit/test_service_token.py +++ /dev/null @@ -1,116 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 identity -from keystoneauth1 import service_token -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class ServiceTokenTests(utils.TestCase): - - TEST_URL = 'http://test.example.com/path/' - USER_URL = 'http://user-keystone.example.com/v3' - SERVICE_URL = 'http://service-keystone.example.com/v3' - - def setUp(self): - super(ServiceTokenTests, self).setUp() - - self.user_token_id = uuid.uuid4().hex - self.user_token = fixture.V3Token() - self.user_token.set_project_scope() - self.user_auth = identity.V3Password(auth_url=self.USER_URL, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - self.service_token_id = uuid.uuid4().hex - self.service_token = fixture.V3Token() - self.service_token.set_project_scope() - self.service_auth = identity.V3Password(auth_url=self.SERVICE_URL, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - for t in (self.user_token, self.service_token): - s = t.add_service('identity') - s.add_standard_endpoints(public='http://keystone.example.com', - admin='http://keystone.example.com', - internal='http://keystone.example.com') - - self.test_data = {'data': uuid.uuid4().hex} - - self.user_mock = self.requests_mock.post( - self.USER_URL + '/auth/tokens', - json=self.user_token, - headers={'X-Subject-Token': self.user_token_id}) - - self.service_mock = self.requests_mock.post( - self.SERVICE_URL + '/auth/tokens', - json=self.service_token, - headers={'X-Subject-Token': self.service_token_id}) - - self.requests_mock.get(self.TEST_URL, json=self.test_data) - - self.combined_auth = service_token.ServiceTokenAuthWrapper( - self.user_auth, - self.service_auth) - - self.session = session.Session(auth=self.combined_auth) - - def test_setting_service_token(self): - self.session.get(self.TEST_URL) - - headers = self.requests_mock.last_request.headers - - self.assertEqual(self.user_token_id, headers['X-Auth-Token']) - self.assertEqual(self.service_token_id, headers['X-Service-Token']) - - self.assertTrue(self.user_mock.called_once) - self.assertTrue(self.service_mock.called_once) - - def test_invalidation(self): - text = uuid.uuid4().hex - test_url = 'http://test.example.com/abc' - - response_list = [{'status_code': 401}, {'text': text}] - mock = self.requests_mock.get(test_url, response_list=response_list) - - resp = self.session.get(test_url) - self.assertEqual(text, resp.text) - - self.assertEqual(2, mock.call_count) - self.assertEqual(2, self.user_mock.call_count) - self.assertEqual(2, self.service_mock.call_count) - - def test_pass_throughs(self): - self.assertEqual(self.user_auth.get_token(self.session), - self.combined_auth.get_token(self.session)) - - self.assertEqual( - self.user_auth.get_endpoint(self.session, 'identity'), - self.combined_auth.get_endpoint(self.session, 'identity')) - - self.assertEqual(self.user_auth.get_user_id(self.session), - self.combined_auth.get_user_id(self.session)) - - self.assertEqual(self.user_auth.get_project_id(self.session), - self.combined_auth.get_project_id(self.session)) - - self.assertEqual(self.user_auth.get_sp_auth_url(self.session, 'a'), - self.combined_auth.get_sp_auth_url(self.session, 'a')) - - self.assertEqual(self.user_auth.get_sp_url(self.session, 'a'), - self.combined_auth.get_sp_url(self.session, 'a')) diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py deleted file mode 100644 index 5aa0ccb..0000000 --- a/keystoneauth1/tests/unit/test_session.py +++ /dev/null @@ -1,1540 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import itertools -import json -import logging -import sys -import uuid - -import mock -import requests -import requests.auth -import six -from testtools import matchers - -from keystoneauth1 import adapter -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import plugin -from keystoneauth1 import session as client_session -from keystoneauth1.tests.unit import utils -from keystoneauth1 import token_endpoint - - -class RequestsAuth(requests.auth.AuthBase): - - def __init__(self, *args, **kwargs): - super(RequestsAuth, self).__init__(*args, **kwargs) - self.header_name = uuid.uuid4().hex - self.header_val = uuid.uuid4().hex - self.called = False - - def __call__(self, request): - request.headers[self.header_name] = self.header_val - self.called = True - return request - - -class SessionTests(utils.TestCase): - - TEST_URL = 'http://127.0.0.1:5000/' - - 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_set_microversion_headers(self): - - # String microversion, specified service type - headers = {} - client_session.Session._set_microversion_headers( - headers, '2.30', 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # Tuple microversion, service type via endpoint_filter - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'compute'}) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # 'latest' (string) microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, 'latest', 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute latest') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest') - self.assertEqual(len(headers.keys()), 2) - - # LATEST (tuple) microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (discover.LATEST, discover.LATEST), 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute latest') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest') - self.assertEqual(len(headers.keys()), 2) - - # ironic microversion, specified service type - headers = {} - client_session.Session._set_microversion_headers( - headers, '2.30', 'baremetal', None) - self.assertEqual(headers['OpenStack-API-Version'], 'baremetal 2.30') - self.assertEqual(headers['X-OpenStack-Ironic-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # volumev2 service-type - volume microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'volumev2'}) - self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30') - self.assertEqual(len(headers.keys()), 1) - - # block-storage service-type - volume microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'block-storage'}) - self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30') - self.assertEqual(len(headers.keys()), 1) - - # Headers already exist - no change - headers = { - 'OpenStack-API-Version': 'compute 2.30', - 'X-OpenStack-Nova-API-Version': '2.30', - } - client_session.Session._set_microversion_headers( - headers, (2, 31), None, {'service_type': 'volume'}) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - - # Can't specify a 'M.latest' microversion - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, '2.latest', 'service_type', None) - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, discover.LATEST), 'service_type', None) - - # Normalization error - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, 'bogus', 'service_type', None) - - # No service type in param or endpoint filter - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, 30), None, None) - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, 30), None, {'no_service_type': 'here'}) - - def test_microversion(self): - # microversion not specified - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestNotInHeader('OpenStack-API-Version') - - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL, microversion='2.30', - microversion_service_type='compute', - endpoint_filter={'endpoint': 'filter'}) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual('OpenStack-API-Version', 'compute 2.30') - self.assertRequestHeaderEqual('X-OpenStack-Nova-API-Version', '2.30') - - def test_user_agent(self): - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - '%s %s' % ("run.py", client_session.DEFAULT_USER_AGENT)) - - custom_agent = 'custom-agent/1.0' - session = client_session.Session(user_agent=custom_agent) - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - '%s %s' % (custom_agent, client_session.DEFAULT_USER_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') - - # If sys.argv is an empty list, then doesn't fail. - with mock.patch.object(sys, 'argv', []): - session = client_session.Session() - resp = session.get(self.TEST_URL) - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - client_session.DEFAULT_USER_AGENT) - - # If sys.argv[0] is an empty string, then doesn't fail. - with mock.patch.object(sys, 'argv', ['']): - session = client_session.Session() - resp = session.get(self.TEST_URL) - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - client_session.DEFAULT_USER_AGENT) - - def test_http_session_opts(self): - session = client_session.Session(cert='cert.pem', timeout=5, - verify='certs') - - FAKE_RESP = utils.TestResponse({'status_code': 200, '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_session_debug_output_logs_openstack_request_id(self): - """Test x-openstack-request-id is logged in debug logs.""" - def get_response(log=True): - session = client_session.Session(verify=False) - endpoint_filter = {'service_name': 'Identity'} - headers = {'X-OpenStack-Request-Id': 'req-1234'} - body = 'BODYRESPONSE' - data = 'BODYDATA' - all_headers = dict(itertools.chain(headers.items())) - self.stub_url('POST', text=body, headers=all_headers) - resp = session.post(self.TEST_URL, endpoint_filter=endpoint_filter, - headers=all_headers, data=data, log=log) - return resp - - # if log is disabled then request-id is not logged in debug logs - resp = get_response(log=False) - self.assertEqual(resp.status_code, 200) - - expected_log = ('POST call to Identity for %s used request ' - 'id req-1234' % self.TEST_URL) - self.assertNotIn(expected_log, self.logger.output) - - # if log is enabled then request-id is logged in debug logs - resp = get_response() - self.assertEqual(resp.status_code, 200) - self.assertIn(expected_log, 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 = json.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 = json.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 = json.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_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): - self.stub_url('GET', exc=requests.exceptions.Timeout()) - - session = client_session.Session() - retries = 3 - - with mock.patch('time.sleep') as m: - self.assertRaises(exceptions.ConnectTimeout, - 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 - - self.stub_url('GET', exc=requests.exceptions.SSLError(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_json_content_type(self): - session = client_session.Session() - self.stub_url('POST', text='response') - resp = session.post( - self.TEST_URL, - json=[{'op': 'replace', - 'path': '/name', - 'value': 'new_name'}], - headers={'Content-Type': 'application/json-patch+json'}) - - self.assertEqual('POST', self.requests_mock.last_request.method) - self.assertEqual(resp.text, 'response') - self.assertTrue(resp.ok) - self.assertRequestBodyIs( - json=[{'op': 'replace', - 'path': '/name', - 'value': 'new_name'}]) - self.assertContentTypeIs('application/json-patch+json') - - -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_redirects(self, method='GET', status_code=305, - redirect_kwargs={}, final_kwargs={}): - 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) - - def test_permanent_redirect_308(self): - session = client_session.Session() - self.setup_redirects(status_code=308) - resp = session.get(self.REDIRECT_CHAIN[-2]) - self.assertResponse(resp) - - -class AuthPlugin(plugin.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(plugin.BaseAuthPlugin): - - ENDPOINT = 'http://fakeendpoint/' - TOKEN = utils.TestCase.TEST_TOKEN - USER_ID = uuid.uuid4().hex - PROJECT_ID = uuid.uuid4().hex - - def __init__(self, invalidate=True): - self.get_token_called = False - self.get_endpoint_called = False - self.endpoint_arguments = {} - self.invalidate_called = False - self.get_project_id_called = False - self.get_user_id_called = False - self._invalidate = invalidate - - def get_token(self, session): - self.get_token_called = True - return self.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 - - def get_project_id(self, session, **kwargs): - self.get_project_id_called = True - return self.PROJECT_ID - - def get_user_id(self, session, **kwargs): - self.get_user_id_called = True - return self.USER_ID - - -class SessionAuthTests(utils.TestCase): - - TEST_URL = 'http://127.0.0.1:5000/' - TEST_JSON = {'hello': 'world'} - - 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 = RequestsAuth() - - self.requests_mock.get(self.TEST_URL, text='resp') - - sess.get(self.TEST_URL, requests_auth=requests_auth) - last = self.requests_mock.last_request - - self.assertEqual(requests_auth.header_val, - last.headers[requests_auth.header_name]) - self.assertTrue(requests_auth.called) - - 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) - - self.assertFalse(auth.get_user_id_called) - self.assertFalse(auth.get_project_id_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) - - self.assertFalse(auth.get_user_id_called) - self.assertFalse(auth.get_project_id_called) - - def test_endpoint_override_does_id_replacement(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(project_id)s/%(user_id)s' - path = 'path' - replacements = {'user_id': CalledAuthPlugin.USER_ID, - 'project_id': CalledAuthPlugin.PROJECT_ID} - override_url = override_base % replacements + '/' + 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.assertTrue(auth.get_user_id_called) - self.assertTrue(auth.get_project_id_called) - self.assertFalse(auth.get_endpoint_called) - - def test_endpoint_override_fails_to_replace_if_none(self): - # The token_endpoint plugin doesn't know user_id or project_id - auth = token_endpoint.Token(uuid.uuid4().hex, uuid.uuid4().hex) - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(project_id)s' - - e = self.assertRaises(ValueError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - - self.assertIn('project_id', str(e)) - override_base = 'http://mytest/%(user_id)s' - - e = self.assertRaises(ValueError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - self.assertIn('user_id', str(e)) - - def test_endpoint_override_fails_to_do_unknown_replacement(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(unknown_id)s' - - e = self.assertRaises(AttributeError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - self.assertIn('unknown_id', str(e)) - - 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 = six.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(self.TEST_URL, self.logger.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 - ALLOW = {'allow_deprecated': False, - 'allow_experimental': True, - 'allow_unknown': True} - - TEST_URL = CalledAuthPlugin.ENDPOINT - - def _create_loaded_adapter(self, sess=None, auth=None): - return adapter.Adapter(sess or client_session.Session(), - auth=auth or CalledAuthPlugin(), - 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, - allow=self.ALLOW) - - 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.assertEqual(self.ALLOW, - adpt.auth.endpoint_arguments['allow']) - self.assertTrue(adpt.auth.get_token_called) - self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) - - def test_setting_global_id_on_request(self): - global_id = "req-%s" % uuid.uuid4() - response = uuid.uuid4().hex - self.stub_url('GET', text=response) - adpt = adapter.Adapter(client_session.Session(), - auth=CalledAuthPlugin(), - 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, - allow=self.ALLOW, - global_request_id=global_id) - resp = adpt.get('/') - self.assertEqual(resp.text, response) - - self._verify_endpoint_called(adpt) - self.assertEqual(self.ALLOW, - adpt.auth.endpoint_arguments['allow']) - self.assertTrue(adpt.auth.get_token_called) - self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id) - - 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 = json.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) - - self.stub_url('GET', exc=requests.exceptions.ConnectionError()) - - with mock.patch('time.sleep') as m: - self.assertRaises(exceptions.ConnectionError, - 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 = six.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(self.TEST_URL, self.logger.output) - self.assertNotIn(list(response.keys())[0], self.logger.output) - self.assertNotIn(list(response.values())[0], self.logger.output) - - def test_unknown_connection_error(self): - self.stub_url('GET', exc=requests.exceptions.RequestException) - self.assertRaises(exceptions.UnknownConnectionError, - client_session.Session().request, - self.TEST_URL, - 'GET') - - def test_additional_headers(self): - session_key = uuid.uuid4().hex - session_val = uuid.uuid4().hex - adapter_key = uuid.uuid4().hex - adapter_val = uuid.uuid4().hex - request_key = uuid.uuid4().hex - request_val = uuid.uuid4().hex - text = uuid.uuid4().hex - - url = 'http://keystone.test.com' - self.requests_mock.get(url, text=text) - - sess = client_session.Session( - additional_headers={session_key: session_val}) - adap = adapter.Adapter(session=sess, - additional_headers={adapter_key: adapter_val}) - resp = adap.get(url, headers={request_key: request_val}) - - request = self.requests_mock.last_request - - self.assertEqual(resp.text, text) - self.assertEqual(session_val, request.headers[session_key]) - self.assertEqual(adapter_val, request.headers[adapter_key]) - self.assertEqual(request_val, request.headers[request_key]) - - def test_additional_headers_overrides(self): - header = uuid.uuid4().hex - session_val = uuid.uuid4().hex - adapter_val = uuid.uuid4().hex - request_val = uuid.uuid4().hex - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - sess = client_session.Session(additional_headers={header: session_val}) - adap = adapter.Adapter(session=sess) - - adap.get(url) - self.assertEqual(session_val, - self.requests_mock.last_request.headers[header]) - - adap.additional_headers[header] = adapter_val - adap.get(url) - self.assertEqual(adapter_val, - self.requests_mock.last_request.headers[header]) - - adap.get(url, headers={header: request_val}) - self.assertEqual(request_val, - self.requests_mock.last_request.headers[header]) - - def test_adapter_user_agent_session_adapter(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_version_on_adapter(self): - - class TestAdapter(adapter.Adapter): - - client_name = 'testclient' - client_version = '4.5.6' - - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = TestAdapter(session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_no_app_version(self): - sess = client_session.Session(app_name='ksatest') - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_no_client_version(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(client_name='testclient', session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_additional(self): - sess = client_session.Session(app_name='ksatest', - app_version='1.2.3', - additional_user_agent=[('one', '1.1.1'), - ('two', '2.2.2')]) - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 one/1.1.1 two/2.2.2 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_adapter(self): - sess = client_session.Session() - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_override(self): - sess = client_session.Session(app_name='ksatest', - app_version='1.2.3', - additional_user_agent=[('one', '1.1.1'), - ('two', '2.2.2')]) - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - override_user_agent = '%s/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) - adap.get(url, user_agent=override_user_agent) - - self.assertEqual(override_user_agent, - self.requests_mock.last_request.headers['User-Agent']) - - def test_nested_adapters(self): - text = uuid.uuid4().hex - token = uuid.uuid4().hex - url = 'http://keystone.example.com/path' - - sess = client_session.Session() - auth = CalledAuthPlugin() - auth.ENDPOINT = url - auth.TOKEN = token - - adap1 = adapter.Adapter(session=sess, - interface='public') - adap2 = adapter.Adapter(session=adap1, - service_type='identity', - auth=auth) - - self.requests_mock.get(url + '/test', text=text) - - resp = adap2.get('/test') - - self.assertEqual(text, resp.text) - self.assertTrue(auth.get_endpoint_called) - - self.assertEqual('public', auth.endpoint_arguments['interface']) - self.assertEqual('identity', auth.endpoint_arguments['service_type']) - - last_token = self.requests_mock.last_request.headers['X-Auth-Token'] - self.assertEqual(token, last_token) - - def test_default_microversion(self): - sess = client_session.Session() - url = 'http://url' - - def validate(adap_kwargs, get_kwargs, exp_kwargs): - with mock.patch.object(sess, 'request') as m: - adapter.Adapter(sess, **adap_kwargs).get(url, **get_kwargs) - m.assert_called_once_with(url, 'GET', endpoint_filter={}, - **exp_kwargs) - - # No default_microversion in Adapter, no microversion in get() - validate({}, {}, {}) - - # default_microversion in Adapter, no microversion in get() - validate({'default_microversion': '1.2'}, {}, {'microversion': '1.2'}) - - # No default_microversion in Adapter, microversion specified in get() - validate({}, {'microversion': '1.2'}, {'microversion': '1.2'}) - - # microversion in get() overrides default_microversion in Adapter - validate({'default_microversion': '1.2'}, {'microversion': '1.5'}, - {'microversion': '1.5'}) - - -class TCPKeepAliveAdapterTest(utils.TestCase): - - def setUp(self): - super(TCPKeepAliveAdapterTest, self).setUp() - self.init_poolmanager = self.patch( - client_session.requests.adapters.HTTPAdapter, - 'init_poolmanager') - self.constructor = self.patch( - client_session.TCPKeepAliveAdapter, '__init__', lambda self: None) - - def test_init_poolmanager_with_requests_lesser_than_2_4_1(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 0)) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then no socket_options are given - self.init_poolmanager.assert_called_once_with(1, 2, 3) - - def test_init_poolmanager_with_basic_options(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then no socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - - def test_init_poolmanager_with_tcp_keepidle(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPIDLE']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)]) - - def test_init_poolmanager_with_tcp_keepcnt(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - self.patch(client_session.utils, 'is_windows_linux_subsystem', False) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPCNT']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)]) - - def test_init_poolmanager_with_tcp_keepcnt_on_windows(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - self.patch(client_session.utils, 'is_windows_linux_subsystem', True) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPCNT']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - - def test_init_poolmanager_with_tcp_keepintvl(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPINTVL']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)]) - - def test_init_poolmanager_with_given_optionsl(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - given_adapter = client_session.TCPKeepAliveAdapter() - given_options = object() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3, socket_options=given_options) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=given_options) - - def patch_socket_with_options(self, option_names): - # to mock socket module with exactly the attributes I want I create - # a class with that attributes - socket = type('socket', (object,), - {name: 'socket.' + name for name in option_names}) - return self.patch(client_session, 'socket', socket) - - def patch(self, target, name, *args, **kwargs): - context = mock.patch.object(target, name, *args, **kwargs) - patch = context.start() - self.addCleanup(context.stop) - return patch diff --git a/keystoneauth1/tests/unit/test_token_endpoint.py b/keystoneauth1/tests/unit/test_token_endpoint.py deleted file mode 100644 index ff28c05..0000000 --- a/keystoneauth1/tests/unit/test_token_endpoint.py +++ /dev/null @@ -1,76 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 keystoneauth1.loading._plugins import admin_token as loader -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils -from keystoneauth1 import token_endpoint - - -class TokenEndpointTest(utils.TestCase): - - TEST_TOKEN = 'aToken' - TEST_URL = 'http://server/prefix' - - 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_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)) - - -class AdminTokenTest(utils.TestCase): - - def test_token_endpoint_options(self): - opt_names = [opt.name for opt in loader.AdminToken().get_options()] - - self.assertThat(opt_names, matchers.HasLength(2)) - - self.assertIn('token', opt_names) - self.assertIn('endpoint', opt_names) - - def test_token_endpoint_deprecated_options(self): - endpoint_opt = [ - opt for opt in loader.AdminToken().get_options() - if opt.name == 'endpoint'][0] - - opt_names = [opt.name for opt in endpoint_opt.deprecated] - - self.assertEqual(['url'], opt_names) diff --git a/keystoneauth1/tests/unit/test_utils.py b/keystoneauth1/tests/unit/test_utils.py deleted file mode 100644 index 8ec9edc..0000000 --- a/keystoneauth1/tests/unit/test_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 keystoneauth1 import _utils - - -class UtilsTests(testtools.TestCase): - - def test_get_logger(self): - self.assertEqual('keystoneauth.tests.unit.test_utils', - _utils.get_logger(__name__).name) diff --git a/keystoneauth1/tests/unit/utils.py b/keystoneauth1/tests/unit/utils.py deleted file mode 100644 index 4a77b16..0000000 --- a/keystoneauth1/tests/unit/utils.py +++ /dev/null @@ -1,153 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 json as jsonutils -import logging -import time -import uuid - -import fixtures -import requests -from requests_mock.contrib import fixture -from six.moves.urllib import parse as urlparse -import testtools - - -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.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) - - fixtures.MockPatchObject(time, 'time', lambda: 1234) - - 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.setdefault('Content-Type', 'application/json') - - if parts: - url = '/'.join([p.strip('/') for p in [base_url] + parts]) - else: - url = base_url - - url = url.replace("/?", "?") - return 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 assertContentTypeIs(self, content_type): - last_request = self.requests_mock.last_request - self.assertEqual(last_request.headers['Content-Type'], content_type) - - 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 assertRequestNotInHeader(self, name): - """Verify that the last request made does not contain a header key. - - The request must have already been made. - """ - headers = self.requests_mock.last_request.headers - self.assertNotIn(name, headers) - - -class TestResponse(requests.Response): - """Class used to wrap requests.Response. - - This provides some convenience to initialize with a dict. - """ - - def __init__(self, data): - self._text = None - super(TestResponse, self).__init__() - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - headers = data.get('headers') - if headers: - self.headers.update(headers) - # Fake the text attribute to streamline Response creation - # _content is defined by requests.Response - self._content = data.get('text') - else: - self.status_code = data - - def __eq__(self, other): - """Define equiality behavior of request and response.""" - return self.__dict__ == other.__dict__ - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequiality behavior of request and response.""" - return not self.__eq__(other) - - @property - def text(self): - return self.content diff --git a/keystoneauth1/token_endpoint.py b/keystoneauth1/token_endpoint.py deleted file mode 100644 index 675d4c1..0000000 --- a/keystoneauth1/token_endpoint.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 - - -class Token(plugin.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 - 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 - - def get_auth_ref(self, session, **kwargs): - """Return the authentication reference of an auth plugin. - - :param session: A session object to be used for communication - :type session: keystoneauth1.session.session - """ - # token plugin does not have an auth ref, because it's a - # "static" authentication using a pre-existing token. - return None diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/notes/1583780-700f99713e06324e.yaml b/releasenotes/notes/1583780-700f99713e06324e.yaml deleted file mode 100644 index 343392c..0000000 --- a/releasenotes/notes/1583780-700f99713e06324e.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - Added a new OidcAccessToken plugin, accessible via the 'v3oidcaccesstoken' - entry point, making possible to authenticate using an existing OpenID - Connect Access token. -fixes: - - > - [`bug 1583780 `_] - OpenID connect support should include authenticating using directly an access token. diff --git a/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml b/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml deleted file mode 100644 index a5dadd7..0000000 --- a/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - Add support for the Client Credentials OpenID Connect - grant type. diff --git a/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml b/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml deleted file mode 100644 index 80b4bc7..0000000 --- a/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -features: - - > - Add support for the `OpenID Connect Discovery Document - `_ - into the OpenID Connect related plugins. Now it is possible to only pass the - `discovery-url` option and the plugins will try to fetch the required - metadata from there. -fixes: - - > - [`bug 1583682 `_] - OpenID Connect plugins should support OpenID Connect Discovery. diff --git a/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml b/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml deleted file mode 100644 index 72bc114..0000000 --- a/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -prelude: > - Add the prompt parameter to loader Opts -features: - - The prompt parameter was added to the Opts provided by auth plugins. The - presence of the prompt parameter on an Option will indicate to plugin - loaders that it is ok to prompt the user for input for this parameter if - none is provided initially. Actual implementation of this prompting - mechanism will be handled by the individual loaders such as - os-client-config. diff --git a/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml b/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml deleted file mode 100644 index b96b46d..0000000 --- a/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -features: - - > - [`blueprint totp-auth `_] - Add an auth plugin to handle Time-Based One-Time Password (TOTP) - authentication via the ``totp`` method. This new plugin will accept the - following identity options: - - - ``user-id``: user ID - - ``username``: username - - ``user-domain-id``: user's domain ID - - ``user-domain-name``: user's domain name - - ``passcode``: passcode generated by TOTP app or device - - User is uniquely identified by either ``user-id`` or combination of - ``username`` and ``user-domain-id`` or ``user-domain-name``. diff --git a/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml b/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml deleted file mode 100644 index f18fa31..0000000 --- a/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -prelude: > - Allow specifying additional_headers to the session and the adapter to add - headers to all requests that pass through these objects. -features: - - Add the ability to provide additional_headers to the session and adapter - object. This will allow clients particularly to provide additional ways to - identify their requests. It will also hopefully provide an intermediate way - to handle setting microversions until we support them directly with - keystoneauth. diff --git a/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml b/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml deleted file mode 100644 index 6a5f73d..0000000 --- a/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - A new flag `allow_version_hack` was added to identity plugins and the - adapter which will allow a client to opt out of making guesses at the - version url page of a service. This means that if a deployment is - misconfigured and the service catalog contains a versioned endpoint that - does not match the requested version the request will fail. This will be - useful in beginning to require correctly deployed catalogs rather than - continue to hide the problem. diff --git a/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml b/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml deleted file mode 100644 index d58ca63..0000000 --- a/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -fixes: - - Fix passing scope parameters in Oidc* auth plugins. - [Bug `1582774 `_] diff --git a/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml b/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml deleted file mode 100644 index 2883c69..0000000 --- a/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -prelude: > - HTTP connections work under Windows Subsystem for Linux -fixes: - - > - [`bug 1614688 `_] - HTTP connections were failing under Windows subsystem for Linux because - TCP_KEEPCNT was being set and that environment does not support such - override yet. diff --git a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml deleted file mode 100644 index e9c1c9c..0000000 --- a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -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. diff --git a/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml b/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml deleted file mode 100644 index 5d066e9..0000000 --- a/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -fixes: - - | - The ``X-Service-Token`` header value is now properly masked, and is - displayed as a hash value, in the log. diff --git a/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml b/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml deleted file mode 100644 index 1df886b..0000000 --- a/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -prelude: > - Allow setting EndpointReference in ADFSPassword -features: - - > - Add the ability to specify the WS-Policy EndpointReference used in the - ADFSPassword plugin's RequestSecurityToken message via the - 'service-provider-entity-id' option. Also added 'identity-provider-url' - option which was required, but missing from option list. -fixes: - - > - [`bug 1689424 `_] - Allow setting EndpointReference in ADFSPassword. diff --git a/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml b/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml deleted file mode 100644 index 36d6b43..0000000 --- a/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -fixes: - - > - [`bug 1527131 `_] - Do not provide socket values for OSX and Windows. -other: - - Added a betamax fixture for keystoneauth sessions. - - Added a RFC 7231 compliant user agent string. diff --git a/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml b/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml deleted file mode 100644 index 3f841f3..0000000 --- a/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - Added support for specifying a microversion to use on - a given REST request. The microversion can be specified - on session request calls and a default can be set on - Adapter construction. diff --git a/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml b/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml deleted file mode 100644 index 2c4bc54..0000000 --- a/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -features: - - Added support for the API Working Group recommendations - on service and version discovery. New methods on - Session and Adapter, "get_endpoint_data" will return - endpoint metadata including microversion information. - Additionally, versions can be requested with a range - and with the string "latest", and interface values - can be given as a list in case a user wants to express - a 'best available' set of preferences. diff --git a/releasenotes/notes/user-agent-generation-b069100508c06177.yaml b/releasenotes/notes/user-agent-generation-b069100508c06177.yaml deleted file mode 100644 index 7e1a443..0000000 --- a/releasenotes/notes/user-agent-generation-b069100508c06177.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -prelude: > - Allow adding client and application name and version to the session and - adapter that will generate a userful user agent string. -features: - - You can specify a ``app_name`` and ``app_version`` when creating a - session. This information will be encoded into the user agent. - - You can specify a ``client_name`` and ``client_version`` when creating an - adapter. This will be handled by client libraries and incluced into the user - agent. - - Libraries like shade that modify the way requests are made can add - themselves to additional_user_agent and have their version reflected in the - user agent string. -deprecations: - - We suggest you fill the name and version for the application and client - instead of specifying a custom ``user_agent``. This will then generate a - standard user agent string. diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py deleted file mode 100644 index 77cd260..0000000 --- a/releasenotes/source/conf.py +++ /dev/null @@ -1,286 +0,0 @@ -# -*- 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. - -# keystoneauth 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. -project = u'keystoneauth Release Notes' -copyright = u'2015, Keystone Developers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -import pbr.version -keystone_version = pbr.version.VersionInfo('keystoneauth') -# The full version, including alpha/beta/rc tags. -release = keystone_version.version_string_with_vcs() -# The short X.Y version. -version = keystone_version.canonical_version_string() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'openstackdocs' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' -html_last_updated_fmt = '%Y-%m-%d %H:%M' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'keystoneauthReleaseNotesdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'keystoneauthReleaseNotes.tex', - u'keystoneauth Release Notes Documentation', - u'Keystone Developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'keystoneauthreleasenotes', - u'keystoneauth Release Notes Documentation', - [u'Keystone Developers'], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'keystoneauthReleaseNotes', - u'keystoneauth Release Notes Documentation', - u'Keystone Developers', 'keystoneauthReleaseNotes', - 'Authentication plugins 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 ------------------------------------------- -repository_name = 'openstack/keystoneauth' -bug_project = 'keystoneauth' -bug_tag = 'doc' diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst deleted file mode 100644 index ad95286..0000000 --- a/releasenotes/source/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -============================ - keystoneauth Release Notes -============================ - -.. toctree:: - :maxdepth: 1 - - unreleased - ocata - newton - mitaka diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst deleted file mode 100644 index e545609..0000000 --- a/releasenotes/source/mitaka.rst +++ /dev/null @@ -1,6 +0,0 @@ -=================================== - Mitaka Series Release Notes -=================================== - -.. release-notes:: - :branch: origin/stable/mitaka diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst deleted file mode 100644 index 7b7d735..0000000 --- a/releasenotes/source/newton.rst +++ /dev/null @@ -1,6 +0,0 @@ -============================= - Newton Series Release Notes -============================= - -.. release-notes:: - :branch: origin/stable/newton diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst deleted file mode 100644 index 9515f6c..0000000 --- a/releasenotes/source/ocata.rst +++ /dev/null @@ -1,6 +0,0 @@ -============================ - Ocata Series Release Notes -============================ - -.. release-notes:: - :branch: origin/stable/ocata diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst deleted file mode 100644 index cd22aab..0000000 --- a/releasenotes/source/unreleased.rst +++ /dev/null @@ -1,5 +0,0 @@ -============================== - Current Series Release Notes -============================== - -.. release-notes:: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2e3b9c5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -# All additions to this file must have significant justification. - -# NOTE(morgan) At no time may any olso library be added to the keystoneauth -# requirements. The requirements for keystoneauth are very tightly controlled -# to ensure we are not pulling in a ton of transient dependencies. This is -# important from the standpoint of ensuring keystoneauth can be used outside -# of openstack-specific projects (allowing interaction with openstack APIs) -# where oslo and associated transient dependencies are not desired. - -pbr!=2.1.0,>=2.0.0 # Apache-2.0 -iso8601>=0.1.11 # MIT -positional>=1.1.1 # Apache-2.0 -requests>=2.14.2 # Apache-2.0 -six>=1.9.0 # MIT -stevedore>=1.20.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 28c8978..0000000 --- a/setup.cfg +++ /dev/null @@ -1,74 +0,0 @@ -[metadata] -name = keystoneauth1 -summary = Authentication Library for OpenStack Identity -description-file = - README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = https://docs.openstack.org/keystoneauth/latest/ -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - -[files] -packages = - keystoneauth1 - -[extras] -kerberos = - requests-kerberos>=0.6 # ISC -saml2 = - lxml!=3.7.0,>=2.3 # BSD -oauth1 = - oauthlib>=0.6 # BSD -betamax = - betamax>=0.7.0 # Apache-2.0 - fixtures>=3.0.0 # Apache-2.0/BSD - mock>=2.0 # BSD - -[entry_points] - -keystoneauth1.plugin = - none = keystoneauth1.loading._plugins.noauth:NoAuth - password = keystoneauth1.loading._plugins.identity.generic:Password - token = keystoneauth1.loading._plugins.identity.generic:Token - admin_token = keystoneauth1.loading._plugins.admin_token:AdminToken - v2password = keystoneauth1.loading._plugins.identity.v2:Password - v2token = keystoneauth1.loading._plugins.identity.v2:Token - v3password = keystoneauth1.loading._plugins.identity.v3:Password - v3token = keystoneauth1.loading._plugins.identity.v3:Token - v3oidcclientcredentials = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectClientCredentials - v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword - v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode - v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken - v3oauth1 = keystoneauth1.extras.oauth1._loading:V3OAuth1 - v3kerberos = keystoneauth1.extras.kerberos._loading:Kerberos - v3totp = keystoneauth1.loading._plugins.identity.v3:TOTP - v3fedkerb = keystoneauth1.extras.kerberos._loading:MappedKerberos - v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth - v3adfspassword = keystoneauth1.extras._saml2._loading:ADFSPassword - v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 -warning-is-error = 1 - -[pbr] -autodoc_tree_index_modules = True -autodoc_tree_excludes = setup.py - -[upload_sphinx] -upload-dir = doc/build/html - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 566d844..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=2.0.0'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index e78b494..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 -flake8-docstrings==0.2.1.post1 # MIT - -bandit>=1.1.0 # Apache-2.0 -coverage!=4.4,>=4.0 # Apache-2.0 -fixtures>=3.0.0 # Apache-2.0/BSD -mock>=2.0 # BSD -oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 -openstackdocstheme>=1.11.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -os-testr>=0.8.0 # Apache-2.0 -betamax>=0.7.0 # Apache-2.0 -reno!=2.3.1,>=1.8.0 # Apache-2.0 -requests-mock>=1.1 # Apache-2.0 -sphinx>=1.6.2 # BSD -testrepository>=0.0.18 # Apache-2.0/BSD -testresources>=0.2.4 # Apache-2.0/BSD -testtools>=1.4.0 # MIT -PyYAML>=3.10.0 # MIT diff --git a/tools/tox_install.sh b/tools/tox_install.sh deleted file mode 100755 index e61b63a..0000000 --- a/tools/tox_install.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -# Client constraint file contains this client version pin that is in conflict -# with installing the client from source. We should remove the version pin in -# the constraints file before applying it for from-source installation. - -CONSTRAINTS_FILE="$1" -shift 1 - -set -e - -# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get -# published to logs.openstack.org for easy debugging. -localfile="$VIRTUAL_ENV/log/upper-constraints.txt" - -if [[ "$CONSTRAINTS_FILE" != http* ]]; then - CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" -fi -# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep -curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" - -pip install -c"$localfile" openstack-requirements - -# This is the main purpose of the script: Allow local installation of -# the current repo. It is listed in constraints file and thus any -# install will be constrained and we need to unconstrain it. -edit-constraints "$localfile" -- "$CLIENT_NAME" - -pip install -c"$localfile" -U "$@" -exit $? diff --git a/tox.ini b/tox.ini deleted file mode 100644 index b51de02..0000000 --- a/tox.ini +++ /dev/null @@ -1,65 +0,0 @@ -[tox] -minversion = 2.0 -skipsdist = True -envlist = py35,py27,pep8,releasenotes - -[testenv] -usedevelop = True -install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - BRANCH_NAME=master - CLIENT_NAME=keystoneauth1 - OS_STDOUT_NOCAPTURE=False - OS_STDERR_NOCAPTURE=False - -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - .[kerberos,saml2,betamax,oauth1] -commands = ostestr {posargs} -whitelist_externals = - bash - -[testenv:pep8] -commands = - flake8 - # Run security linter - # B110: except: pass - # B410: importing etree - bandit -r keystoneauth1 -x tests -s B110,B410 - -[testenv:bandit] -# NOTE(browne): This is required for the integration test job of the bandit -# project. Please do not remove. -commands = bandit -r keystoneauth1 -x tests -s B110,B410 - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' - -[testenv:debug] -commands = oslo_debug_helper -t keystoneauth1/tests {posargs} - -[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 -# D203: 1 blank line required before class docstring (deprecated in pep257) -ignore = D100,D101,D102,D103,D104,D203 -show-source = True -exclude = .venv,.tox,dist,doc,*egg,build - -[testenv:docs] -commands= - bash -c "rm -rf doc/build" - bash -c "rm -rf doc/source/api" - python setup.py build_sphinx - -[testenv:releasenotes] -commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html - -[hacking] -local-check-factory = keystoneauth1.hacking.checks.factory