././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/0000775000372000037200000000000000000000000016031 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/LICENSE0000664000372000037200000002613500000000000017045 0ustar00travistravis00000000000000 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. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/MANIFEST.in0000664000372000037200000000010600000000000017564 0ustar00travistravis00000000000000include requirements.txt include requirements-dev.txt include LICENSE ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/PKG-INFO0000664000372000037200000001036700000000000017135 0ustar00travistravis00000000000000Metadata-Version: 2.1 Name: ibm-cloud-sdk-core Version: 3.19.2 Summary: Core library used by SDKs for IBM Cloud Services Home-page: https://github.com/IBM/python-sdk-core Author: IBM Author-email: devxsdk@us.ibm.com License: Apache 2.0 Keywords: ibm,cloud,ibm cloud services Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Application Frameworks Description-Content-Type: text/markdown License-File: LICENSE [![Build Status](https://app.travis-ci.com/IBM/python-sdk-core.svg?branch=main)](https://app.travis-ci.com/IBM/python-sdk-core) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ibm-cloud-sdk-core)](https://pypi.org/project/ibm-cloud-sdk-core/) [![Latest Stable Version](https://img.shields.io/pypi/v/ibm-cloud-sdk-core.svg)](https://pypi.python.org/pypi/ibm-cloud-sdk-core) [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) # IBM Python SDK Core Version 3.19.2 This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator (openapi-sdkgen). # Python Version The current minimum Python version supported is 3.8. ## Installation To install, use `pip`: ```bash python -m pip install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token Authentication - Identity and Access Management (IAM) Authentication - Container Authentication - VPC Instance Authentication - Cloud Pak for Data Authentication - No Authentication (for testing) For more information about the various authentication types and how to use them with your services, click [here](Authentication.md). ## Issues If you encounter an issue with this project, you are welcome to submit a [bug report](https://github.com/IBM/python-sdk-core/issues). Before opening a new issue, please search for similar issues. It's possible that someone has already reported it. ## Logging ### Enable logging ```python import logging logging.basicConfig(level=logging.DEBUG) ``` This would show output of the form: ``` DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): iam.cloud.ibm.com:443 DEBUG:urllib3.connectionpool:https://iam.cloud.ibm.com:443 "POST /identity/token HTTP/1.1" 200 1809 DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "POST /assistant/api/v1/workspaces?version=2018-07-10 HTTP/1.1" 201 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "GET /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10&export=true HTTP/1.1" 200 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "DELETE /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10 HTTP/1.1" 200 28 ``` ### Low level request and response dump To get low level information of the requests/ responses: ```python from http.client import HTTPConnection HTTPConnection.debuglevel = 1 ``` ## Open source @ IBM Find more open source projects on the [IBM Github Page](http://github.com/IBM) ## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/README.md0000664000372000037200000000640000000000000017310 0ustar00travistravis00000000000000[![Build Status](https://app.travis-ci.com/IBM/python-sdk-core.svg?branch=main)](https://app.travis-ci.com/IBM/python-sdk-core) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ibm-cloud-sdk-core)](https://pypi.org/project/ibm-cloud-sdk-core/) [![Latest Stable Version](https://img.shields.io/pypi/v/ibm-cloud-sdk-core.svg)](https://pypi.python.org/pypi/ibm-cloud-sdk-core) [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) # IBM Python SDK Core Version 3.19.2 This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator (openapi-sdkgen). # Python Version The current minimum Python version supported is 3.8. ## Installation To install, use `pip`: ```bash python -m pip install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token Authentication - Identity and Access Management (IAM) Authentication - Container Authentication - VPC Instance Authentication - Cloud Pak for Data Authentication - No Authentication (for testing) For more information about the various authentication types and how to use them with your services, click [here](Authentication.md). ## Issues If you encounter an issue with this project, you are welcome to submit a [bug report](https://github.com/IBM/python-sdk-core/issues). Before opening a new issue, please search for similar issues. It's possible that someone has already reported it. ## Logging ### Enable logging ```python import logging logging.basicConfig(level=logging.DEBUG) ``` This would show output of the form: ``` DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): iam.cloud.ibm.com:443 DEBUG:urllib3.connectionpool:https://iam.cloud.ibm.com:443 "POST /identity/token HTTP/1.1" 200 1809 DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "POST /assistant/api/v1/workspaces?version=2018-07-10 HTTP/1.1" 201 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "GET /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10&export=true HTTP/1.1" 200 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "DELETE /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10 HTTP/1.1" 200 28 ``` ### Low level request and response dump To get low level information of the requests/ responses: ```python from http.client import HTTPConnection HTTPConnection.debuglevel = 1 ``` ## Open source @ IBM Find more open source projects on the [IBM Github Page](http://github.com/IBM) ## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1709138948.136725 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/0000775000372000037200000000000000000000000021637 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/__init__.py0000664000372000037200000000544500000000000023760 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM 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. """Classes and helper functions used by generated SDKs. classes: BaseService: Abstract class for common functionality between each service. DetailedResponse: The object returned from successful service operations. IAMTokenManager: Requests and refreshes IAM tokens using an apikey, and optionally a client_id and client_secret. JWTTokenManager: Abstract class for common functionality between each JWT token manager. CP4DTokenManager: Requests and refreshes CP4D tokens given a username and password. ApiException: Custom exception class for errors returned from service operations. functions: datetime_to_string: Serializes a datetime to a string. string_to_datetime: De-serializes a string to a datetime. datetime_to_string_list: Serializes a list of datetimes to a list of strings. string_to_datetime_list: De-serializes a list of strings to a list of datetimes. date_to_string: Serializes a date to a string. string_to_date: De-serializes a string to a date. convert_model: Convert a model object into an equivalent dict. convert_list: Convert a list of strings into comma-separated string. get_query_param: Return a query parameter value from a URL read_external_sources: Get config object from external sources. get_authenticator_from_environment: Get authenticator from external sources. """ from .base_service import BaseService from .detailed_response import DetailedResponse from .token_managers.iam_token_manager import IAMTokenManager from .token_managers.jwt_token_manager import JWTTokenManager from .token_managers.cp4d_token_manager import CP4DTokenManager from .token_managers.container_token_manager import ContainerTokenManager from .token_managers.vpc_instance_token_manager import VPCInstanceTokenManager from .token_managers.mcsp_token_manager import MCSPTokenManager from .api_exception import ApiException from .utils import datetime_to_string, string_to_datetime, read_external_sources from .utils import datetime_to_string_list, string_to_datetime_list from .utils import date_to_string, string_to_date from .utils import convert_model, convert_list from .utils import get_query_param from .get_authenticator import get_authenticator_from_environment ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/api_exception.py0000664000372000037200000000710500000000000025043 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM 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 warnings from http import HTTPStatus from typing import Optional from requests import Response class ApiException(Exception): """Custom exception class for errors returned from operations. Args: code: HTTP status code of the error response. message: The error response body. Defaults to None. http_response: The HTTP response of the failed request. Defaults to None. Attributes: code (int): HTTP status code of the error response. message (str): The error response body. http_response (requests.Response): The HTTP response of the failed request. global_transaction_id (str, optional): Globally unique id the service endpoint has given a transaction. """ def __init__(self, code: int, *, message: Optional[str] = None, http_response: Optional[Response] = None) -> None: # Call the base class constructor with the parameters it needs super().__init__(message) self.message = message self.status_code = code self.http_response = http_response self.global_transaction_id = None if http_response is not None: self.global_transaction_id = http_response.headers.get('X-Global-Transaction-ID') self.message = self.message if self.message else self._get_error_message(http_response) # pylint: disable=fixme # TODO: delete this by the end of 2024. @property def code(self): """The old `code` property with a deprecation warning.""" warnings.warn( 'Using the `code` attribute on the `ApiException` is deprecated and' 'will be removed in the future. Use `status_code` instead.', DeprecationWarning, ) return self.status_code def __str__(self) -> str: msg = 'Error: ' + str(self.message) + ', Status code: ' + str(self.status_code) if self.global_transaction_id is not None: msg += ' , X-global-transaction-id: ' + str(self.global_transaction_id) return msg @staticmethod def _get_error_message(response: Response) -> str: error_message = 'Unknown error' try: error_json = response.json(strict=False) if 'errors' in error_json: if isinstance(error_json['errors'], list): err = error_json['errors'][0] error_message = err.get('message') elif 'error' in error_json: error_message = error_json['error'] elif 'message' in error_json: error_message = error_json['message'] elif 'errorMessage' in error_json: error_message = error_json['errorMessage'] elif response.status_code == 401: error_message = 'Unauthorized: Access is denied due to invalid credentials' else: error_message = HTTPStatus(response.status_code).phrase return error_message except: return response.text or error_message ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1709138948.136725 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/0000775000372000037200000000000000000000000024674 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/__init__.py0000664000372000037200000000412500000000000027007 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """The ibm_cloud_sdk_core project supports the following types of authentication: Basic Authentication Bearer Token Identity and Access Management (IAM) Cloud Pak for Data No Authentication The authentication types that are appropriate for a particular service may vary from service to service. Each authentication type is implemented as an Authenticator for consumption by a service. classes: Authenticator: Abstract Base Class. Implement this interface to provide custom authentication schemes to services. BasicAuthenticator: Authenticator for passing supplied basic authentication information to service endpoint. BearerTokenAuthenticator: Authenticator for passing supplied bearer token to service endpoint. CloudPakForDataAuthenticator: Authenticator for passing CP4D authentication information to service endpoint. IAMAuthenticator: Authenticator for passing IAM authentication information to service endpoint. NoAuthAuthenticator: Performs no authentication. Useful for testing purposes. """ from .authenticator import Authenticator from .basic_authenticator import BasicAuthenticator from .bearer_token_authenticator import BearerTokenAuthenticator from .container_authenticator import ContainerAuthenticator from .cp4d_authenticator import CloudPakForDataAuthenticator from .iam_authenticator import IAMAuthenticator from .vpc_instance_authenticator import VPCInstanceAuthenticator from .no_auth_authenticator import NoAuthAuthenticator from .mcsp_authenticator import MCSPAuthenticator ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/authenticator.py0000664000372000037200000000367300000000000030131 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019, 2023 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from abc import ABC, abstractmethod class Authenticator(ABC): """This interface defines the common methods and constants associated with an Authenticator implementation.""" # Constants representing the various authenticator types. AUTHTYPE_BASIC = 'basic' AUTHTYPE_BEARERTOKEN = 'bearerToken' AUTHTYPE_IAM = 'iam' AUTHTYPE_CONTAINER = 'container' AUTHTYPE_CP4D = 'cp4d' AUTHTYPE_VPC = 'vpc' AUTHTYPE_NOAUTH = 'noAuth' AUTHTYPE_MCSP = 'mcsp' AUTHTYPE_UNKNOWN = 'unknown' @abstractmethod def authenticate(self, req: dict) -> None: """Perform the necessary authentication steps for the specified request. Attributes: req (dict): Will be modified to contain the appropriate authentication information. To be implemented by subclasses. """ pass @abstractmethod def validate(self) -> None: """Validates the current set of configuration information in the Authenticator. Raises: ValueError: The configuration information is not valid for service operations. To be implemented by subclasses. """ pass def authentication_type(self) -> str: """Returns the authenticator's type. This method should be overridden by each authenticator implementation.""" return self.AUTHTYPE_UNKNOWN ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/basic_authenticator.py0000664000372000037200000000607100000000000031265 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 from requests import Request from .authenticator import Authenticator from ..utils import has_bad_first_or_last_char class BasicAuthenticator(Authenticator): """The BasicAuthenticator is used to add basic authentication information to requests. Basic Authorization will be sent as an Authorization header in the form: Authorization: Basic Args: username: User-supplied username for basic auth. password: User-supplied password for basic auth. Raises: ValueError: The username or password is not specified or contains invalid characters. """ def __init__(self, username: str, password: str) -> None: self.username = username self.password = password self.validate() self.authorization_header = self.__construct_basic_auth_header() def authentication_type(self) -> str: """Returns this authenticator's type ('basic').""" return Authenticator.AUTHTYPE_BASIC def validate(self) -> None: """Validate username and password. Ensure the username and password are valid for service operations. Raises: ValueError: The username and/or password is not valid for service operations. """ if self.username is None or self.password is None: raise ValueError('The username and password shouldn\'t be None.') if has_bad_first_or_last_char(self.username) or has_bad_first_or_last_char(self.password): raise ValueError( 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) def __construct_basic_auth_header(self) -> str: authstring = "{0}:{1}".format(self.username, self.password) base64_authorization = base64.b64encode(authstring.encode('utf-8')).decode('utf-8') return 'Basic {0}'.format(base64_authorization) def authenticate(self, req: Request) -> None: """Add basic authentication information to a request. Basic Authorization will be added to the request's headers in the form: Authorization: Basic Args: req: The request to add basic auth information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') headers['Authorization'] = self.authorization_header ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py0000664000372000037200000000507700000000000032651 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from requests import Request from .authenticator import Authenticator class BearerTokenAuthenticator(Authenticator): """The BearerTokenAuthenticator will add a user-supplied bearer token to requests. The bearer token will be sent as an Authorization header in the form: Authorization: Bearer Args: bearer_token: The user supplied bearer token. Raises: ValueError: Bearer token is none. """ def __init__(self, bearer_token: str) -> None: self.bearer_token = bearer_token self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('bearertoken').""" return Authenticator.AUTHTYPE_BEARERTOKEN def validate(self) -> None: """Validate the bearer token. Ensures the bearer token is valid for service operations. Raises: ValueError: The bearer token is not valid for service operations. """ if self.bearer_token is None: raise ValueError('The bearer token shouldn\'t be None.') def authenticate(self, req: Request) -> None: """Adds bearer authentication information to the request. The bearer token will be added to the request's headers in the form: Authorization: Bearer Args: req: The request to add bearer authentication information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') headers['Authorization'] = 'Bearer {0}'.format(self.bearer_token) def set_bearer_token(self, bearer_token: str) -> None: """Set a new bearer token to be sent in subsequent service operations. Args: bearer_token: The bearer token that will be sent in service requests. Raises: ValueError: The bearer token is not valid for service operations. """ self.bearer_token = bearer_token self.validate() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/container_authenticator.py0000664000372000037200000001560200000000000032166 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from .iam_request_based_authenticator import IAMRequestBasedAuthenticator from ..token_managers.container_token_manager import ContainerTokenManager from .authenticator import Authenticator class ContainerAuthenticator(IAMRequestBasedAuthenticator): """ContainerAuthenticator implements an IAM-based authentication schema where by it retrieves a "compute resource token" from the local compute resource (VM) and uses that to obtain an IAM access token by invoking the IAM "get token" operation with grant-type=cr-token. The resulting IAM access token is then added to outbound requests in an Authorization header of the form: Authorization: Bearer Args: cr_token_filename: The name of the file containing the injected CR token value (applies to IKS-managed compute resources). Defaults to "/var/run/secrets/tokens/vault-token". iam_profile_name: The name of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_profile_id must be specified. Defaults to None. iam_profile_id: The id of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_profile_id must be specified. Defaults to None. url: The URL representing the IAM token service endpoint. If not specified, a suitable default value is used. client_id: The client_id and client_secret fields are used to form a "basic" authorization header for IAM token requests. Defaults to None. client_secret: The client_id and client_secret fields are used to form a "basic" authorization header for IAM token requests. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every IAM token request. Defaults to None. proxies: Dictionary for mapping request protocol to proxy URL. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. scope: The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Attributes: token_manager (ContainerTokenManager): Retrieves and manages IAM tokens from the endpoint specified by the url. Raises: TypeError: The `disable_ssl_verification` is not a bool. ValueError: Neither of iam_profile_name or iam_profile_idk are set, or client_id, and/or client_secret are not valid for IAM token requests. """ def __init__( self, cr_token_filename: Optional[str] = None, iam_profile_name: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, disable_ssl_verification: bool = False, scope: Optional[str] = None, proxies: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None, ) -> None: # Check the type of `disable_ssl_verification`. Must be a bool. if not isinstance(disable_ssl_verification, bool): raise TypeError('disable_ssl_verification must be a bool') self.token_manager = ContainerTokenManager( cr_token_filename=cr_token_filename, iam_profile_name=iam_profile_name, iam_profile_id=iam_profile_id, url=url, client_id=client_id, client_secret=client_secret, disable_ssl_verification=disable_ssl_verification, scope=scope, proxies=proxies, headers=headers, ) self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('container').""" return Authenticator.AUTHTYPE_CONTAINER def validate(self) -> None: """Validates the iam_profile_name, iam_profile_id, client_id, and client_secret for IAM token requests. Ensure that one of the iam_profile_name or iam_profile_id are specified. Additionally, ensure both of the client_id and client_secret are set if either of them are defined. Raises: ValueError: Neither of iam_profile_name or iam_profile_idk are set, or client_id, and/or client_secret are not valid for IAM token requests. """ super().validate() if not self.token_manager.iam_profile_name and not self.token_manager.iam_profile_id: raise ValueError('At least one of iam_profile_name or iam_profile_id must be specified.') def set_cr_token_filename(self, cr_token_filename: str) -> None: """Set the location of the compute resource token on the local filesystem. Args: cr_token_filename: path to the compute resource token """ self.token_manager.cr_token_filename = cr_token_filename def set_iam_profile_name(self, iam_profile_name: str) -> None: """Set the name of the IAM profile. Args: iam_profile_name: name of the linked trusted IAM profile to be used when obtaining the IAM access token Raises: ValueError: Neither of iam_profile_name or iam_profile_idk are set, or client_id, and/or client_secret are not valid for IAM token requests. """ self.token_manager.iam_profile_name = iam_profile_name self.validate() def set_iam_profile_id(self, iam_profile_id: str) -> None: """Set the id of the IAM profile. Args: iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token Raises: ValueError: Neither of iam_profile_name or iam_profile_idk are set, or client_id, and/or client_secret are not valid for IAM token requests. """ self.token_manager.iam_profile_id = iam_profile_id self.validate() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py0000664000372000037200000001513700000000000031041 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from requests import Request from .authenticator import Authenticator from ..token_managers.cp4d_token_manager import CP4DTokenManager from ..utils import has_bad_first_or_last_char class CloudPakForDataAuthenticator(Authenticator): """The CloudPakForDataAuthenticator utilizes a username and password pair to obtain a suitable bearer token, and adds it requests. The bearer token will be sent as an Authorization header in the form: Authorization: Bearer Keyword Args: username: The username used to obtain a bearer token [required]. password: The password used to obtain a bearer token [required if apikey not specified]. url: The URL representing the Cloud Pak for Data token service endpoint [required]. apikey: The API key used to obtain a bearer token [required if password not specified]. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every CP4D token request. Defaults to None. proxies: Dictionary for mapping request protocol to proxy URL. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. verify (optional): The path to the certificate to use for HTTPS requests. Attributes: token_manager (CP4DTokenManager): Retrieves and manages CP4D tokens from the endpoint specified by the url. Raises: TypeError: The `disable_ssl_verification` is not a bool. ValueError: The username, password/apikey, and/or url are not valid for CP4D token requests. """ def __init__( self, username: str = None, password: str = None, url: str = None, *, apikey: str = None, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, verify: Optional[str] = None ) -> None: # Check the type of `disable_ssl_verification`. Must be a bool. if not isinstance(disable_ssl_verification, bool): raise TypeError('disable_ssl_verification must be a bool') self.token_manager = CP4DTokenManager( username=username, password=password, apikey=apikey, url=url, disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, verify=verify, ) self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('cp4d').""" return Authenticator.AUTHTYPE_CP4D def validate(self) -> None: """Validate username, password, and url for token requests. Ensures the username, password, and url are not None. Additionally, ensures they do not contain invalid characters. Raises: ValueError: The username, password, and/or url are not valid for token requests. """ if self.token_manager.username is None: raise ValueError('The username shouldn\'t be None.') if (self.token_manager.password is None and self.token_manager.apikey is None) or ( self.token_manager.password is not None and self.token_manager.apikey is not None ): raise ValueError('Exactly one of `apikey` or `password` must be specified.') if self.token_manager.url is None: raise ValueError('The url shouldn\'t be None.') if has_bad_first_or_last_char(self.token_manager.username) or has_bad_first_or_last_char( self.token_manager.password ): raise ValueError( 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) if has_bad_first_or_last_char(self.token_manager.url): raise ValueError( 'The url shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) def authenticate(self, req: Request) -> None: """Adds CP4D authentication information to the request. The CP4D bearer token will be added to the request's headers in the form: Authorization: Bearer Args: req: The request to add CP4D authentication information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') bearer_token = self.token_manager.get_token() headers['Authorization'] = 'Bearer {0}'.format(bearer_token) def set_disable_ssl_verification(self, status: bool = False) -> None: """Set the flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. Args: status: Set to true in order to disable SSL certificate verification. Defaults to False. Raises: TypeError: The `status` is not a bool. """ self.token_manager.set_disable_ssl_verification(status) def set_headers(self, headers: Dict[str, str]) -> None: """Default headers to be sent with every CP4D token request. Args: headers: The headers to be sent with every CP4D token request. """ self.token_manager.set_headers(headers) def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with CP4D on behalf of the host. Args: proxies: Dictionary for mapping request protocol to proxy URL. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ self.token_manager.set_proxies(proxies) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/iam_authenticator.py0000664000372000037200000001076400000000000030756 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from .authenticator import Authenticator from .iam_request_based_authenticator import IAMRequestBasedAuthenticator from ..token_managers.iam_token_manager import IAMTokenManager from ..utils import has_bad_first_or_last_char class IAMAuthenticator(IAMRequestBasedAuthenticator): """The IAMAuthenticator utilizes an apikey, or client_id and client_secret pair to obtain a suitable bearer token, and adds it to requests. The bearer token will be sent as an Authorization header in the form: Authorization: Bearer Args: apikey: The IAM api key. Keyword Args: url: The URL representing the IAM token service endpoint. If not specified, a suitable default value is used. client_id: The client_id and client_secret fields are used to form a "basic" authorization header for IAM token requests. Defaults to None. client_secret: The client_id and client_secret fields are used to form a "basic" authorization header for IAM token requests. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every IAM token request. Defaults to None. proxies: Dictionary for mapping request protocol to proxy URL. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. scope: The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Attributes: token_manager (IAMTokenManager): Retrieves and manages IAM tokens from the endpoint specified by the url. Raises: TypeError: The `disable_ssl_verification` is not a bool. ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests. """ def __init__( self, apikey: str, *, url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, scope: Optional[str] = None ) -> None: # Check the type of `disable_ssl_verification`. Must be a bool. if not isinstance(disable_ssl_verification, bool): raise TypeError('disable_ssl_verification must be a bool') self.token_manager = IAMTokenManager( apikey, url=url, client_id=client_id, client_secret=client_secret, disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope, ) self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('iam').""" return Authenticator.AUTHTYPE_IAM def validate(self) -> None: """Validates the apikey, client_id, and client_secret for IAM token requests. Ensure the apikey is not none, and has no bad characters. Additionally, ensure the both the client_id and client_secret are both set if either of them are defined. Raises: ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests. """ super().validate() if self.token_manager.apikey is None: raise ValueError('The apikey shouldn\'t be None.') if has_bad_first_or_last_char(self.token_manager.apikey): raise ValueError( 'The apikey shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py0000664000372000037200000001064300000000000033660 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict from requests import Request from .authenticator import Authenticator class IAMRequestBasedAuthenticator(Authenticator): """The IAMRequestBasedAuthenticator class contains code that is common to all authenticators that need to interact with the IAM tokens service to obtain an access token. The bearer token will be sent as an Authorization header in the form: Authorization: Bearer Attributes: token_manager (TokenManager): Retrieves and manages IAM tokens from the endpoint specified by the url. """ def validate(self) -> None: """Validates the client_id, and client_secret for IAM token requests. Ensure both the client_id and client_secret are set if either of them are defined. Raises: ValueError: The client_id, and/or client_secret are not valid for IAM token requests. """ if (self.token_manager.client_id and not self.token_manager.client_secret) or ( not self.token_manager.client_id and self.token_manager.client_secret ): raise ValueError('Both client_id and client_secret should be initialized.') def authenticate(self, req: Request) -> None: """Adds IAM authentication information to the request. The IAM bearer token will be added to the request's headers in the form: Authorization: Bearer Args: req: The request to add IAM authentication information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') bearer_token = self.token_manager.get_token() headers['Authorization'] = 'Bearer {0}'.format(bearer_token) def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None: """Set the client_id and client_secret pair the token manager will use for IAM token requests. Args: client_id: The client id to be used in basic auth. client_secret: The client secret to be used in basic auth. Raises: ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests. """ self.token_manager.set_client_id_and_secret(client_id, client_secret) self.validate() def set_disable_ssl_verification(self, status: bool = False) -> None: """Set the flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. Args: status: Headers to be sent with every IAM token request. Defaults to None Raises: TypeError: The `status` is not a bool. """ self.token_manager.set_disable_ssl_verification(status) def set_headers(self, headers: Dict[str, str]) -> None: """Headers to be sent with every IAM token request. Args: headers: Headers to be sent with every IAM token request. """ self.token_manager.set_headers(headers) def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with IAM on behalf of the host. Args: proxies: Dictionary for mapping request protocol to proxy URL. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ self.token_manager.set_proxies(proxies) def set_scope(self, value: str) -> None: """Sets the "scope" parameter to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Args: value: A space seperated string that makes up the scope parameter. """ self.token_manager.set_scope(value) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py0000664000372000037200000001207300000000000031145 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2023 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from requests import Request from .authenticator import Authenticator from ..token_managers.mcsp_token_manager import MCSPTokenManager class MCSPAuthenticator(Authenticator): """The MCSPAuthenticator uses an apikey to obtain an access token from the MCSP token server. When the access token expires, a new access token is obtained from the token server. The access token will be added to outbound requests via the Authorization header of the form: "Authorization: Bearer " Keyword Args: url: The base endpoint URL for the MCSP token service [required]. apikey: The API key used to obtain an access token [required]. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every MCSP token request. Defaults to None. proxies: Dictionary for mapping request protocol to proxy URL. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. Attributes: token_manager (MCSPTokenManager): Retrieves and manages MCSP tokens from the endpoint specified by the url. Raises: TypeError: The `disable_ssl_verification` is not a bool. ValueError: The apikey and/or url are not valid for MCSP token exchange requests. """ def __init__( self, apikey: str, url: str, *, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, ) -> None: # Check the type of `disable_ssl_verification`. Must be a bool. if not isinstance(disable_ssl_verification, bool): raise TypeError('disable_ssl_verification must be a bool') self.token_manager = MCSPTokenManager( apikey=apikey, url=url, disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, ) self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('mcsp').""" return Authenticator.AUTHTYPE_MCSP def validate(self) -> None: """Validate apikey and url for token requests. Raises: ValueError: The apikey and/or url are not valid for token requests. """ if self.token_manager.apikey is None: raise ValueError('The apikey shouldn\'t be None.') if self.token_manager.url is None: raise ValueError('The url shouldn\'t be None.') def authenticate(self, req: Request) -> None: """Adds MCSP authentication information to the request. The MCSP bearer token will be added to the request's headers in the form: Authorization: Bearer Args: req: The request to add MCSP authentication information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') bearer_token = self.token_manager.get_token() headers['Authorization'] = 'Bearer {0}'.format(bearer_token) def set_disable_ssl_verification(self, status: bool = False) -> None: """Set the flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. Args: status: Set to true in order to disable SSL certificate verification. Defaults to False. Raises: TypeError: The `status` is not a bool. """ self.token_manager.set_disable_ssl_verification(status) def set_headers(self, headers: Dict[str, str]) -> None: """Default headers to be sent with every MCSP token request. Args: headers: The headers to be sent with every MCSP token request. """ self.token_manager.set_headers(headers) def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with MCSP on behalf of the host. Args: proxies: Dictionary for mapping request protocol to proxy URL. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ self.token_manager.set_proxies(proxies) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/no_auth_authenticator.py0000664000372000037200000000172300000000000031640 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from .authenticator import Authenticator class NoAuthAuthenticator(Authenticator): """Performs no authentication.""" def authentication_type(self) -> str: """Returns this authenticator's type ('noauth').""" return Authenticator.AUTHTYPE_NOAUTH def validate(self) -> None: pass def authenticate(self, req) -> None: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py0000664000372000037200000001217300000000000032660 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Optional from requests import Request from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager from .authenticator import Authenticator class VPCInstanceAuthenticator(Authenticator): """VPCInstanceAuthenticator implements an authentication scheme in which it retrieves an "instance identity token" and exchanges that for an IAM access token using the VPC Instance Metadata Service API which is available on the local compute resource (VM). The instance identity token is similar to an IAM apikey, except that it is managed automatically by the compute resource provider (VPC). The resulting IAM access token is then added to outbound requests in an Authorization header of the form: Authorization: Bearer Keyword Arguments: iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile to be used as the identity of the compute resource. At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. Defaults to None. iam_profile_id (str, optional): The ID of the linked trusted IAM profile to be used when obtaining the IAM access token. At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. Defaults to None. url (str, optional): The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'. Attributes: iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile. iam_profile_id (str, optional): The ID of the linked trusted IAM profile. url (str, optional): The VPC Instance Metadata Service's base endpoint URL. """ DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254' def __init__( self, iam_profile_crn: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None ) -> None: if not url: url = self.DEFAULT_IMS_ENDPOINT self.token_manager = VPCInstanceTokenManager( url=url, iam_profile_crn=iam_profile_crn, iam_profile_id=iam_profile_id ) self.validate() def authentication_type(self) -> str: """Returns this authenticator's type ('VPC').""" return Authenticator.AUTHTYPE_VPC def validate(self) -> None: super().validate() if self.token_manager.iam_profile_crn and self.token_manager.iam_profile_id: raise ValueError('At most one of "iam_profile_id" or "iam_profile_crn" may be specified.') def authenticate(self, req: Request) -> None: """Adds IAM authentication information to the request. The IAM access token will be added to the request's headers in the form: Authorization: Bearer Args: req: The request to add IAM authentication information to. Must contain a key to a dictionary called headers. """ headers = req.get('headers') bearer_token = self.token_manager.get_token() headers['Authorization'] = 'Bearer {0}'.format(bearer_token) def set_iam_profile_crn(self, iam_profile_crn: str) -> None: """Sets CRN of the IAM profile. Args: iam_profile_crn (str): the CRN of the linked trusted IAM profile to be used as the identity of the compute resource. Raises: ValueError: At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. """ self.token_manager.set_iam_profile_crn(iam_profile_crn) self.validate() def set_iam_profile_id(self, iam_profile_id: str) -> None: """Sets the ID of the IAM profile. Args: iam_profile_id (str): id of the linked trusted IAM profile to be used when obtaining the IAM access token Raises: ValueError: At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. """ self.token_manager.set_iam_profile_id(iam_profile_id) self.validate() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/base_service.py0000664000372000037200000005130400000000000024646 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM 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 gzip import io import json as json_import import logging import platform from http.cookiejar import CookieJar from os.path import basename from typing import Dict, List, Optional, Tuple, Union from urllib3.util.retry import Retry import requests from requests.structures import CaseInsensitiveDict from requests.exceptions import JSONDecodeError from ibm_cloud_sdk_core.authenticators import Authenticator from .api_exception import ApiException from .detailed_response import DetailedResponse from .token_managers.token_manager import TokenManager from .utils import ( has_bad_first_or_last_char, is_json_mimetype, remove_null_values, cleanup_values, read_external_sources, strip_extra_slashes, SSLHTTPAdapter, GzipStream, ) from .version import __version__ # Uncomment this to enable http debugging # import http.client as http_client # http_client.HTTPConnection.debuglevel = 1 logger = logging.getLogger(__name__) # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-locals class BaseService: """Common functionality shared by generated service classes. The base service authenticates requests via its authenticator, stores cookies, and wraps responses from the service endpoint in DetailedResponse or APIException objects. Keyword Arguments: service_url: Url to the service endpoint. Defaults to None. authenticator: Adds authentication data to service requests. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. enable_gzip_compression: A flag that indicates whether to enable gzip compression on request bodies Attributes: service_url (str): Url to the service endpoint. authenticator (Authenticator): Adds authentication data to service requests. disable_ssl_verification (bool): A flag that indicates whether verification of the server's SSL certificate should be disabled or not. default_headers (dict): A dictionary of headers to be sent with every HTTP request to the service endpoint. jar (http.cookiejar.CookieJar): Stores cookies received from the service. http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests. http_client (Session): A configurable session which can use Transport Adapters to configure retries, timeouts, proxies, etc. globally for all requests. enable_gzip_compression (bool): A flag that indicates whether to enable gzip compression on request bodies Raises: ValueError: If Authenticator is not provided or invalid type. """ SDK_NAME = 'ibm-python-sdk-core' ERROR_MSG_DISABLE_SSL = ( 'The connection failed because the SSL certificate is not valid. To use a self-signed ' 'certificate, disable verification of the server\'s SSL certificate by invoking the ' 'set_disable_ssl_verification(True) on your service instance and/ or use the ' 'disable_ssl_verification option of the authenticator.' ) def __init__( self, *, service_url: str = None, authenticator: Authenticator = None, disable_ssl_verification: bool = False, enable_gzip_compression: bool = False ) -> None: self.set_service_url(service_url) self.http_client = requests.Session() self.http_config = {} self.jar = CookieJar() self.authenticator = authenticator self.disable_ssl_verification = disable_ssl_verification self.default_headers = None self.enable_gzip_compression = enable_gzip_compression self._set_user_agent_header(self._build_user_agent()) self.retry_config = None self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification) if not self.authenticator: raise ValueError('authenticator must be provided') if not isinstance(self.authenticator, Authenticator): raise ValueError('authenticator should be of type Authenticator') self.http_client.mount('http://', self.http_adapter) self.http_client.mount('https://', self.http_adapter) def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) -> None: """Enable automatic retries on the underlying http client used by the BaseService instance. Args: max_retries: the maximum number of retries to attempt for a failed retryable request retry_interval: the maximum wait time (in seconds) to use for retry attempts. In general, if a response includes the Retry-After header, that will be used for the wait time associated with the retry attempt. If the Retry-After header is not present, then the wait time is based on an exponential backoff policy with a maximum backoff time of "retry_interval". """ self.retry_config = Retry( total=max_retries, backoff_factor=1.0, backoff_max=retry_interval, # List of HTTP status codes to retry on in addition to Timeout/Connection Errors status_forcelist=[429, 500, 502, 503, 504], # List of HTTP methods to retry on # Omitting this will default to all methods except POST allowed_methods=['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'], ) self.http_adapter = SSLHTTPAdapter( max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification ) self.http_client.mount('http://', self.http_adapter) self.http_client.mount('https://', self.http_adapter) def disable_retries(self): """Remove retry config from http_adapter""" self.retry_config = None self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification) self.http_client.mount('http://', self.http_adapter) self.http_client.mount('https://', self.http_adapter) @staticmethod def _get_system_info() -> str: return '{0} {1} {2}'.format( platform.system(), platform.release(), platform.python_version() # OS # OS version # Python version ) def _build_user_agent(self) -> str: return '{0}-{1} {2}'.format(self.SDK_NAME, __version__, self._get_system_info()) def configure_service(self, service_name: str) -> None: """Look for external configuration of a service. Set service properties. Try to get config from external sources, with the following priority: 1. Credentials file(ibm-credentials.env) 2. Environment variables 3. VCAP Services(Cloud Foundry) Args: service_name: The service name Raises: ValueError: If service_name is not a string. """ if not isinstance(service_name, str): raise ValueError('Service_name must be of type string.') config = read_external_sources(service_name) if config.get('URL'): self.set_service_url(config.get('URL')) if config.get('DISABLE_SSL'): self.set_disable_ssl_verification(config.get('DISABLE_SSL').lower() == 'true') if config.get('ENABLE_GZIP'): self.set_enable_gzip_compression(config.get('ENABLE_GZIP').lower() == 'true') if config.get('ENABLE_RETRIES'): if config.get('ENABLE_RETRIES').lower() == 'true': kwargs = {} if config.get('MAX_RETRIES'): kwargs["max_retries"] = int(config.get('MAX_RETRIES')) if config.get('RETRY_INTERVAL'): kwargs["retry_interval"] = float(config.get('RETRY_INTERVAL')) self.enable_retries(**kwargs) def _set_user_agent_header(self, user_agent_string: str) -> None: self.user_agent_header = {'User-Agent': user_agent_string} def set_http_config(self, http_config: dict) -> None: """Sets the http config dictionary. The dictionary can contain values that control the timeout, proxies, and etc of HTTP requests. Arguments: http_config: Configuration values to customize HTTP behaviors. Raises: TypeError: http_config is not a dict. """ if isinstance(http_config, dict): self.http_config = http_config if ( self.authenticator and hasattr(self.authenticator, 'token_manager') and isinstance(self.authenticator.token_manager, TokenManager) ): self.authenticator.token_manager.http_config = http_config else: raise TypeError("http_config parameter must be a dictionary") def set_disable_ssl_verification(self, status: bool = False) -> None: """Set the flag that indicates whether verification of the server's SSL certificate should be disabled or not. Keyword Arguments: status: set to true to disable ssl verification (default: {False}) """ if self.disable_ssl_verification == status: # Do nothing if the state doesn't change. return self.disable_ssl_verification = status self.http_adapter = SSLHTTPAdapter( max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification ) self.http_client.mount('http://', self.http_adapter) self.http_client.mount('https://', self.http_adapter) def set_service_url(self, service_url: str) -> None: """Set the url the service will make HTTP requests too. Arguments: service_url: The WHATWG URL standard origin ex. https://example.service.com Raises: ValueError: Improperly formatted service_url """ if has_bad_first_or_last_char(service_url): raise ValueError( 'The service url shouldn\'t start or end with curly brackets or quotes. ' 'Be sure to remove any {} and \" characters surrounding your service url' ) if service_url is not None: service_url = service_url.rstrip('/') self.service_url = service_url def get_http_client(self) -> requests.sessions.Session: """Get the http client session currently used by the service. Returns: The http client session currently used by the service. """ return self.http_client def set_http_client(self, http_client: requests.sessions.Session) -> None: """Set current http client session Arguments: http_client: A new requests session client """ if isinstance(http_client, requests.sessions.Session): self.http_client = http_client else: raise TypeError("http_client parameter must be a requests.sessions.Session") def get_authenticator(self) -> Authenticator: """Get the authenticator currently used by the service. Returns: The authenticator currently used by the service. """ return self.authenticator def set_default_headers(self, headers: Dict[str, str]) -> None: """Set http headers to be sent in every request. Arguments: headers: A dictionary of headers """ if isinstance(headers, dict): self.default_headers = headers else: raise TypeError("headers parameter must be a dictionary") def send(self, request: requests.Request, **kwargs) -> DetailedResponse: """Send a request and wrap the response in a DetailedResponse or APIException. Args: request: The request to send to the service endpoint. Raises: ApiException: The exception from the API. Returns: The response from the request. """ # Use a one minute timeout when our caller doesn't give a timeout. # http://docs.python-requests.org/en/master/user/quickstart/#timeouts kwargs = dict({"timeout": 60}, **kwargs) kwargs = dict(kwargs, **self.http_config) if self.disable_ssl_verification: kwargs['verify'] = False # Check to see if the caller specified the 'stream' argument. stream_response = kwargs.get('stream') or False # Remove the keys we set manually, don't let the user to overwrite these. reserved_keys = ['method', 'url', 'headers', 'params', 'cookies'] silent_keys = ['headers'] for key in reserved_keys: if key in kwargs: del kwargs[key] if key not in silent_keys: logger.warning('"%s" has been removed from the request', key) try: response = self.http_client.request(**request, cookies=self.jar, **kwargs) # Process a "success" response. if 200 <= response.status_code <= 299: if response.status_code == 204 or request['method'] == 'HEAD': # There is no body content for a HEAD response or a 204 response. result = None elif stream_response: result = response elif not response.text: result = None elif is_json_mimetype(response.headers.get('Content-Type')): # If this is a JSON response, then try to unmarshal it. try: result = response.json(strict=False) except JSONDecodeError as err: raise ApiException( code=response.status_code, http_response=response, message='Error processing the HTTP response', ) from err else: # Non-JSON response, just use response body as-is. result = response return DetailedResponse(response=result, headers=response.headers, status_code=response.status_code) # Received error status code from server, raise an APIException. raise ApiException(response.status_code, http_response=response) except requests.exceptions.SSLError: logger.exception(self.ERROR_MSG_DISABLE_SSL) raise def set_enable_gzip_compression(self, should_enable_compression: bool = False) -> None: """Set value to enable gzip compression on request bodies""" self.enable_gzip_compression = should_enable_compression def get_enable_gzip_compression(self) -> bool: """Get value for enabling gzip compression on request bodies""" return self.enable_gzip_compression def prepare_request( self, method: str, url: str, *, headers: Optional[dict] = None, params: Optional[dict] = None, data: Optional[Union[str, dict]] = None, files: Optional[Union[Dict[str, Tuple[str]], List[Tuple[str, Tuple[str, ...]]]]] = None, **kwargs ) -> dict: """Build a dict that represents an HTTP service request. Clean up headers, add default http configuration, convert data into json, process files, and merge all into a single request dict. Args: method: The HTTP method of the request ex. GET, POST, etc. url: The origin + pathname according to WHATWG spec. Keyword Arguments: headers: Headers of the request. params: Querystring data to be appended to the url. data: The request body. Converted to json if a dict. files: 'files' can be a dictionary (i.e { '': ()}), or a list of tuples [ (, ())... ] Returns: Prepared request dictionary. """ # pylint: disable=unused-argument; necessary for kwargs request = {'method': method} # validate the service url is set if not self.service_url: raise ValueError('The service_url is required') # Combine the service_url and operation path to form the request url. # Note: we have already stripped any trailing slashes from the service_url # and we know that the operation path ('url') will start with a slash. request['url'] = strip_extra_slashes(self.service_url + url) headers = remove_null_values(headers) if headers else {} headers = cleanup_values(headers) headers = CaseInsensitiveDict(headers) if self.default_headers is not None: headers.update(self.default_headers) if 'user-agent' not in headers: headers.update(self.user_agent_header) request['headers'] = headers params = remove_null_values(params) params = cleanup_values(params) request['params'] = params if isinstance(data, str): data = data.encode('utf-8') elif isinstance(data, dict) and data: data = remove_null_values(data) if headers.get('content-type') is None: headers.update({'content-type': 'application/json'}) data = json_import.dumps(data).encode('utf-8') request['data'] = data self.authenticator.authenticate(request) # Compress the request body if applicable if self.get_enable_gzip_compression() and 'content-encoding' not in headers and request['data'] is not None: headers['content-encoding'] = 'gzip' request['headers'] = headers # If the provided data is a file-like object, we create `GzipStream` which will handle # the compression on-the-fly when the requests package starts reading its content. # This helps avoid OOM errors when the opened file is too big. # In any other cases, we use the in memory compression directly from # the `gzip` package for backward compatibility. raw_data = request['data'] request['data'] = GzipStream(raw_data) if isinstance(raw_data, io.IOBase) else gzip.compress(raw_data) # Next, we need to process the 'files' argument to try to fill in # any missing filenames where possible. # 'files' can be a dictionary (i.e { '': ()} ) # or a list of tuples [ (, ())... ] # If 'files' is a dictionary we'll convert it to a list of tuples. new_files = [] if files is not None: # If 'files' is a dictionary, transform it into a list of tuples. if isinstance(files, dict): files = remove_null_values(files) files = files.items() # Next, fill in any missing filenames from file tuples. for part_name, file_tuple in files: if file_tuple and len(file_tuple) == 3 and file_tuple[0] is None: file = file_tuple[1] if file and hasattr(file, 'name'): filename = basename(file.name) file_tuple = (filename, file_tuple[1], file_tuple[2]) new_files.append((part_name, file_tuple)) request['files'] = new_files return request @staticmethod def encode_path_vars(*args: str) -> List[str]: """Encode path variables to be substituted into a URL path. Arguments: args: A list of strings to be URL path encoded Returns: A list of encoded strings that are safe to substitute into a URL path. """ return (requests.utils.quote(x, safe='') for x in args) # The methods below are kept for compatibility and should be removed # in the next major release. # pylint: disable=protected-access @staticmethod def _convert_model(val: str) -> None: if isinstance(val, str): val = json_import.loads(val) if hasattr(val, "_to_dict"): return val._to_dict() return val @staticmethod def _convert_list(val: list) -> None: if isinstance(val, list): return ",".join(val) return val @staticmethod def _encode_path_vars(*args) -> None: return (requests.utils.quote(x, safe='') for x in args) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/detailed_response.py0000664000372000037200000000603400000000000025705 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM 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 json from typing import Dict, Optional, Union import requests class DetailedResponse: """Custom class for detailed response returned from APIs. Keyword Args: response: The response to the service request, defaults to None. headers: The headers of the response, defaults to None. status_code: The status code of the response, defaults to None. Attributes: result (dict, requests.Response, None): The response to the service request. headers (dict): The headers of the response. status_code (int): The status code of the response. """ def __init__( self, *, response: Optional[Union[dict, requests.Response]] = None, headers: Optional[Dict[str, str]] = None, status_code: Optional[int] = None ) -> None: self.result = response self.headers = headers self.status_code = status_code def get_result(self) -> Optional[Union[dict, requests.Response]]: """Get the response returned by the service request. Returns: The response to the service request. This could be one of the following: 1. a dict that represents an instance of a response model 2. a requests.Response instance if the operation returns a streamed response 3. None if the server returned no response body """ return self.result def get_headers(self) -> Optional[dict]: """The HTTP response headers of the service request. Returns: A dictionary of response headers or None if no headers are present. """ return self.headers def get_status_code(self) -> Union[int, None]: """The HTTP status code of the service request. Returns: The status code associated with the service request. """ return self.status_code def _to_dict(self) -> dict: _dict = {} if hasattr(self, 'result') and self.result is not None: _dict['result'] = self.result if isinstance(self.result, (dict, list)) else 'HTTP response' if hasattr(self, 'headers') and self.headers is not None: _dict['headers'] = self.headers if hasattr(self, 'status_code') and self.status_code is not None: _dict['status_code'] = self.status_code return _dict def __str__(self) -> str: return json.dumps(self._to_dict(), indent=4, default=lambda o: o.__dict__) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/get_authenticator.py0000664000372000037200000001074100000000000025725 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from .authenticators import ( Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator, CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator, VPCInstanceAuthenticator, MCSPAuthenticator, ) from .utils import read_external_sources def get_authenticator_from_environment(service_name: str) -> Authenticator: """Look for external configuration of authenticator. Try to get authenticator from external sources, with the following priority: 1. Credentials file(ibm-credentials.env) 2. Environment variables 3. VCAP Services(Cloud Foundry) Args: service_name: The service name. Returns: The authenticator found from service information. """ authenticator = None config = read_external_sources(service_name) if config: authenticator = __construct_authenticator(config) return authenticator def __construct_authenticator(config: dict) -> Authenticator: # Determine the authentication type if not specified explicitly. if config.get('AUTH_TYPE'): auth_type = config.get('AUTH_TYPE') elif config.get('AUTHTYPE'): auth_type = config.get('AUTHTYPE') else: # If authtype wasn't specified explicitly, then determine the default. # If the APIKEY property is specified, then it should be IAM, otherwise Container Auth. if config.get('APIKEY'): auth_type = Authenticator.AUTHTYPE_IAM else: auth_type = Authenticator.AUTHTYPE_CONTAINER auth_type = auth_type.lower() authenticator = None if auth_type == Authenticator.AUTHTYPE_BASIC.lower(): authenticator = BasicAuthenticator(username=config.get('USERNAME'), password=config.get('PASSWORD')) elif auth_type == Authenticator.AUTHTYPE_BEARERTOKEN.lower(): authenticator = BearerTokenAuthenticator(bearer_token=config.get('BEARER_TOKEN')) elif auth_type == Authenticator.AUTHTYPE_CONTAINER.lower(): authenticator = ContainerAuthenticator( cr_token_filename=config.get('CR_TOKEN_FILENAME'), iam_profile_name=config.get('IAM_PROFILE_NAME'), iam_profile_id=config.get('IAM_PROFILE_ID'), url=config.get('AUTH_URL'), client_id=config.get('CLIENT_ID'), client_secret=config.get('CLIENT_SECRET'), disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true', scope=config.get('SCOPE'), ) elif auth_type == Authenticator.AUTHTYPE_CP4D.lower(): authenticator = CloudPakForDataAuthenticator( username=config.get('USERNAME'), password=config.get('PASSWORD'), url=config.get('AUTH_URL'), apikey=config.get('APIKEY'), disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true', ) elif auth_type == Authenticator.AUTHTYPE_IAM.lower() and config.get('APIKEY'): authenticator = IAMAuthenticator( apikey=config.get('APIKEY'), url=config.get('AUTH_URL'), client_id=config.get('CLIENT_ID'), client_secret=config.get('CLIENT_SECRET'), disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true', scope=config.get('SCOPE'), ) elif auth_type == Authenticator.AUTHTYPE_VPC.lower(): authenticator = VPCInstanceAuthenticator( iam_profile_crn=config.get('IAM_PROFILE_CRN'), iam_profile_id=config.get('IAM_PROFILE_ID'), url=config.get('AUTH_URL'), ) elif auth_type == Authenticator.AUTHTYPE_MCSP.lower(): authenticator = MCSPAuthenticator( apikey=config.get('APIKEY'), url=config.get('AUTH_URL'), ) elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower(): authenticator = NoAuthAuthenticator() return authenticator ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/0000775000372000037200000000000000000000000024634 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/__init__.py0000664000372000037200000000113600000000000026746 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021 IBM 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/container_token_manager.py0000664000372000037200000002224000000000000032062 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from typing import Dict, Optional from .iam_request_based_token_manager import IAMRequestBasedTokenManager logger = logging.getLogger(__name__) class ContainerTokenManager(IAMRequestBasedTokenManager): """The ContainerTokenManager takes a compute resource token and performs the necessary interactions with the IAM token service to obtain and store a suitable bearer token. Additionally, the ContainerTokenManager will retrieve bearer tokens via basic auth using a supplied client_id and client_secret pair. If the current stored bearer token has expired a new bearer token will be retrieved. Attributes: cr_token_filename(str): The name of the file containing the injected CR token value (applies to IKS-managed compute resources). iam_profile_name (str): The name of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_profile_id must be specified. iam_profile_id (str): The id of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_profile_id must be specified. url (str): The IAM endpoint to token requests. client_id (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. client_secret (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. headers (dict): Default headers to be sent with every IAM token request. proxies (dict): Proxies to use for communicating with IAM. proxies.http (str): The proxy endpoint to use for HTTP requests. proxies.https (str): The proxy endpoint to use for HTTPS requests. http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests. scope (str): The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Keyword Args: cr_token_filename: The name of the file containing the injected CR token value (applies to IKS-managed compute resources). Defaults to "/var/run/secrets/tokens/vault-token". iam_profile_name: The name of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_profile_id must be specified. Defaults to None. iam_profile_id: The id of the linked trusted IAM profile to be used when obtaining the IAM access token (a CR token might map to multiple IAM profiles). One of iam_profile_name or iam_prfoile_id must be specified. Defaults to None. url: The IAM endpoint to token requests. Defaults to None. client_id: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. client_secret: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every IAM token request. Defaults to None. proxies: Proxies to use for communicating with IAM. Defaults to None. proxies.http: The proxy endpoint to use for HTTP requests. proxies.https: The proxy endpoint to use for HTTPS requests. scope: The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. """ DEFAULT_CR_TOKEN_FILENAME1 = '/var/run/secrets/tokens/vault-token' DEFAULT_CR_TOKEN_FILENAME2 = '/var/run/secrets/tokens/sa-token' def __init__( self, cr_token_filename: Optional[str] = None, iam_profile_name: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, disable_ssl_verification: bool = False, scope: Optional[str] = None, proxies: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None, ) -> None: super().__init__( url=url, client_id=client_id, client_secret=client_secret, disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope, ) self.cr_token_filename = cr_token_filename self.iam_profile_name = iam_profile_name self.iam_profile_id = iam_profile_id self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:cr-token' def retrieve_cr_token(self) -> str: """Retrieves the CR token for the current compute resource by reading it from the local file system. Raises: Exception: Error retrieving the compute resource token. Returns: A string which contains the compute resource token. """ try: cr_token = None if self.cr_token_filename: # If the user specified a filename, then use that. cr_token = self.read_file(self.cr_token_filename) else: # If the user didn't specify a filename, then try our two defaults. try: cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME1) except: cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME2) return cr_token except Exception as ex: # pylint: disable=broad-exception-raised raise Exception('Unable to retrieve the CR token: {}'.format(ex)) from None def request_token(self) -> dict: """Retrieves a CR token value from the current compute resource, then uses that to obtain a new IAM access token from the IAM token server. Returns: A dictionary containing the bearer token to be subsequently used service requests. """ # Set the request payload. self.request_payload['cr_token'] = self.retrieve_cr_token() if self.iam_profile_id: self.request_payload['profile_id'] = self.iam_profile_id if self.iam_profile_name: self.request_payload['profile_name'] = self.iam_profile_name return super().request_token() def set_cr_token_filename(self, cr_token_filename: str) -> None: """Set the location of the compute resource token on the local filesystem. Args: cr_token_filename: path to the compute resource token """ self.cr_token_filename = cr_token_filename def set_iam_profile_name(self, iam_profile_name: str) -> None: """Set the name of the IAM profile. Args: iam_profile_name: name of the linked trusted IAM profile to be used when obtaining the IAM access token """ self.iam_profile_name = iam_profile_name def set_iam_profile_id(self, iam_profile_id: str) -> None: """Set the id of the IAM profile. Args: iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token """ self.iam_profile_id = iam_profile_id def read_file(self, filename: str) -> str: """Read in the specified file and return the contents as a string. Args: filename: the name of the file to read Returns: The contents of the file as a string. Raises: Exception: An error occured reading the file. """ try: logger.debug('Attempting to read CR token from file: %s', filename) with open(filename, 'r', encoding='utf-8') as file: cr_token = file.read() logger.debug('Successfully read CR token from file: %s', filename) return cr_token except Exception as ex: # pylint: disable=broad-exception-raised raise Exception('Error reading CR token from file {}: {}'.format(filename, ex)) from None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py0000664000372000037200000001067200000000000030740 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM 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 json from typing import Dict, Optional from .jwt_token_manager import JWTTokenManager class CP4DTokenManager(JWTTokenManager): """Token Manager of CloudPak for data. The Token Manager performs basic auth with a username and password to acquire JWT tokens. Keyword Arguments: username: The username for authentication [required]. password: The password for authentication [required if apikey not specified]. url: The endpoint for JWT token requests [required]. apikey: The apikey for authentication [required if password not specified]. disable_ssl_verification: Disable ssl verification. Defaults to False. headers: Headers to be sent with every service token request. Defaults to None. proxies: Proxies to use for making request. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. verify (optional): The path to the certificate to use for HTTPS requests. Attributes: username (str): The username for authentication. password (str): The password for authentication. url (str): The endpoint for JWT token requests. headers (dict): Headers to be sent with every service token request. proxies (dict): Proxies to use for making token requests. proxies.http (str): The proxy endpoint to use for HTTP requests. proxies.https (str): The proxy endpoint to use for HTTPS requests. verify (str): The path to the certificate to use for HTTPS requests. """ TOKEN_NAME = 'token' VALIDATE_AUTH_PATH = '/v1/authorize' def __init__( self, username: str = None, password: str = None, url: str = None, *, apikey: str = None, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, verify: Optional[str] = None ) -> None: self.username = username self.password = password self.verify = verify if url and not self.VALIDATE_AUTH_PATH in url: url = url + '/v1/authorize' self.apikey = apikey self.headers = headers if self.headers is None: self.headers = {} self.headers['Content-Type'] = 'application/json' self.proxies = proxies super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME) def request_token(self) -> dict: """Makes a request for a token.""" response = self._request( method='POST', headers=self.headers, url=self.url, data=json.dumps({"username": self.username, "password": self.password, "api_key": self.apikey}), proxies=self.proxies, verify=self.verify, ) return response def set_headers(self, headers: Dict[str, str]) -> None: """Headers to be sent with every CP4D token request. Args: headers: The headers to be sent with every CP4D token request. """ if isinstance(headers, dict): self.headers = headers else: raise TypeError('headers must be a dictionary') def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with CP4D on behalf of the host. Args: proxies: Proxies to use for making request. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ if isinstance(proxies, dict): self.proxies = proxies else: raise TypeError('proxies must be a dictionary') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py0000664000372000037200000001767600000000000033575 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019, 2024 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from .jwt_token_manager import JWTTokenManager # pylint: disable=too-many-instance-attributes class IAMRequestBasedTokenManager(JWTTokenManager): """The IamRequestBasedTokenManager class contains code relevant to any token manager that interacts with the IAM service to manage a token. It stores information relevant to all IAM requests, such as the client ID and secret, and performs the token request with a set of request options common to any IAM token management scheme. If the current stored bearer token has expired a new bearer token will be retrieved. Attributes: request_payload(dict): the data that will be sent in the IAM OAuth token request url (str): The IAM endpoint to token requests. client_id (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. client_secret (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. headers (dict): Default headers to be sent with every IAM token request. proxies (dict): Proxies to use for communicating with IAM. proxies.http (str): The proxy endpoint to use for HTTP requests. proxies.https (str): The proxy endpoint to use for HTTPS requests. http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests. scope (str): The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Keyword Args: url: The IAM endpoint to token requests. Defaults to None. client_id: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. client_secret: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every IAM token request. Defaults to None. proxies: Proxies to use for communicating with IAM. Defaults to None. proxies.http: The proxy endpoint to use for HTTP requests. proxies.https: The proxy endpoint to use for HTTPS requests. scope: The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. """ DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com' OPERATION_PATH = "/identity/token" IAM_EXPIRATION_WINDOW = 10 def __init__( self, url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, scope: Optional[str] = None, ) -> None: if not url: url = self.DEFAULT_IAM_URL if url.endswith(self.OPERATION_PATH): url = url[: -len(self.OPERATION_PATH)] self.url = url self.client_id = client_id self.client_secret = client_secret self.headers = headers self.refresh_token = None self.proxies = proxies self.scope = scope self.request_payload = {} super().__init__(self.url, disable_ssl_verification=disable_ssl_verification, token_name='access_token') def request_token(self) -> dict: """Request an IAM OAuth token given an API Key. If client_id and client_secret are specified use their values as a user and pass auth set according to WHATWG url spec. Returns: A dictionary containing the bearer token to be subsequently used service requests. """ headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'} if self.headers is not None and isinstance(self.headers, dict): headers.update(self.headers) data = dict(self.request_payload) if self.scope is not None and self.scope: data['scope'] = self.scope auth_tuple = None # If both the client_id and secret were specified by the user, then use them if self.client_id and self.client_secret: auth_tuple = (self.client_id, self.client_secret) response = self._request( method='POST', url=(self.url + self.OPERATION_PATH) if self.url else self.url, headers=headers, data=data, auth_tuple=auth_tuple, proxies=self.proxies, ) return response def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None: """Set the client_id and client_secret. Args: client_id: The client id to be used for token requests. client_secret: The client secret to be used for token requests. """ self.client_id = client_id self.client_secret = client_secret def set_headers(self, headers: Dict[str, str]) -> None: """Headers to be sent with every CP4D token request. Args: headers: Headers to be sent with every IAM token request. """ if isinstance(headers, dict): self.headers = headers else: raise TypeError('headers must be a dictionary') def _save_token_info(self, token_response: dict) -> None: super()._save_token_info(token_response) self.refresh_token = token_response.get("refresh_token") def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with IAM on behalf of the host. Args: proxies: Proxies to use for communicating with IAM. proxies.http (str, optional): The proxy endpoint to use for HTTP requests. proxies.https (str, optional): The proxy endpoint to use for HTTPS requests. """ if isinstance(proxies, dict): self.proxies = proxies else: raise TypeError('proxies must be a dictionary') def set_scope(self, value: str) -> None: """Sets the "scope" parameter to use when fetching the bearer token from the IAM token server. Args: value: A space seperated string that makes up the scope parameter. """ self.scope = value def _is_token_expired(self) -> bool: """ Returns true iff the current cached token is expired. We'll consider an access token as expired when we reach its IAM server-reported expiration time minus our expiration window (10 secs). We do this to avoid using an access token that might expire in the middle of a long-running transaction within an IBM Cloud service. Returns ------- bool True if token is expired; False otherwise """ current_time = self._get_current_time() return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/iam_token_manager.py0000664000372000037200000001021100000000000030641 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Dict, Optional from .iam_request_based_token_manager import IAMRequestBasedTokenManager class IAMTokenManager(IAMRequestBasedTokenManager): """The IAMTokenManager takes an api key and performs the necessary interactions with the IAM token service to obtain and store a suitable bearer token. Additionally, the IAMTokenManager If the current stored bearer token has expired a new bearer token will be retrieved. Attributes: apikey: A generated API key from ibmcloud. url (str): The IAM endpoint to token requests. client_id (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. client_secret (str): The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. headers (dict): Default headers to be sent with every IAM token request. proxies (dict): Proxies to use for communicating with IAM. proxies.http (str): The proxy endpoint to use for HTTP requests. proxies.https (str): The proxy endpoint to use for HTTPS requests. http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests. scope (str): The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. Args: apikey: A generated APIKey from ibmcloud. Keyword Args: url: The IAM endpoint to token requests. Defaults to None. client_id: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. client_secret: The client_id and client_secret fields are used to form a "basic auth" Authorization header for interactions with the IAM token server. Defaults to None. disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. headers: Default headers to be sent with every IAM token request. Defaults to None. proxies: Proxies to use for communicating with IAM. Defaults to None. proxies.http: The proxy endpoint to use for HTTP requests. proxies.https: The proxy endpoint to use for HTTPS requests. scope: The "scope" to use when fetching the bearer token from the IAM token server. This can be used to obtain an access token with a specific scope. """ def __init__( self, apikey: str, *, url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, scope: Optional[str] = None ) -> None: super().__init__( url=url, client_id=client_id, client_secret=client_secret, disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope, ) self.apikey = apikey # Set API key related data. self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:apikey' self.request_payload['apikey'] = self.apikey self.request_payload['response_type'] = 'cloud_iam' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/jwt_token_manager.py0000664000372000037200000000731100000000000030706 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019, 2020 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from abc import ABC from typing import Optional import jwt import requests from .token_manager import TokenManager from ..api_exception import ApiException class JWTTokenManager(TokenManager, ABC): """An abstract class to contain functionality for parsing, storing, and requesting JWT tokens. get_token will retrieve a new token from the url in case the that there is no existing token, or the previous token has expired. Child classes will implement request_token, which will do the actual acquisition of a new token. Args: url: The url to request tokens from. Keyword Args: disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. token_name: The key that maps to the token in the dictionary returned from request_token. Defaults to None. Attributes: url (str): The url to request tokens from. disable_ssl_verification (bool): A flag that indicates whether verification of the server's SSL certificate should be disabled or not. token_name (str): The key used of the token in the dict returned from request_token. token_info (dict): The most token_response from request_token. """ def __init__(self, url: str, *, disable_ssl_verification: bool = False, token_name: Optional[str] = None): super().__init__(url, disable_ssl_verification=disable_ssl_verification) self.token_name = token_name self.token_info = {} def _save_token_info(self, token_response: dict) -> None: """ Decode the access token and save the response from the JWT service to the object's state Refresh time is set to approximately 80% of the token's TTL to ensure that the token refresh completes before the current token expires. Parameters ---------- token_response : dict Response from token service """ self.token_info = token_response self.access_token = token_response.get(self.token_name) # The time of expiration is found by decoding the JWT access token decoded_response = jwt.decode(self.access_token, algorithms=["RS256"], options={"verify_signature": False}) # exp is the time of expire and iat is the time of token retrieval exp = decoded_response.get('exp') iat = decoded_response.get('iat') self.expire_time = exp buffer = (exp - iat) * 0.2 self.refresh_time = self.expire_time - buffer def _request(self, method, url, *, headers=None, params=None, data=None, auth_tuple=None, **kwargs) -> dict: kwargs = dict({"timeout": 60}, **kwargs) kwargs = dict(kwargs, **self.http_config) if self.disable_ssl_verification: kwargs['verify'] = False response = requests.request( method=method, url=url, headers=headers, params=params, data=data, auth=auth_tuple, **kwargs ) if 200 <= response.status_code <= 299: return response.json() raise ApiException(response.status_code, http_response=response) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py0000664000372000037200000000677700000000000031063 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2023 IBM 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 json from typing import Dict, Optional from .jwt_token_manager import JWTTokenManager class MCSPTokenManager(JWTTokenManager): """The MCSPTokenManager accepts a user-supplied apikey and performs the necessary interactions with the Multi-Cloud Saas Platform (MCSP) token service to obtain an MCSP access token (a bearer token). When the access token expires, a new access token is obtained from the token server. Keyword Arguments: apikey: The apikey for authentication [required]. url: The endpoint for JWT token requests [required]. disable_ssl_verification: Disable ssl verification. Defaults to False. headers: Headers to be sent with every service token request. Defaults to None. proxies: Proxies to use for making request. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ TOKEN_NAME = 'token' OPERATION_PATH = '/siusermgr/api/1.0/apikeys/token' def __init__( self, apikey: str, url: str, *, disable_ssl_verification: bool = False, headers: Optional[Dict[str, str]] = None, proxies: Optional[Dict[str, str]] = None, ) -> None: self.apikey = apikey self.headers = headers if self.headers is None: self.headers = {} self.headers['Content-Type'] = 'application/json' self.headers['Accept'] = 'application/json' self.proxies = proxies super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME) def request_token(self) -> dict: """Makes a request for a token.""" response = self._request( method='POST', headers=self.headers, url=self.url + self.OPERATION_PATH, data=json.dumps({"apikey": self.apikey}), proxies=self.proxies, ) return response def set_headers(self, headers: Dict[str, str]) -> None: """Headers to be sent with every MCSP token request. Args: headers: The headers to be sent with every MCSP token request. """ if isinstance(headers, dict): self.headers = headers else: raise TypeError('headers must be a dictionary') def set_proxies(self, proxies: Dict[str, str]) -> None: """Sets the proxies the token manager will use to communicate with MCSP on behalf of the host. Args: proxies: Proxies to use for making request. Defaults to None. proxies.http (optional): The proxy endpoint to use for HTTP requests. proxies.https (optional): The proxy endpoint to use for HTTPS requests. """ if isinstance(proxies, dict): self.proxies = proxies else: raise TypeError('proxies must be a dictionary') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/token_manager.py0000664000372000037200000001635700000000000030034 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2020 IBM 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 time from abc import ABC, abstractmethod from threading import Lock import requests from ..api_exception import ApiException # pylint: disable=too-many-instance-attributes class TokenManager(ABC): """An abstract class to contain functionality for parsing, storing, and requesting tokens. get_token will retrieve a new token from the url in case the that there is no existing token, or the previous token has expired. Child classes will implement request_token, which will do the actual acquisition of a new token. Args: url: The url to request tokens from. Keyword Args: disable_ssl_verification: A flag that indicates whether verification of the server's SSL certificate should be disabled or not. Defaults to False. Attributes: url (str): The url to request tokens from. disable_ssl_verification (bool): A flag that indicates whether verification of the server's SSL certificate should be disabled or not. expire_time (int): The time in epoch seconds when the current stored token will expire. refresh_time (int): The time in epoch seconds when the current stored token should be refreshed. request_time (int): The time the last outstanding token request was issued lock (Lock): Lock variable to serialize access to refresh/request times http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests. access_token (str): The latest stored access token """ def __init__(self, url: str, *, disable_ssl_verification: bool = False): self.url = url self.disable_ssl_verification = disable_ssl_verification self.expire_time = 0 self.refresh_time = 0 self.request_time = 0 self.lock = Lock() self.http_config = {} self.access_token = None def get_token(self) -> str: """Get a token to be used for authentication. The source of the token is determined by the following logic: 1. a) If the current token is expired (or never fetched), make a request for one b) If the current token should be refreshed, issue a refresh request 2. After any requests initiated above complete, return the stored token Returns: str: A valid access token """ if self._is_token_expired(): self.paced_request_token() if self._token_needs_refresh(): token_response = self.request_token() self._save_token_info(token_response) return self.access_token def set_disable_ssl_verification(self, status: bool = False) -> None: """Sets the ssl verification to enabled or disabled. Args: status: the flag to be used for determining status. Raises: TypeError: The `status` is not a bool. """ if isinstance(status, bool): self.disable_ssl_verification = status else: raise TypeError('status must be a bool') def paced_request_token(self) -> None: """ Paces requests to request_token. This method pseudo-serializes requests for an access_token when the current token is expired (or has never been fetched). The first caller into this method records its `request_time` and then issues the token request. Subsequent callers will check the `request_time` to see if a request is active (has been issued within the past 60 seconds), and if so will sleep for a short time interval (currently 0.5 seconds) before checking again. The check for an active request and update of `request_time` are serailized by the `lock` variable so that only one caller can become the active requester with a 60 second interval. Threads that sleep waiting for the active request to complete will eventually find a newly valid token and return, or 60 seconds will elapse and a new thread will assume the role of the active request. """ while self._is_token_expired(): current_time = self._get_current_time() with self.lock: request_active = self.request_time > (current_time - 60) if not request_active: self.request_time = current_time if not request_active: token_response = self.request_token() self._save_token_info(token_response) self.request_time = 0 return # Sleep for 0.5 seconds before checking token again time.sleep(0.5) @abstractmethod # pragma: no cover def request_token(self) -> None: """Should be overridden by child classes.""" pass @staticmethod def _get_current_time() -> int: return int(time.time()) def _is_token_expired(self) -> bool: """ Check if currently stored token is expired. Returns ------- bool True if token is expired; False otherwise """ current_time = self._get_current_time() return self.expire_time < current_time def _token_needs_refresh(self) -> bool: """ Check if currently stored token needs refresh. Returns ------- bool True if token needs refresh; False otherwise """ current_time = self._get_current_time() with self.lock: needs_refresh = self.refresh_time < current_time if needs_refresh: self.refresh_time = current_time + 60 return needs_refresh @abstractmethod def _save_token_info(self, token_response: dict) -> None: # pragma: no cover """ Decode the access token and save the response from the service to the object's state Refresh time is set to approximately 80% of the token's TTL to ensure that the token refresh completes before the current token expires. Parameters ---------- token_response : dict Response from token service """ pass def _request(self, method, url, *, headers=None, params=None, data=None, auth_tuple=None, **kwargs): kwargs = dict({"timeout": 60}, **kwargs) kwargs = dict(kwargs, **self.http_config) if self.disable_ssl_verification: kwargs['verify'] = False response = requests.request( method=method, url=url, headers=headers, params=params, data=data, auth=auth_tuple, **kwargs ) if 200 <= response.status_code <= 299: return response raise ApiException(response.status_code, http_response=response) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py0000664000372000037200000001516400000000000032563 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021, 2024 IBM 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 json import logging from typing import Optional from .jwt_token_manager import JWTTokenManager logger = logging.getLogger(__name__) class VPCInstanceTokenManager(JWTTokenManager): """The VPCInstanceTokenManager retrieves an "instance identity token" and exchanges that for an IAM access token using the VPC Instance Metadata Service API which is available on the local compute resource (VM). The instance identity token is similar to an IAM apikey, except that it is managed automatically by the compute resource provider (VPC). The resulting IAM access token is then added to outbound requests in an Authorization header of the form: Authorization: Bearer Keyword Arguments: iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile to be used as the identity of the compute resource. At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. Defaults to None. iam_profile_id (str, optional): The ID of the linked trusted IAM profile to be used when obtaining the IAM access token. At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified, then the default IAM profile defined for the compute resource will be used. Defaults to None. url (str, optional): The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'. Attributes: iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile. iam_profile_id (str, optional): The ID of the linked trusted IAM profile. url (str, optional): The VPC Instance Metadata Service's base endpoint URL. """ METADATA_SERVICE_VERSION = '2022-03-01' DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254' TOKEN_NAME = 'access_token' IAM_EXPIRATION_WINDOW = 10 def __init__( self, iam_profile_crn: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None ) -> None: if not url: url = self.DEFAULT_IMS_ENDPOINT super().__init__(url, token_name=self.TOKEN_NAME) self.iam_profile_crn = iam_profile_crn self.iam_profile_id = iam_profile_id def request_token(self) -> dict: """RequestToken will use the VPC Instance Metadata Service to (1) retrieve a fresh instance identity token and then (2) exchange that for an IAM access token. Returns: A dictionary containing the bearer token to be subsequently used service requests. """ # Retrieve the Instance Identity Token first. instance_identity_token = self.retrieve_instance_identity_token() url = self.url + '/instance_identity/v1/iam_token' request_payload = None if self.iam_profile_crn: request_payload = {'trusted_profile': {'crn': self.iam_profile_crn}} if self.iam_profile_id: request_payload = {'trusted_profile': {'id': self.iam_profile_id}} headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer ' + instance_identity_token, } logger.debug('Invoking VPC \'create_iam_token\' operation: %s', url) response = self._request( method='POST', url=url, headers=headers, params={'version': self.METADATA_SERVICE_VERSION}, data=json.dumps(request_payload) if request_payload else None, ) logger.debug('Returned from VPC \'create_iam_token\' operation."') return response def set_iam_profile_crn(self, iam_profile_crn: str) -> None: """Sets the CRN of the IAM profile. Args: iam_profile_crn (str): the CRN of the linked trusted IAM profile to be used as the identity of the compute resource. """ self.iam_profile_crn = iam_profile_crn def set_iam_profile_id(self, iam_profile_id: str) -> None: """Sets the ID of the IAM profile. Args: iam_profile_id (str): id of the linked trusted IAM profile to be used when obtaining the IAM access token """ self.iam_profile_id = iam_profile_id def retrieve_instance_identity_token(self) -> str: """Retrieves the local compute resource's instance identity token using the "create_access_token" operation of the local VPC Instance Metadata Service API. Returns: The retrieved instance identity token string. """ url = self.url + '/instance_identity/v1/token' headers = { 'Content-type': 'application/json', 'Accept': 'application/json', 'Metadata-Flavor': 'ibm', } request_body = {'expires_in': 300} logger.debug('Invoking VPC \'create_access_token\' operation: %s', url) response = self._request( method='PUT', url=url, headers=headers, params={'version': self.METADATA_SERVICE_VERSION}, data=json.dumps(request_body), ) logger.debug('Returned from VPC \'create_access_token\' operation."') return response['access_token'] def _is_token_expired(self) -> bool: """ Returns true iff the current cached token is expired. We'll consider an access token as expired when we reach its IAM server-reported expiration time minus our expiration window (10 secs). We do this to avoid using an access token that might expire in the middle of a long-running transaction within an IBM Cloud service. Returns ------- bool True if token is expired; False otherwise """ current_time = self._get_current_time() return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/utils.py0000664000372000037200000004024700000000000023360 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2019, 2021 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # from ibm_cloud_sdk_core.authenticators import Authenticator import datetime import gzip import io import json as json_import import re import ssl from os import getenv, environ, getcwd from os.path import isfile, join, expanduser from typing import List, Union from urllib.parse import urlparse, parse_qs from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK from urllib3.util.ssl_ import create_urllib3_context import dateutil.parser as date_parser class SSLHTTPAdapter(HTTPAdapter): """Wraps the original HTTP adapter and adds additional SSL context.""" def __init__(self, *args, **kwargs): self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None) super().__init__(*args, **kwargs) def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): """Create and use custom SSL configuration.""" ssl_context = create_urllib3_context() ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 if self._disable_ssl_verification: ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs) class GzipStream(io.RawIOBase): """Compress files on the fly. GzipStream is a helper class around the gzip library. It helps to compress already opened files (file-like objects) on the fly, so there is no need to read everything into the memory and call the `compress` function on it. The GzipFile is opened on the instance itself so it needs to act as a file-like object. Args: input: the source of the data to be compressed. It can be a file-like object, bytes or string. """ def __init__(self, source: Union[io.IOBase, bytes, str]): self.buffer = b'' if isinstance(source, io.IOBase): # The input is already a file-like object, use it as-is. self.uncompressed = source elif isinstance(source, str): # Strings must be handled with StringIO. self.uncompressed = io.StringIO(source) else: # Handle the rest as raw bytes. self.uncompressed = io.BytesIO(source) self.compressor = gzip.GzipFile(fileobj=self, mode='wb') def read(self, size: int = -1) -> bytes: """Compresses and returns the requested size of data. Args: size: how many bytes to return. -1 to read and compress the whole file """ compressed = b'' if (size < 0) or (len(self.buffer) < size): for raw in self.uncompressed: # We need to encode text like streams (e.g. TextIOWrapper) to bytes. if isinstance(raw, str): raw = raw.encode() self.compressor.write(raw) # Stop compressing if we reached the max allowed size. if 0 < size < len(self.buffer): self.compressor.flush() break else: self.compressor.close() if size < 0: # Return all data from the buffer. compressed = self.buffer self.buffer = b'' else: # If we already have enough data in our buffer # return the desired chunk of bytes compressed = self.buffer[:size] # then remove them from the buffer. self.buffer = self.buffer[size:] return compressed def flush(self) -> None: """Not implemented.""" # Since this "pipe" sits between 2 other stream (source/read -> target/write) # it wouldn't be worth to implemet flushing. pass def write(self, compressed: bytes) -> None: """Append the compressed data to the buffer This happens when the target stream calls the `read` method and that triggers the gzip "compressor". """ self.buffer += compressed def close(self) -> None: """Closes the underlying file-like object.""" self.uncompressed.close() def has_bad_first_or_last_char(val: str) -> bool: """Returns true if a string starts with any of: {," ; or ends with any of: },". Args: val: The string to be tested. Returns: Whether or not the string starts or ends with bad characters. """ return val is not None and (val.startswith('{') or val.startswith('"') or val.endswith('}') or val.endswith('"')) def remove_null_values(dictionary: dict) -> dict: """Create a new dictionary without keys mapped to null values. Args: dictionary: The dictionary potentially containing keys mapped to values of None. Returns: A dict with no keys mapped to None. """ if isinstance(dictionary, dict): return {k: v for (k, v) in dictionary.items() if v is not None} return dictionary def cleanup_values(dictionary: dict) -> dict: """Create a new dictionary with boolean values converted to strings. Ex. true -> 'true', false -> 'false'. { 'key': true } -> { 'key': 'true' } Args: dictionary: The dictionary with keys mapped to booleans. Returns: The dictionary with certain keys mapped to s and not booleans. """ if isinstance(dictionary, dict): return {k: cleanup_value(v) for (k, v) in dictionary.items()} return dictionary def cleanup_value(value: any) -> any: """Convert a boolean value to string.""" if isinstance(value, bool): return 'true' if value else 'false' return value def strip_extra_slashes(value: str) -> str: """Combine multiple trailing slashes to a single slash""" if value.endswith('//'): return value.rstrip('/') + '/' return value def datetime_to_string(val: datetime.datetime) -> str: """Convert a datetime object to string. If the supplied datetime does not specify a timezone, it is assumed to be UTC. Args: val: The datetime object. Returns: datetime serialized to iso8601 format. """ if isinstance(val, datetime.datetime): if val.tzinfo is None: return val.isoformat() + 'Z' val = val.astimezone(datetime.timezone.utc) return val.isoformat().replace('+00:00', 'Z') return val def string_to_datetime(string: str) -> datetime.datetime: """De-serializes string to datetime. Args: string: string containing datetime in iso8601 format. Returns: the de-serialized string as a datetime object. """ val = date_parser.parse(string) if val.tzinfo is not None: return val return val.replace(tzinfo=datetime.timezone.utc) def string_to_datetime_list(string_list: List[str]) -> List[datetime.datetime]: """De-serializes each string in a list to a datetime. Args: string_list: list of strings containing datetime in iso8601 format. Returns: the de-serialized list of strings as a list of datetime objects. """ if not isinstance(string_list, list): raise ValueError( "Invalid argument type: " + str(type(string_list)) + ". Argument string_list must be of type List[str]" ) datetime_list = [] for string_val in string_list: datetime_list.append(string_to_datetime(string_val)) return datetime_list def datetime_to_string_list(datetime_list: List[datetime.datetime]) -> List[str]: """Convert a list of datetime objects to a list of strings. Args: datetime_list: The list of datetime objects. Returns: list of datetimes serialized as strings in iso8601 format. """ if not isinstance(datetime_list, list): raise ValueError( "Invalid argument type: " + str(type(datetime_list)) + ". Argument datetime_list must be of type List[datetime.datetime]" ) string_list = [] for datetime_val in datetime_list: string_list.append(datetime_to_string(datetime_val)) return string_list def date_to_string(val: datetime.date) -> str: """Convert a date object to string. Args: val: The date object. Returns: date serialized to `YYYY-MM-DD` format. """ if isinstance(val, datetime.date): return str(val) return val def string_to_date(string: str) -> datetime.date: """De-serializes string to date. Args: string: string containing date in 'YYYY-MM-DD' format. Returns: the de-serialized string as a date object. """ return date_parser.parse(string).date() def get_query_param(url_str: str, param: str) -> str: """Return a query parameter value from url_str Args: url_str: the URL from which to extract the query parameter value param: the name of the query parameter whose value should be returned Returns: the value of the `param` query parameter as a string Raises: ValueError: if errors are encountered parsing `url_str` """ if not url_str: return None url = urlparse(url_str) if not url.query: return None query = parse_qs(url.query, strict_parsing=True) values = query.get(param) return values[0] if values else None def convert_model(val: any) -> dict: """Convert a model object into an equivalent dict. Arguments: val: A dict or a model object Returns: A dict representation of the input object. """ if isinstance(val, dict): return val if hasattr(val, "to_dict"): return val.to_dict() # Consider raising a ValueError here in the next major release return val def convert_list(val: Union[str, List[str]]) -> str: """Convert a list of strings into comma-separated string. Arguments: val: A string or list of strings Returns: A comma-separated string of the items in the input list. """ if isinstance(val, str): return val if isinstance(val, list) and all(isinstance(x, str) for x in val): return ",".join(val) # Consider raising a ValueError here in the next major release return val def read_external_sources(service_name: str) -> dict: """Look for external configuration of a service. Try to get config from external sources, with the following priority: 1. Credentials file(ibm-credentials.env) 2. Environment variables 3. VCAP Services(Cloud Foundry) Args: service_name: The service name Returns: A dictionary containing relevant configuration for the service if found. """ config = {} config = __read_from_credential_file(service_name) if not config: config = __read_from_env_variables(service_name) if not config: config = __read_from_vcap_services(service_name) return config def __read_from_env_variables(service_name: str) -> dict: """Return a config object based on environment variables for a service. Args: service_name: The name of the service to look for in env variables. Returns: A set of service configuration key-value pairs. """ config = {} for key, value in environ.items(): _parse_key_and_update_config(config, service_name, key, value) return config def __read_from_credential_file(service_name: str, *, separator: str = '=') -> dict: """Return a config object based on credentials file for a service. Args: service_name: The name of the service to look for in env variables. Keyword Args: separator: The character to split on to de-serialize a line into a key-value pair. Returns: A set of service configuration key-value pairs. """ default_credentials_file_name = 'ibm-credentials.env' # 1. ${IBM_CREDENTIALS_FILE} credential_file_path = getenv('IBM_CREDENTIALS_FILE') # 2. /ibm-credentials.env if credential_file_path is None: file_path = join(getcwd(), default_credentials_file_name) if isfile(file_path): credential_file_path = file_path # 3. /ibm-credentials.env if credential_file_path is None: file_path = join(expanduser('~'), default_credentials_file_name) if isfile(file_path): credential_file_path = file_path config = {} if credential_file_path is not None: try: with open(credential_file_path, 'r', encoding='utf-8') as fobj: for line in fobj: key_val = line.strip().split(separator, 1) if len(key_val) == 2: key = key_val[0] value = key_val[1] _parse_key_and_update_config(config, service_name, key, value) except OSError: # just absorb the exception and make sure we return an empty response config = {} return config def _parse_key_and_update_config(config: dict, service_name: str, key: str, value: str) -> None: service_name = service_name.replace(' ', '_').replace('-', '_').upper() if key.startswith(service_name): config[key[len(service_name) + 1 :]] = value def __read_from_vcap_services(service_name: str) -> dict: """Return a config object based on the vcap services environment variable. Args: service_name: The name of the service to look for in the vcap. Returns: A set of service configuration key-value pairs. """ vcap_services = getenv('VCAP_SERVICES') vcap_service_credentials = {} if vcap_services: services = json_import.loads(vcap_services) for key in services.keys(): for i in range(len(services[key])): if vcap_service_credentials and isinstance(vcap_service_credentials, dict): break if services[key][i].get('name') == service_name: vcap_service_credentials = services[key][i].get('credentials', {}) if not vcap_service_credentials: if service_name in services.keys(): service = services.get(service_name) if service: vcap_service_credentials = service[0].get('credentials', {}) if vcap_service_credentials and isinstance(vcap_service_credentials, dict): new_vcap_creds = {} # cf if vcap_service_credentials.get('username') and vcap_service_credentials.get('password'): new_vcap_creds['AUTH_TYPE'] = 'basic' new_vcap_creds['USERNAME'] = vcap_service_credentials.get('username') new_vcap_creds['PASSWORD'] = vcap_service_credentials.get('password') vcap_service_credentials = new_vcap_creds elif vcap_service_credentials.get('iam_apikey'): new_vcap_creds['AUTH_TYPE'] = 'iam' new_vcap_creds['APIKEY'] = vcap_service_credentials.get('iam_apikey') vcap_service_credentials = new_vcap_creds elif vcap_service_credentials.get('apikey'): new_vcap_creds['AUTH_TYPE'] = 'iam' new_vcap_creds['APIKEY'] = vcap_service_credentials.get('apikey') vcap_service_credentials = new_vcap_creds return vcap_service_credentials # A regex that matches an "application/json" mimetype. json_mimetype_pattern = re.compile('^application/json(\\s*;.*)?$') def is_json_mimetype(mimetype: str) -> bool: """Returns true if 'mimetype' is a JSON-like mimetype, false otherwise. Args: mimetype: The mimetype to check. Returns: true if mimetype is a JSON-line mimetype, false otherwise. """ return mimetype is not None and json_mimetype_pattern.match(mimetype) is not None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core/version.py0000664000372000037200000000002700000000000023675 0ustar00travistravis00000000000000__version__ = '3.19.2' ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1709138948.136725 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/0000775000372000037200000000000000000000000023331 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138948.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/PKG-INFO0000644000372000037200000001036700000000000024433 0ustar00travistravis00000000000000Metadata-Version: 2.1 Name: ibm-cloud-sdk-core Version: 3.19.2 Summary: Core library used by SDKs for IBM Cloud Services Home-page: https://github.com/IBM/python-sdk-core Author: IBM Author-email: devxsdk@us.ibm.com License: Apache 2.0 Keywords: ibm,cloud,ibm cloud services Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Application Frameworks Description-Content-Type: text/markdown License-File: LICENSE [![Build Status](https://app.travis-ci.com/IBM/python-sdk-core.svg?branch=main)](https://app.travis-ci.com/IBM/python-sdk-core) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ibm-cloud-sdk-core)](https://pypi.org/project/ibm-cloud-sdk-core/) [![Latest Stable Version](https://img.shields.io/pypi/v/ibm-cloud-sdk-core.svg)](https://pypi.python.org/pypi/ibm-cloud-sdk-core) [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) # IBM Python SDK Core Version 3.19.2 This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator (openapi-sdkgen). # Python Version The current minimum Python version supported is 3.8. ## Installation To install, use `pip`: ```bash python -m pip install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token Authentication - Identity and Access Management (IAM) Authentication - Container Authentication - VPC Instance Authentication - Cloud Pak for Data Authentication - No Authentication (for testing) For more information about the various authentication types and how to use them with your services, click [here](Authentication.md). ## Issues If you encounter an issue with this project, you are welcome to submit a [bug report](https://github.com/IBM/python-sdk-core/issues). Before opening a new issue, please search for similar issues. It's possible that someone has already reported it. ## Logging ### Enable logging ```python import logging logging.basicConfig(level=logging.DEBUG) ``` This would show output of the form: ``` DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): iam.cloud.ibm.com:443 DEBUG:urllib3.connectionpool:https://iam.cloud.ibm.com:443 "POST /identity/token HTTP/1.1" 200 1809 DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "POST /assistant/api/v1/workspaces?version=2018-07-10 HTTP/1.1" 201 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "GET /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10&export=true HTTP/1.1" 200 None DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): gateway.watsonplatform.net:443 DEBUG:urllib3.connectionpool:https://gateway.watsonplatform.net:443 "DELETE /assistant/api/v1/workspaces/883a2a44-eb5f-4b1a-96b0-32a90b475ea8?version=2018-07-10 HTTP/1.1" 200 28 ``` ### Low level request and response dump To get low level information of the requests/ responses: ```python from http.client import HTTPConnection HTTPConnection.debuglevel = 1 ``` ## Open source @ IBM Find more open source projects on the [IBM Github Page](http://github.com/IBM) ## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138948.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/SOURCES.txt0000664000372000037200000000507100000000000025220 0ustar00travistravis00000000000000LICENSE MANIFEST.in README.md pyproject.toml requirements-dev.txt requirements.txt setup.py ibm_cloud_sdk_core/__init__.py ibm_cloud_sdk_core/api_exception.py ibm_cloud_sdk_core/base_service.py ibm_cloud_sdk_core/detailed_response.py ibm_cloud_sdk_core/get_authenticator.py ibm_cloud_sdk_core/utils.py ibm_cloud_sdk_core/version.py ibm_cloud_sdk_core.egg-info/PKG-INFO ibm_cloud_sdk_core.egg-info/SOURCES.txt ibm_cloud_sdk_core.egg-info/dependency_links.txt ibm_cloud_sdk_core.egg-info/requires.txt ibm_cloud_sdk_core.egg-info/top_level.txt ibm_cloud_sdk_core.egg-info/zip-safe ibm_cloud_sdk_core/authenticators/__init__.py ibm_cloud_sdk_core/authenticators/authenticator.py ibm_cloud_sdk_core/authenticators/basic_authenticator.py ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py ibm_cloud_sdk_core/authenticators/container_authenticator.py ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py ibm_cloud_sdk_core/authenticators/iam_authenticator.py ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py ibm_cloud_sdk_core/authenticators/no_auth_authenticator.py ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py ibm_cloud_sdk_core/token_managers/__init__.py ibm_cloud_sdk_core/token_managers/container_token_manager.py ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py ibm_cloud_sdk_core/token_managers/iam_token_manager.py ibm_cloud_sdk_core/token_managers/jwt_token_manager.py ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py ibm_cloud_sdk_core/token_managers/token_manager.py ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py test/__init__.py test/test_api_exception.py test/test_authenticator.py test/test_base_service.py test/test_basic_authenticator.py test/test_bearer_authenticator.py test/test_container_authenticator.py test/test_container_token_manager.py test/test_cp4d_authenticator.py test/test_cp4d_token_manager.py test/test_detailed_response.py test/test_iam_authenticator.py test/test_iam_token_manager.py test/test_jwt_token_manager.py test/test_mcsp_authenticator.py test/test_mcsp_token_manager.py test/test_no_auth_authenticator.py test/test_token_manager.py test/test_utils.py test/test_vpc_instance_authenticator.py test/test_vpc_instance_token_manager.py test_integration/__init__.py test_integration/test_cp4d_authenticator_integration.py test_integration/test_iam_authenticator_integration.py test_integration/test_mcsp_authenticator_integration.py test_integration/test_ssl_verification.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138948.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/dependency_links.txt0000664000372000037200000000000100000000000027377 0ustar00travistravis00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138948.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/requires.txt0000664000372000037200000000014000000000000025724 0ustar00travistravis00000000000000requests<3.0.0,>=2.31.0 urllib3<3.0.0,>=2.1.0 python_dateutil<3.0.0,>=2.8.2 PyJWT<3.0.0,>=2.8.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138948.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/top_level.txt0000664000372000037200000000005100000000000026057 0ustar00travistravis00000000000000ibm_cloud_sdk_core test test_integration ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138854.0 ibm-cloud-sdk-core-3.19.2/ibm_cloud_sdk_core.egg-info/zip-safe0000664000372000037200000000000100000000000024761 0ustar00travistravis00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/pyproject.toml0000664000372000037200000000010000000000000020734 0ustar00travistravis00000000000000[tool.black] line-length = 120 skip-string-normalization = true ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/requirements-dev.txt0000664000372000037200000000021100000000000022063 0ustar00travistravis00000000000000coverage>=7.3.2,<8.0.0 pylint>=3.0.0,<4.0.0 pytest>=7.4.2,<8.0.0 pytest-cov>=4.1.0,<5.0.0 responses>=0.23.3,<1.0.0 black>=24.0.0,<25.0.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/requirements.txt0000664000372000037200000000014000000000000021310 0ustar00travistravis00000000000000requests>=2.31.0,<3.0.0 urllib3>=2.1.0,<3.0.0 python_dateutil>=2.8.2,<3.0.0 PyJWT>=2.8.0,<3.0.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/setup.cfg0000664000372000037200000000004600000000000017652 0ustar00travistravis00000000000000[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/setup.py0000664000372000037200000000573100000000000017551 0ustar00travistravis00000000000000#!/usr/bin/env python # Copyright 2019 IBM 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 os import sys import pkg_resources from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand __version__ = '3.19.2' if sys.argv[-1] == 'publish': # test server os.system('python setup.py register -r pypitest') os.system('python setup.py sdist upload -r pypitest') # production server os.system('python setup.py register -r pypi') os.system('python setup.py sdist upload -r pypi') sys.exit() with open('requirements.txt', encoding='utf-8') as f: install_requires = [str(req) for req in pkg_resources.parse_requirements(f)] with open('requirements-dev.txt', encoding='utf-8') as f: tests_require = [str(req) for req in pkg_resources.parse_requirements(f)] class PyTest(TestCommand): """PyTest class.""" def finalize_options(self): TestCommand.finalize_options(self) self.test_args = ['--strict', '--verbose', '--tb=long', 'test'] self.test_suite = True def run_tests(self): import pytest errcode = pytest.main(self.test_args) sys.exit(errcode) with open("README.md", "r", encoding='utf-8') as fh: readme = fh.read() setup( name='ibm-cloud-sdk-core', version=__version__, description='Core library used by SDKs for IBM Cloud Services', license='Apache 2.0', install_requires=install_requires, tests_require=tests_require, cmdclass={'test': PyTest}, author='IBM', author_email='devxsdk@us.ibm.com', long_description=readme, long_description_content_type='text/markdown', url='https://github.com/IBM/python-sdk-core', packages=find_packages(), include_package_data=True, keywords='ibm, cloud, ibm cloud services', classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Application Frameworks', ], zip_safe=True, ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/test/0000775000372000037200000000000000000000000017010 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/__init__.py0000664000372000037200000000000000000000000021107 0ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_api_exception.py0000664000372000037200000000536000000000000023254 0ustar00travistravis00000000000000# coding=utf-8 import json import requests import responses from ibm_cloud_sdk_core import ApiException @responses.activate def test_api_exception(): """Test APIException class""" responses.add( responses.GET, 'https://test.com', status=500, body=json.dumps({'error': 'sorry', 'msg': 'serious error'}), content_type='application/json', ) mock_response = requests.get('https://test.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception is not None assert exception.message == 'sorry' responses.add( responses.GET, 'https://test-again.com', status=500, body=json.dumps( { "errors": [ { "message": "sorry again", } ], } ), content_type='application/json', ) mock_response = requests.get('https://test-again.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception.message == 'sorry again' responses.add( responses.GET, 'https://test-once-more.com', status=500, body=json.dumps({'message': 'sorry once more'}), content_type='application/json', ) mock_response = requests.get('https://test-once-more.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception.message == 'sorry once more' responses.add( responses.GET, 'https://test-msg.com', status=500, body=json.dumps({'msg': 'serious error'}), content_type='application/json', ) mock_response = requests.get('https://test-msg.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception.message == 'Internal Server Error' responses.add( responses.GET, 'https://test-errormessage.com', status=500, body=json.dumps({'errorMessage': 'IAM error message'}), content_type='application/json', ) mock_response = requests.get('https://test-errormessage.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception.message == 'IAM error message' responses.add( responses.GET, 'https://test-for-text.com', status=500, headers={'X-Global-Transaction-ID': 'xx'}, body="plain text error", ) mock_response = requests.get('https://test-for-text.com', timeout=None) exception = ApiException(500, http_response=mock_response) assert exception.message == 'plain text error' assert str(exception) == 'Error: plain text error, Status code: 500 , X-global-transaction-id: xx' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_authenticator.py0000664000372000037200000000112000000000000023265 0ustar00travistravis00000000000000# coding=utf-8 # pylint: disable=missing-docstring from requests import Request from ibm_cloud_sdk_core.authenticators import Authenticator class TestAuthenticator(Authenticator): """A test of the Authenticator base class""" def validate(self) -> None: """Simulated validate() method.""" def authenticate(self, req: Request) -> None: """Simulated authenticate() method.""" def test_authenticator(): authenticator = TestAuthenticator() assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_UNKNOWN ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_base_service.py0000664000372000037200000011612600000000000023062 0ustar00travistravis00000000000000# coding=utf-8 # pylint: disable=missing-docstring,protected-access,too-few-public-methods,too-many-lines import gzip import json import os import ssl import tempfile import time from shutil import copyfile from typing import Optional from urllib3.exceptions import ConnectTimeoutError, MaxRetryError import jwt import pytest import responses import requests from ibm_cloud_sdk_core import ApiException from ibm_cloud_sdk_core import BaseService, DetailedResponse from ibm_cloud_sdk_core import CP4DTokenManager from ibm_cloud_sdk_core import get_authenticator_from_environment from ibm_cloud_sdk_core.authenticators import ( IAMAuthenticator, NoAuthAuthenticator, Authenticator, BasicAuthenticator, CloudPakForDataAuthenticator, ) class IncludeExternalConfigService(BaseService): default_service_url = 'https://servicesthatincludeexternalconfig.com/api' def __init__( self, api_version: str, authenticator: Optional[Authenticator] = None, trace_id: Optional[str] = None ) -> None: BaseService.__init__( self, service_url=self.default_service_url, authenticator=authenticator, disable_ssl_verification=False ) self.api_version = api_version self.trace_id = trace_id self.configure_service('include-external-config') class AnyServiceV1(BaseService): default_url = 'https://gateway.watsonplatform.net/test/api' def __init__( self, version: str, service_url: str = default_url, authenticator: Optional[Authenticator] = None, disable_ssl_verification: bool = False, ) -> None: BaseService.__init__( self, service_url=service_url, authenticator=authenticator, disable_ssl_verification=disable_ssl_verification, ) self.version = version def op_with_path_params(self, path0: str, path1: str) -> DetailedResponse: if path0 is None: raise ValueError('path0 must be provided') if path1 is None: raise ValueError('path1 must be provided') params = {'version': self.version} url = '/v1/foo/{0}/bar/{1}/baz'.format(*self._encode_path_vars(path0, path1)) request = self.prepare_request(method='GET', url=url, params=params) response = self.send(request) return response def with_http_config(self, http_config: dict) -> DetailedResponse: self.set_http_config(http_config) request = self.prepare_request(method='GET', url='') response = self.send(request) return response def any_service_call(self) -> DetailedResponse: request = self.prepare_request(method='GET', url='') response = self.send(request) return response def head_request(self) -> DetailedResponse: request = self.prepare_request(method='HEAD', url='') response = self.send(request) return response def get_document_as_stream(self) -> DetailedResponse: params = {'version': self.version} url = '/v1/streamjson' request = self.prepare_request(method='GET', url=url, params=params) response = self.send(request, stream=True) return response def get_access_token() -> str: access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": 3600, "exp": int(time.time()), } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) return access_token def test_invalid_authenticator(): with pytest.raises(ValueError) as err: AnyServiceV1('2021-08-18') assert str(err.value) == 'authenticator must be provided' @responses.activate def test_url_encoding(): service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator()) # All characters in path0 _must_ be encoded in path segments path0 = ' \"<>^`{}|/\\?#%[]' path0_encoded = '%20%22%3C%3E%5E%60%7B%7D%7C%2F%5C%3F%23%25%5B%5D' # All non-ASCII chars _must_ be encoded in path segments path1 = '比萨浇头'.encode('utf8') # "pizza toppings" path1_encoded = '%E6%AF%94%E8%90%A8%E6%B5%87%E5%A4%B4' path_encoded = '/v1/foo/' + path0_encoded + '/bar/' + path1_encoded + '/baz' test_url = service.default_url + path_encoded responses.add( responses.GET, test_url, status=200, body=json.dumps({"foobar": "baz"}), content_type='application/json' ) # Set Host as a default header on the service. service.set_default_headers({'Host': 'alternatehost.ibm.com:443'}) response = service.op_with_path_params(path0, path1) assert response is not None assert len(responses.calls) == 1 assert path_encoded in responses.calls[0].request.url assert 'version=2017-07-07' in responses.calls[0].request.url # Verify that the Host header was set in the request. assert responses.calls[0].request.headers.get('Host') == 'alternatehost.ibm.com:443' @responses.activate def test_stream_json_response(): service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator()) path = '/v1/streamjson' test_url = service.default_url + path expected_response = json.dumps({"id": 1, "rev": "v1", "content": "this is a document"}) # print("Expected response: ", expected_response) # Simulate a JSON response responses.add(responses.GET, test_url, status=200, body=expected_response, content_type='application/json') # Invoke the operation and receive an "iterable" as the response response = service.get_document_as_stream() assert response is not None assert len(responses.calls) == 1 # retrieve the requests.Response object from the DetailedResponse resp = response.get_result() assert isinstance(resp, requests.Response) assert hasattr(resp, "iter_content") # Retrieve the response body, one chunk at a time. actual_response = '' for chunk in resp.iter_content(chunk_size=3): actual_response += chunk.decode("utf-8") # print("Actual response: ", actual_response) assert actual_response == expected_response @responses.activate def test_http_config(): service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator()) responses.add( responses.GET, service.default_url, status=200, body=json.dumps({"foobar": "baz"}), content_type='application/json', ) response = service.with_http_config({'timeout': 100}) assert response is not None assert len(responses.calls) == 1 def test_fail_http_config(): service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator()) with pytest.raises(TypeError): service.with_http_config(None) @responses.activate def test_cwd(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials.env') # Try changing working directories to test getting creds from cwd cwd = os.getcwd() os.chdir(os.path.dirname(file_path)) iam_authenticator = get_authenticator_from_environment('ibm_watson') service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator) service.configure_service('ibm_watson') os.chdir(cwd) assert service.service_url == 'https://cwdserviceurl' assert service.authenticator is not None # Copy credentials file to cwd to test loading from current working directory temp_env_path = os.getcwd() + '/ibm-credentials.env' copyfile(file_path, temp_env_path) iam_authenticator = get_authenticator_from_environment('ibm_watson') service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator) service.configure_service('ibm_watson') os.remove(temp_env_path) assert service.service_url == 'https://cwdserviceurl' assert service.authenticator is not None @responses.activate def test_iam(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-iam.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path iam_authenticator = get_authenticator_from_environment('ibm-watson') service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator) assert service.service_url == 'https://gateway.watsonplatform.net/test/api' del os.environ['IBM_CREDENTIALS_FILE'] assert service.authenticator is not None response = { "access_token": get_access_token(), "token_type": "Bearer", "expires_in": 3600, "expiration": int(time.time()), "refresh_token": "jy4gl91BQ", } responses.add(responses.POST, url='https://iam.cloud.ibm.com/identity/token', body=json.dumps(response), status=200) responses.add( responses.GET, url='https://gateway.watsonplatform.net/test/api', body=json.dumps({"foobar": "baz"}), content_type='application/json', ) service.any_service_call() assert "grant-type%3Aapikey" in responses.calls[0].request.body def test_no_auth(): class MadeUp: def __init__(self): self.lazy = 'made up' with pytest.raises(ValueError) as err: service = AnyServiceV1('2017-07-07', authenticator=MadeUp()) service.prepare_request( responses.GET, url='https://gateway.watsonplatform.net/test/api', ) assert str(err.value) == 'authenticator should be of type Authenticator' service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator()) service.prepare_request( responses.GET, url='https://gateway.watsonplatform.net/test/api', ) assert service.authenticator is not None assert isinstance(service.authenticator, Authenticator) def test_for_cp4d(): cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password', 'my_url') service = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator) assert service.authenticator.token_manager is not None assert service.authenticator.token_manager.username == 'my_username' assert service.authenticator.token_manager.password == 'my_password' assert service.authenticator.token_manager.url == 'my_url/v1/authorize' assert isinstance(service.authenticator.token_manager, CP4DTokenManager) def test_disable_ssl_verification(): service1 = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator(), disable_ssl_verification=True) assert service1.disable_ssl_verification is True service1.set_disable_ssl_verification(False) assert service1.disable_ssl_verification is False cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password', 'my_url') service2 = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator) assert service2.disable_ssl_verification is False cp4d_authenticator.set_disable_ssl_verification(True) assert service2.authenticator.token_manager.disable_ssl_verification is True @responses.activate def test_http_head(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) expected_headers = {'Test-Header1': 'value1', 'Test-Header2': 'value2'} responses.add(responses.HEAD, service.default_url, status=200, headers=expected_headers, content_type=None) response = service.head_request() assert response is not None assert len(responses.calls) == 1 assert response.headers is not None assert response.headers == expected_headers @responses.activate def test_response_with_no_body(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) responses.add(responses.GET, service.default_url, status=200, body=None) response = service.any_service_call() assert response is not None assert len(responses.calls) == 1 assert response.get_result() is None def test_has_bad_first_or_last_char(): with pytest.raises(ValueError) as err: basic_authenticator = BasicAuthenticator('{my_username}', 'my_password') AnyServiceV1('2018-11-20', authenticator=basic_authenticator).prepare_request( responses.GET, 'https://gateway.watsonplatform.net/test/api' ) assert ( str(err.value) == 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) @responses.activate def test_request_server_error(): with pytest.raises(ApiException, match=r'internal server error') as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=500, body=json.dumps({'error': 'internal server error'}), content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 500 assert err.value.status_code == 500 assert err.value.http_response.headers['Content-Type'] == 'application/json' assert err.value.message == 'internal server error' @responses.activate def test_request_success_json(): responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body=json.dumps({'foo': 'bar'}), content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') detailed_response = service.send(prepped) assert detailed_response.get_result() == {'foo': 'bar'} service = AnyServiceV1('2018-11-20', authenticator=BasicAuthenticator('my_username', 'my_password')) service.set_default_headers({'test': 'header'}) service.set_disable_ssl_verification(True) prepped = service.prepare_request('GET', url='') detailed_response = service.send(prepped) assert detailed_response.get_result() == {'foo': 'bar'} @responses.activate def test_request_success_invalid_json(): # expect an ApiException with JSONDecodeError as the cause when a "success" # response contains invalid JSON in response body. with pytest.raises(ApiException, match=r'Error processing the HTTP response') as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body='{ "invalid": "json", "response"', content_type='application/json; charset=utf8', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 200 assert err.value.status_code == 200 assert err.value.http_response.headers['Content-Type'] == 'application/json; charset=utf8' assert isinstance(err.value.__cause__, requests.exceptions.JSONDecodeError) assert "Expecting ':' delimiter: line 1" in str(err.value.__cause__) @responses.activate def test_request_success_response(): expected_body = '{"foo": "bar", "description": "this\nis\na\ndescription"}' responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body=expected_body, content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') detailed_response = service.send(prepped) assert detailed_response.get_result() == {"foo": "bar", "description": "this\nis\na\ndescription"} @responses.activate def test_request_success_nonjson(): responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body='

Hola, amigo!

', content_type='text/html', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') detailed_response = service.send(prepped) # It's odd that we have to call ".text" to get the string value # (see issue 3557) assert detailed_response.get_result().text == '

Hola, amigo!

' @responses.activate def test_request_fail_401_nonerror_json(): # response body not an error object, so we expect the default error message. error_msg = 'Unauthorized: Access is denied due to invalid credentials' with pytest.raises(ApiException, match=error_msg) as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=401, body=json.dumps({'foo': 'bar'}), content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 401 assert err.value.status_code == 401 assert err.value.http_response.headers['Content-Type'] == 'application/json' assert err.value.message == error_msg @responses.activate def test_request_fail_401_error_json(): # response body is an error object, so we expect to get the message from there. error_msg = 'You dont need to know...' with pytest.raises(ApiException, match=error_msg) as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=401, body=json.dumps({'message': error_msg}), content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 401 assert err.value.status_code == 401 assert err.value.http_response.headers['Content-Type'] == 'application/json' assert err.value.message == error_msg @responses.activate def test_request_fail_401_nonjson(): response_body = 'You dont have a need to know...' with pytest.raises(ApiException, match=response_body) as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=401, body=response_body, content_type='text/plain', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 401 assert err.value.status_code == 401 assert err.value.http_response.headers['Content-Type'] == 'text/plain' assert err.value.message == response_body @responses.activate def test_request_fail_401_badjson(): # if an error response contains invalid JSON, then we should # end up with 'Unknown error' as the message since we couldn't get # the actual error message from the response body. response_body = 'This is not a JSON object' with pytest.raises(ApiException, match=response_body) as err: responses.add( responses.GET, 'https://gateway.watsonplatform.net/test/api', status=401, body=response_body, content_type='application/json', ) service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) prepped = service.prepare_request('GET', url='') service.send(prepped) assert err.value.code == 401 assert err.value.status_code == 401 assert err.value.http_response.headers['Content-Type'] == 'application/json' assert err.value.message == response_body def test_misc_methods(): class MockModel: def __init__(self, xyz=None): self.xyz = xyz def _to_dict(self): _dict = {} if hasattr(self, 'xyz') and self.xyz is not None: _dict['xyz'] = self.xyz return _dict @classmethod def _from_dict(cls, _dict): args = {} if 'xyz' in _dict: args['xyz'] = _dict.get('xyz') return cls(**args) mock = MockModel('foo') service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) model1 = service._convert_model(mock) assert model1 == {'xyz': 'foo'} model2 = service._convert_model("{\"xyz\": \"foo\"}") assert model2 is not None assert model2['xyz'] == 'foo' temp = ['default', '123'] res_str = service._convert_list(temp) assert res_str == 'default,123' temp2 = 'default123' res_str2 = service._convert_list(temp2) assert res_str2 == temp2 def test_default_headers(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) service.set_default_headers({'xxx': 'yyy'}) assert service.default_headers == {'xxx': 'yyy'} with pytest.raises(TypeError): service.set_default_headers('xxx') def test_set_service_url(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) with pytest.raises(ValueError) as err: service.set_service_url('{url}') assert ( str(err.value) == 'The service url shouldn\'t start or end with curly brackets or quotes. ' 'Be sure to remove any {} and \" characters surrounding your service url' ) service.set_service_url('my_url') def test_http_client(): auth = BasicAuthenticator('my_username', 'my_password') service = AnyServiceV1('2018-11-20', authenticator=auth) assert isinstance(service.get_http_client(), requests.sessions.Session) assert service.get_http_client().headers.get('Accept-Encoding') == 'gzip, deflate' new_http_client = requests.Session() new_http_client.headers.update({'Accept-Encoding': 'gzip'}) service.set_http_client(http_client=new_http_client) assert service.get_http_client().headers.get('Accept-Encoding') == 'gzip' with pytest.raises(TypeError): service.set_http_client("bad_argument_type") def test_get_authenticator(): auth = BasicAuthenticator('my_username', 'my_password') service = AnyServiceV1('2018-11-20', authenticator=auth) assert service.get_authenticator() is not None def test_gzip_compression(): # Should return uncompressed data when gzip is off service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) assert not service.get_enable_gzip_compression() prepped = service.prepare_request('GET', url='', data=json.dumps({"foo": "bar"})) assert prepped['data'] == b'{"foo": "bar"}' assert prepped['headers'].get('content-encoding') != 'gzip' # Should return compressed data when gzip is on service.set_enable_gzip_compression(True) assert service.get_enable_gzip_compression() prepped = service.prepare_request('GET', url='', data=json.dumps({"foo": "bar"})) assert prepped['data'] == gzip.compress(b'{"foo": "bar"}') assert prepped['headers'].get('content-encoding') == 'gzip' # Should return compressed data when gzip is on for non-json data assert service.get_enable_gzip_compression() prepped = service.prepare_request('GET', url='', data=b'rawdata') assert prepped['data'] == gzip.compress(b'rawdata') assert prepped['headers'].get('content-encoding') == 'gzip' # Should return compressed data when gzip is on for gzip file data assert service.get_enable_gzip_compression() with tempfile.TemporaryFile(mode='w+b') as t_f: with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f: gz_f.write(json.dumps({"foo": "bar"}).encode()) with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f: gzip_data = gz_f.read() prepped = service.prepare_request('GET', url='', data=gzip_data) assert prepped['data'] == gzip.compress(t_f.read()) assert prepped['headers'].get('content-encoding') == 'gzip' # Should return compressed json data when gzip is on for gzip file json data assert service.get_enable_gzip_compression() with tempfile.TemporaryFile(mode='w+b') as t_f: with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f: gz_f.write("rawdata".encode()) with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f: gzip_data = gz_f.read() prepped = service.prepare_request('GET', url='', data=gzip_data) assert prepped['data'] == gzip.compress(t_f.read()) assert prepped['headers'].get('content-encoding') == 'gzip' # Should return uncompressed data when content-encoding is set assert service.get_enable_gzip_compression() prepped = service.prepare_request( 'GET', url='', headers={"content-encoding": "gzip"}, data=json.dumps({"foo": "bar"}) ) assert prepped['data'] == b'{"foo": "bar"}' assert prepped['headers'].get('content-encoding') == 'gzip' def test_gzip_compression_file_input(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) service.set_enable_gzip_compression(True) # Should return file-like object with the compressed data when compression is on # and the input is a file, opened for reading in binary mode. raw_data = b'rawdata' with tempfile.TemporaryFile(mode='w+b') as tmp_file: tmp_file.write(raw_data) tmp_file.seek(0) prepped = service.prepare_request('GET', url='', data=tmp_file) assert prepped['data'].read() == gzip.compress(raw_data) assert prepped['headers'].get('content-encoding') == 'gzip' assert prepped['data'].read() == b'' # Simulate the requests (urllib3) package reading method for binary files. with tempfile.TemporaryFile(mode='w+b') as tmp_file: tmp_file.write(raw_data) tmp_file.seek(0) prepped = service.prepare_request('GET', url='', data=tmp_file) compressed = b'' for chunk in prepped['data']: compressed += chunk assert compressed == gzip.compress(raw_data) # Make sure the decompression works fine. assert gzip.decompress(compressed) == raw_data # Should return file-like object with the compressed data when compression is on # and the input is a file, opened for reading in text mode. assert service.get_enable_gzip_compression() text_data = 'textdata' with tempfile.TemporaryFile(mode='w+') as tmp_file: tmp_file.write(text_data) tmp_file.seek(0) prepped = service.prepare_request('GET', url='', data=tmp_file) assert prepped['data'].read() == gzip.compress(text_data.encode()) assert prepped['headers'].get('content-encoding') == 'gzip' assert prepped['data'].read() == b'' # Simulate the requests (urllib3) package reading method for text files. with tempfile.TemporaryFile(mode='w+') as tmp_file: tmp_file.write(text_data) tmp_file.seek(0) prepped = service.prepare_request('GET', url='', data=tmp_file) compressed = b'' for chunk in prepped['data']: compressed += chunk assert compressed == gzip.compress(text_data.encode()) # Make sure the decompression works fine. assert gzip.decompress(compressed).decode() == text_data def test_gzip_compression_external(): # Should set gzip compression from external config file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-gzip.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path service = IncludeExternalConfigService('v1', authenticator=NoAuthAuthenticator()) assert service.service_url == 'https://mockurl' assert service.get_enable_gzip_compression() is True prepped = service.prepare_request('GET', url='', data=json.dumps({"foo": "bar"})) assert prepped['data'] == gzip.compress(b'{"foo": "bar"}') assert prepped['headers'].get('content-encoding') == 'gzip' def test_retry_config_default(): service = BaseService(service_url='https://mockurl/', authenticator=NoAuthAuthenticator()) service.enable_retries() assert service.retry_config.total == 4 assert service.retry_config.backoff_factor == 1.0 assert service.retry_config.backoff_max == 30.0 assert service.http_client.get_adapter('https://').max_retries.total == 4 # Ensure retries fail after 4 retries error = ConnectTimeoutError() retry = service.http_client.get_adapter('https://').max_retries retry = retry.increment(error=error) retry = retry.increment(error=error) retry = retry.increment(error=error) retry = retry.increment(error=error) with pytest.raises(MaxRetryError) as retry_err: retry.increment(error=error) assert retry_err.value.reason == error def test_retry_config_disable(): # Test disabling retries service = BaseService(service_url='https://mockurl/', authenticator=NoAuthAuthenticator()) service.enable_retries() service.disable_retries() assert service.retry_config is None assert service.http_client.get_adapter('https://').max_retries.total == 0 # Ensure retries are not started after one connection attempt error = ConnectTimeoutError() retry = service.http_client.get_adapter('https://').max_retries with pytest.raises(MaxRetryError) as retry_err: retry.increment(error=error) assert retry_err.value.reason == error def test_retry_config_non_default(): service = BaseService(service_url='https://mockurl/', authenticator=NoAuthAuthenticator()) service.enable_retries(2, 10.0) assert service.retry_config.total == 2 assert service.retry_config.backoff_factor == 1.0 assert service.retry_config.backoff_max == 10.0 # Ensure retries fail after 2 retries error = ConnectTimeoutError() retry = service.http_client.get_adapter('https://').max_retries retry = retry.increment(error=error) retry = retry.increment(error=error) with pytest.raises(MaxRetryError) as retry_err: retry.increment(error=error) assert retry_err.value.reason == error def test_retry_config_external(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-retry.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path service = IncludeExternalConfigService('v1', authenticator=NoAuthAuthenticator()) assert service.retry_config.total == 3 assert service.retry_config.backoff_factor == 1.0 assert service.retry_config.backoff_max == 25.0 # Ensure retries fail after 3 retries error = ConnectTimeoutError() retry = service.http_client.get_adapter('https://').max_retries retry = retry.increment(error=error) retry = retry.increment(error=error) retry = retry.increment(error=error) with pytest.raises(MaxRetryError) as retry_err: retry.increment(error=error) assert retry_err.value.reason == error @responses.activate def test_user_agent_header(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) user_agent_header = service.user_agent_header assert user_agent_header is not None assert user_agent_header['User-Agent'] is not None responses.add(responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body='some text') prepped = service.prepare_request('GET', url='', headers={'user-agent': 'my_user_agent'}) response = service.send(prepped) assert response.get_result().request.headers.get('user-agent') == 'my_user_agent' prepped = service.prepare_request('GET', url='', headers=None) response = service.send(prepped) assert response.get_result().request.headers.get('user-agent') == user_agent_header['User-Agent'] @responses.activate def test_reserved_keys(caplog): service = AnyServiceV1('2021-07-02', authenticator=NoAuthAuthenticator()) responses.add(responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body='some text') prepped = service.prepare_request('GET', url='', headers={'key': 'OK'}) response = service.send( prepped, headers={'key': 'bad'}, method='POST', url='localhost', cookies=None, hooks={'response': []} ) assert response.get_result().request.headers.get('key') == 'OK' assert response.get_result().request.url == 'https://gateway.watsonplatform.net/test/api' assert response.get_result().request.method == 'GET' assert response.get_result().request.hooks == {'response': []} assert caplog.record_tuples[0][2] == '"method" has been removed from the request' assert caplog.record_tuples[1][2] == '"url" has been removed from the request' assert caplog.record_tuples[2][2] == '"cookies" has been removed from the request' @responses.activate def test_ssl_error(): responses.add(responses.GET, 'https://gateway.watsonplatform.net/test/api', body=requests.exceptions.SSLError()) service = AnyServiceV1('2021-08-18', authenticator=NoAuthAuthenticator()) with pytest.raises(requests.exceptions.SSLError): prepped = service.prepare_request('GET', url='') service.send(prepped) def test_files_dict(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) form_data = {} with open( os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8' ) as file: form_data['file1'] = (None, file, 'application/octet-stream') form_data['string1'] = (None, 'hello', 'text/plain') request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data) files = request['files'] assert isinstance(files, list) assert len(files) == 2 files_dict = dict(files) file1 = files_dict['file1'] assert file1[0] == 'ibm-credentials-iam.env' string1 = files_dict['string1'] assert string1[0] is None def test_files_list(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) form_data = [] with open( os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8' ) as file: form_data.append(('file1', (None, file, 'application/octet-stream'))) form_data.append(('string1', (None, 'hello', 'text/plain'))) request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data) files = request['files'] assert isinstance(files, list) assert len(files) == 2 files_dict = dict(files) file1 = files_dict['file1'] assert file1[0] == 'ibm-credentials-iam.env' string1 = files_dict['string1'] assert string1[0] is None def test_files_duplicate_parts(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) form_data = [] with open( os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8' ) as file: form_data.append(('creds_file', (None, file, 'application/octet-stream'))) with open( os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-basic.env'), 'r', encoding='utf-8' ) as file: form_data.append(('creds_file', (None, file, 'application/octet-stream'))) with open( os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-bearer.env'), 'r', encoding='utf-8' ) as file: form_data.append(('creds_file', (None, file, 'application/octet-stream'))) request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data) files = request['files'] assert isinstance(files, list) assert len(files) == 3 for part_name, file_tuple in files: assert part_name == 'creds_file' assert file_tuple[0] is not None def test_json(): service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator()) req = service.prepare_request('POST', url='', headers={'X-opt-out': True}, data={'hello': 'world', 'fóó': 'bår'}) assert req.get('data') == b'{"hello": "world", "f\\u00f3\\u00f3": "b\\u00e5r"}' def test_service_url_handling(): service = AnyServiceV1('2018-11-20', service_url='https://host///////', authenticator=NoAuthAuthenticator()) assert service.service_url == 'https://host' service.set_service_url('https://host/') assert service.service_url == 'https://host' req = service.prepare_request('POST', url='/path/', headers={'X-opt-out': True}, data={'hello': 'world'}) assert req.get('url') == 'https://host/path/' service = AnyServiceV1('2018-11-20', service_url='https://host/', authenticator=NoAuthAuthenticator()) assert service.service_url == 'https://host' service.set_service_url('https://host/') assert service.service_url == 'https://host' req = service.prepare_request('POST', url='/', headers={'X-opt-out': True}, data={'hello': 'world'}) assert req.get('url') == 'https://host/' req = service.prepare_request('POST', url='////', headers={'X-opt-out': True}, data={'hello': 'world'}) assert req.get('url') == 'https://host/' service.set_service_url(None) assert service.service_url is None service = AnyServiceV1('2018-11-20', service_url='/', authenticator=NoAuthAuthenticator()) assert service.service_url == '' service.set_service_url('/') assert service.service_url == '' with pytest.raises(ValueError) as err: service.prepare_request('POST', url='/', headers={'X-opt-out': True}, data={'hello': 'world'}) assert str(err.value) == 'The service_url is required' def test_service_url_slash(): service = AnyServiceV1('2018-11-20', service_url='/', authenticator=NoAuthAuthenticator()) assert service.service_url == '' with pytest.raises(ValueError) as err: service.prepare_request('POST', url='/', headers={'X-opt-out': True}, data={'hello': 'world'}) assert str(err.value) == 'The service_url is required' def test_service_url_not_set(): service = BaseService(service_url='', authenticator=NoAuthAuthenticator()) with pytest.raises(ValueError) as err: service.prepare_request('POST', url='') assert str(err.value) == 'The service_url is required' def test_setting_proxy(): service = BaseService(service_url='test', authenticator=IAMAuthenticator('wonder woman')) assert service.authenticator is not None assert service.authenticator.token_manager.http_config == {} http_config = {"proxies": {"http": "user:password@host:port"}} service.set_http_config(http_config) assert service.authenticator.token_manager.http_config == http_config service2 = BaseService(service_url='test', authenticator=BasicAuthenticator('marvellous', 'mrs maisel')) service2.set_http_config(http_config) assert service2.authenticator is not None def test_configure_service(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-external.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path service = IncludeExternalConfigService('v1', authenticator=NoAuthAuthenticator()) assert service.service_url == 'https://externallyconfigured.com/api' assert service.disable_ssl_verification is True # The authenticator should not be changed as a result of configure_service() assert isinstance(service.get_authenticator(), NoAuthAuthenticator) def test_configure_service_error(): service = BaseService(service_url='v1', authenticator=NoAuthAuthenticator()) with pytest.raises(ValueError) as err: service.configure_service(None) assert str(err.value) == 'Service_name must be of type string.' def test_min_ssl_version(): service = AnyServiceV1('2022-03-08', authenticator=NoAuthAuthenticator()) adapter = service.http_client.get_adapter('https://') ssl_context = adapter.poolmanager.connection_pool_kw.get('ssl_context', None) assert ssl_context is not None assert ssl_context.minimum_version == ssl.TLSVersion.TLSv1_2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_basic_authenticator.py0000664000372000037200000000307100000000000024435 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import pytest from ibm_cloud_sdk_core.authenticators import BasicAuthenticator, Authenticator def test_basic_authenticator(): authenticator = BasicAuthenticator('my_username', 'my_password') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BASIC assert authenticator.username == 'my_username' assert authenticator.password == 'my_password' request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Basic bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=' def test_basic_authenticator_validate_failed(): with pytest.raises(ValueError) as err: BasicAuthenticator('my_username', None) assert str(err.value) == 'The username and password shouldn\'t be None.' with pytest.raises(ValueError) as err: BasicAuthenticator(None, 'my_password') assert str(err.value) == 'The username and password shouldn\'t be None.' with pytest.raises(ValueError) as err: BasicAuthenticator('{my_username}', 'my_password') assert ( str(err.value) == 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) with pytest.raises(ValueError) as err: BasicAuthenticator('my_username', '{my_password}') assert ( str(err.value) == 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_bearer_authenticator.py0000664000372000037200000000205500000000000024615 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import pytest from ibm_cloud_sdk_core.authenticators import BearerTokenAuthenticator, Authenticator def test_bearer_authenticator(): authenticator = BearerTokenAuthenticator('my_bearer_token') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BEARERTOKEN assert authenticator.bearer_token == 'my_bearer_token' authenticator.set_bearer_token('james bond') assert authenticator.bearer_token == 'james bond' request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer james bond' def test_bearer_validate_failed(): with pytest.raises(ValueError) as err: BearerTokenAuthenticator(None) assert str(err.value) == 'The bearer token shouldn\'t be None.' authenticator = BearerTokenAuthenticator('my_bearer_token') with pytest.raises(ValueError) as err: authenticator.set_bearer_token(None) assert str(err.value) == 'The bearer token shouldn\'t be None.' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_container_authenticator.py0000664000372000037200000001035100000000000025335 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import pytest from ibm_cloud_sdk_core.authenticators import ContainerAuthenticator, Authenticator def test_container_authenticator(): authenticator = ContainerAuthenticator(iam_profile_name='iam-user-123') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CONTAINER assert authenticator.token_manager.cr_token_filename is None assert authenticator.token_manager.iam_profile_name == 'iam-user-123' assert authenticator.token_manager.iam_profile_id is None assert authenticator.token_manager.client_id is None assert authenticator.token_manager.client_secret is None assert authenticator.token_manager.disable_ssl_verification is False assert authenticator.token_manager.headers is None assert authenticator.token_manager.proxies is None assert authenticator.token_manager.scope is None authenticator.set_cr_token_filename('path/to/token') assert authenticator.token_manager.cr_token_filename == 'path/to/token' # Set the IAM profile to None to trigger a validation which will fail, # because both of the profile and ID are None. with pytest.raises(ValueError) as err: authenticator.set_iam_profile_name(None) assert str(err.value) == 'At least one of iam_profile_name or iam_profile_id must be specified.' authenticator.set_iam_profile_id('iam-id-123') assert authenticator.token_manager.iam_profile_id == 'iam-id-123' authenticator.set_iam_profile_name('iam-user-123') assert authenticator.token_manager.iam_profile_name == 'iam-user-123' authenticator.set_client_id_and_secret('tom', 'jerry') assert authenticator.token_manager.client_id == 'tom' assert authenticator.token_manager.client_secret == 'jerry' authenticator.set_scope('scope1 scope2 scope3') assert authenticator.token_manager.scope == 'scope1 scope2 scope3' with pytest.raises(TypeError) as err: authenticator.set_headers('dummy') assert str(err.value) == 'headers must be a dictionary' authenticator.set_headers({'dummy': 'headers'}) assert authenticator.token_manager.headers == {'dummy': 'headers'} with pytest.raises(TypeError) as err: authenticator.set_proxies('dummy') assert str(err.value) == 'proxies must be a dictionary' authenticator.set_proxies({'dummy': 'proxies'}) assert authenticator.token_manager.proxies == {'dummy': 'proxies'} def test_disable_ssl_verification(): authenticator = ContainerAuthenticator(iam_profile_name='iam-user-123', disable_ssl_verification=True) assert authenticator.token_manager.disable_ssl_verification is True authenticator.set_disable_ssl_verification(False) assert authenticator.token_manager.disable_ssl_verification is False def test_invalid_disable_ssl_verification_type(): with pytest.raises(TypeError) as err: authenticator = ContainerAuthenticator(iam_profile_name='iam-user-123', disable_ssl_verification='True') assert str(err.value) == 'disable_ssl_verification must be a bool' authenticator = ContainerAuthenticator(iam_profile_name='iam-user-123') assert authenticator.token_manager.disable_ssl_verification is False with pytest.raises(TypeError) as err: authenticator.set_disable_ssl_verification('True') assert str(err.value) == 'status must be a bool' def test_container_authenticator_with_scope(): authenticator = ContainerAuthenticator(iam_profile_name='iam-user-123', scope='scope1 scope2') assert authenticator is not None assert authenticator.token_manager.scope == 'scope1 scope2' def test_authenticator_validate_failed(): with pytest.raises(ValueError) as err: ContainerAuthenticator(None) assert str(err.value) == 'At least one of iam_profile_name or iam_profile_id must be specified.' with pytest.raises(ValueError) as err: ContainerAuthenticator(iam_profile_name='iam-user-123', client_id='my_client_id') assert str(err.value) == 'Both client_id and client_secret should be initialized.' with pytest.raises(ValueError) as err: ContainerAuthenticator(iam_profile_name='iam-user-123', client_secret='my_client_secret') assert str(err.value) == 'Both client_id and client_secret should be initialized.' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_container_token_manager.py0000664000372000037200000002733600000000000025310 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021, 2024 IBM 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. # pylint: disable=missing-docstring import json import os import time from urllib.parse import parse_qs import responses import pytest from ibm_cloud_sdk_core import ApiException, ContainerTokenManager from ibm_cloud_sdk_core.authenticators import ContainerAuthenticator # pylint: disable=line-too-long TEST_ACCESS_TOKEN_1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI' TEST_ACCESS_TOKEN_2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJ1c2VybmFtZSI6ImR1bW15Iiwicm9sZSI6IkFkbWluIiwicGVybWlzc2lvbnMiOlsiYWRtaW5pc3RyYXRvciIsIm1hbmFnZV9jYXRhbG9nIl0sInN1YiI6ImFkbWluIiwiaXNzIjoic3NzIiwiYXVkIjoic3NzIiwidWlkIjoic3NzIiwiaWF0IjozNjAwLCJleHAiOjE2MjgwMDcwODF9.zvUDpgqWIWs7S1CuKv40ERw1IZ5FqSFqQXsrwZJyfRM' TEST_REFRESH_TOKEN = 'Xj7Gle500MachEOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI' MOCK_IAM_PROFILE_NAME = 'iam-user-123' MOCK_CLIENT_ID = 'client-id-1' MOCK_CLIENT_SECRET = 'client-secret-1' EXPIRATION_WINDOW = 10 cr_token_file = os.path.join(os.path.dirname(__file__), '../resources/cr-token.txt') def _get_current_time() -> int: return int(time.time()) def mock_iam_response(func): """This is decorator function which extends `responses.activate`. This sets up all the mock response stuffs. """ def callback(request): assert request.headers['Accept'] == 'application/json' assert request.headers['Content-Type'] == 'application/x-www-form-urlencoded' payload = parse_qs(request.body) assert payload['cr_token'][0] == 'cr-token-1' assert payload['grant_type'][0] == 'urn:ibm:params:oauth:grant-type:cr-token' assert payload.get('profile_name', [None])[0] or payload.get('profile_id', [None])[0] status_code = 200 scope = payload.get('scope')[0] if payload.get('scope') else None if scope == 'send-second-token': access_token = TEST_ACCESS_TOKEN_2 elif scope == 'status-bad-request': access_token = None status_code = 400 elif scope == 'check-basic-auth': assert request.headers['Authorization'] == 'Basic Y2xpZW50LWlkLTE6Y2xpZW50LXNlY3JldC0x' access_token = TEST_ACCESS_TOKEN_1 else: access_token = TEST_ACCESS_TOKEN_1 response = json.dumps( { 'access_token': access_token, 'token_type': 'Bearer', 'expires_in': 3600, 'expiration': _get_current_time() + 3600, 'refresh_token': TEST_REFRESH_TOKEN, } ) return (status_code, {}, response) @responses.activate def wrapper(): response = responses.CallbackResponse( method=responses.POST, url='https://iam.cloud.ibm.com/identity/token', callback=callback, ) responses.add(response) func() return wrapper @mock_iam_response def test_request_token_auth_default(): iam_url = "https://iam.cloud.ibm.com/identity/token" token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, ) token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert json.loads(responses.calls[0].response.text)['access_token'] == TEST_ACCESS_TOKEN_1 @mock_iam_response def test_request_token_auth_in_ctor(): default_auth_header = 'Basic Yng6Yng=' token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, client_id='foo', client_secret='bar' ) token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Authorization'] != default_auth_header assert json.loads(responses.calls[0].response.text)['access_token'] == TEST_ACCESS_TOKEN_1 assert 'scope' not in responses.calls[0].response.request.body @mock_iam_response def test_request_token_auth_in_ctor_with_scope(): default_auth_header = 'Basic Yng6Yng=' token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, client_id='foo', client_secret='bar', scope='john snow', ) token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Authorization'] != default_auth_header assert json.loads(responses.calls[0].response.text)['access_token'] == TEST_ACCESS_TOKEN_1 assert 'scope=john+snow' in responses.calls[0].response.request.body def test_retrieve_cr_token_success(): token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, ) cr_token = token_manager.retrieve_cr_token() assert cr_token == 'cr-token-1' def test_retrieve_cr_token_fail(): token_manager = ContainerTokenManager( cr_token_filename='bogus-cr-token-file', ) with pytest.raises(Exception) as err: token_manager.retrieve_cr_token() assert ( str(err.value) == 'Unable to retrieve the CR token: Error reading CR token from file bogus-cr-token-file: [Errno 2] No such file or directory: \'bogus-cr-token-file\'' ) @mock_iam_response def test_get_token_success(): token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, ) access_token = token_manager.access_token assert access_token is None access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Verify that the token manager returns the cached value. # Before we call `get_token` again, set the expiration and refresh time # so that we do not fetch a new access token. # This is necessary because we are using a fixed JWT response. token_manager.expire_time = _get_current_time() + 1000 token_manager.refresh_time = _get_current_time() + 1000 token_manager.set_scope('send-second-token') access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Force expiration to get the second token. # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs) # because we want the access token to be considered as "expired" # when we reach the IAM-server reported expiration time minus 10 secs. token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_2 assert token_manager.access_token == TEST_ACCESS_TOKEN_2 @mock_iam_response def test_request_token_success(): token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, ) token_response = token_manager.request_token() assert token_response['access_token'] == TEST_ACCESS_TOKEN_1 @mock_iam_response def test_authenticate_success(): authenticator = ContainerAuthenticator(cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME) request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_1 # Verify that the token manager returns the cached value. # Before we call `get_token` again, set the expiration and refresh time # so that we do not fetch a new access token. # This is necessary because we are using a fixed JWT response. authenticator.token_manager.expire_time = _get_current_time() + 1000 authenticator.token_manager.refresh_time = _get_current_time() + 1000 authenticator.token_manager.set_scope('send-second-token') authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_1 # Force expiration to get the second token. # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs) # because we want the access token to be considered as "expired" # when we reach the IAM-server reported expiration time minus 10 secs. authenticator.token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_2 @mock_iam_response def test_authenticate_fail_no_cr_token(): authenticator = ContainerAuthenticator( cr_token_filename='bogus-cr-token-file', iam_profile_name=MOCK_IAM_PROFILE_NAME, url='https://bogus.iam.endpoint', ) request = {'headers': {}} with pytest.raises(Exception) as err: authenticator.authenticate(request) assert ( str(err.value) == 'Unable to retrieve the CR token: Error reading CR token from file bogus-cr-token-file: [Errno 2] No such file or directory: \'bogus-cr-token-file\'' ) @mock_iam_response def test_authenticate_fail_iam(): authenticator = ContainerAuthenticator( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, scope='status-bad-request' ) request = {'headers': {}} with pytest.raises(ApiException) as err: authenticator.authenticate(request) assert str(err.value) == 'Error: Bad Request, Status code: 400' @mock_iam_response def test_client_id_and_secret(): token_manager = ContainerTokenManager( cr_token_filename=cr_token_file, iam_profile_name=MOCK_IAM_PROFILE_NAME, ) token_manager.set_client_id_and_secret(MOCK_CLIENT_ID, MOCK_CLIENT_SECRET) token_manager.set_scope('check-basic-auth') access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 @mock_iam_response def test_setter_methods(): token_manager = ContainerTokenManager( cr_token_filename='bogus-cr-token-file', iam_profile_name=MOCK_IAM_PROFILE_NAME, ) assert token_manager.iam_profile_id is None assert token_manager.iam_profile_name == MOCK_IAM_PROFILE_NAME assert token_manager.cr_token_filename == 'bogus-cr-token-file' token_manager.set_iam_profile_id('iam-id-123') token_manager.set_iam_profile_name(None) token_manager.set_cr_token_filename(cr_token_file) assert token_manager.iam_profile_id == 'iam-id-123' assert token_manager.iam_profile_name is None assert token_manager.cr_token_filename == cr_token_file access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_cp4d_authenticator.py0000664000372000037200000001476300000000000024220 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import json import jwt import pytest import responses from ibm_cloud_sdk_core.authenticators import CloudPakForDataAuthenticator, Authenticator def test_cp4d_authenticator(): authenticator = CloudPakForDataAuthenticator('my_username', 'my_password', 'http://my_url') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CP4D assert authenticator.token_manager.url == 'http://my_url/v1/authorize' assert authenticator.token_manager.username == 'my_username' assert authenticator.token_manager.password == 'my_password' assert authenticator.token_manager.disable_ssl_verification is False assert authenticator.token_manager.headers == {'Content-Type': 'application/json'} assert authenticator.token_manager.proxies is None authenticator.set_disable_ssl_verification(True) assert authenticator.token_manager.disable_ssl_verification is True with pytest.raises(TypeError) as err: authenticator.set_headers('dummy') assert str(err.value) == 'headers must be a dictionary' authenticator.set_headers({'dummy': 'headers'}) assert authenticator.token_manager.headers == {'dummy': 'headers'} with pytest.raises(TypeError) as err: authenticator.set_proxies('dummy') assert str(err.value) == 'proxies must be a dictionary' authenticator.set_proxies({'dummy': 'proxies'}) assert authenticator.token_manager.proxies == {'dummy': 'proxies'} def test_disable_ssl_verification(): authenticator = CloudPakForDataAuthenticator( 'my_username', 'my_password', 'http://my_url', disable_ssl_verification=True ) assert authenticator.token_manager.disable_ssl_verification is True authenticator.set_disable_ssl_verification(False) assert authenticator.token_manager.disable_ssl_verification is False def test_invalid_disable_ssl_verification_type(): with pytest.raises(TypeError) as err: authenticator = CloudPakForDataAuthenticator( 'my_username', 'my_password', 'http://my_url', disable_ssl_verification='True' ) assert str(err.value) == 'disable_ssl_verification must be a bool' authenticator = CloudPakForDataAuthenticator('my_username', 'my_password', 'http://my_url') assert authenticator.token_manager.disable_ssl_verification is False with pytest.raises(TypeError) as err: authenticator.set_disable_ssl_verification('True') assert str(err.value) == 'status must be a bool' def test_cp4d_authenticator_validate_failed(): with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('my_username', None, 'my_url') assert str(err.value) == 'Exactly one of `apikey` or `password` must be specified.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator(username='my_username', url='my_url') assert str(err.value) == 'Exactly one of `apikey` or `password` must be specified.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('my_username', None, 'my_url', apikey=None) assert str(err.value) == 'Exactly one of `apikey` or `password` must be specified.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator(None, 'my_password', 'my_url') assert str(err.value) == 'The username shouldn\'t be None.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator(password='my_password', url='my_url') assert str(err.value) == 'The username shouldn\'t be None.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('my_username', 'my_password', None) assert str(err.value) == 'The url shouldn\'t be None.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator(username='my_username', password='my_password') assert str(err.value) == 'The url shouldn\'t be None.' with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('{my_username}', 'my_password', 'my_url') assert ( str(err.value) == 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('my_username', '{my_password}', 'my_url') assert ( str(err.value) == 'The username and password shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) with pytest.raises(ValueError) as err: CloudPakForDataAuthenticator('my_username', 'my_password', '{my_url}') assert ( str(err.value) == 'The url shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) @responses.activate def test_get_token(): url = "https://test" access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": 1559324664, "exp": 1559324664, } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) response = { "token": access_token, "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ", } responses.add(responses.POST, url + '/v1/authorize', body=json.dumps(response), status=200) auth_headers = {'Host': 'cp4d.cloud.ibm.com:443'} authenticator = CloudPakForDataAuthenticator( 'my_username', 'my_password', url + '/v1/authorize', headers=auth_headers ) # Simulate an SDK API request that needs to be authenticated. request = {'headers': {}} # Trigger the "get token" processing to obtain the access token and add to the "SDK request". authenticator.authenticate(request) # Verify that the "authenticate()" method added the Authorization header assert request['headers']['Authorization'] is not None # Verify that the "get token" call contained the Host header. assert responses.calls[0].request.headers.get('Host') == 'cp4d.cloud.ibm.com:443' # Ensure '/v1/authorize' is added to the url if omitted authenticator = CloudPakForDataAuthenticator('my_username', 'my_password', url) request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] is not None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_cp4d_token_manager.py0000664000372000037200000000317000000000000024146 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import json import time import jwt import responses from ibm_cloud_sdk_core import CP4DTokenManager @responses.activate def test_request_token(): url = "https://test" now = time.time() access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": now, "exp": now + 3600, } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) response = { "token": access_token, } responses.add(responses.POST, url + '/v1/authorize', body=json.dumps(response), status=200) token_manager = CP4DTokenManager("username", "password", url) token_manager.set_disable_ssl_verification(True) token = token_manager.get_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == url + '/v1/authorize' assert token == access_token token_manager = CP4DTokenManager("username", "password", url + '/v1/authorize') token = token_manager.get_token() assert len(responses.calls) == 2 assert responses.calls[1].request.url == url + '/v1/authorize' assert token == access_token token_manager = CP4DTokenManager(username="username", apikey="fake_api_key", url=url + '/v1/authorize') token = token_manager.get_token() assert len(responses.calls) == 3 assert responses.calls[2].request.url == url + '/v1/authorize' assert token == access_token ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_detailed_response.py0000664000372000037200000000431700000000000024117 0ustar00travistravis00000000000000# coding=utf-8 # pylint: disable=missing-docstring import json import responses import requests from ibm_cloud_sdk_core import DetailedResponse def clean(val): """Eliminate all whitespace and convert single to double quotes""" return val.translate(str.maketrans('', '', ' \n\t\r')).replace("'", "\"") @responses.activate def test_detailed_response_dict(): responses.add( responses.GET, 'https://test.com', status=200, body=json.dumps({'foobar': 'baz'}), content_type='application/json', ) mock_response = requests.get('https://test.com', timeout=None) detailed_response = DetailedResponse( response=mock_response.json(), headers=mock_response.headers, status_code=mock_response.status_code ) assert detailed_response is not None assert detailed_response.get_result() == {'foobar': 'baz'} assert detailed_response.get_headers() == {'Content-Type': 'application/json'} assert detailed_response.get_status_code() == 200 response_str = clean(str(detailed_response)) assert clean(str(detailed_response.get_result())) in response_str # assert clean(str(detailed_response.get_headers())) in response_str assert clean(str(detailed_response.get_status_code())) in response_str @responses.activate def test_detailed_response_list(): responses.add( responses.GET, 'https://test.com', status=200, body=json.dumps(['foobar', 'baz']), content_type='application/json', ) mock_response = requests.get('https://test.com', timeout=None) detailed_response = DetailedResponse( response=mock_response.json(), headers=mock_response.headers, status_code=mock_response.status_code ) assert detailed_response is not None assert detailed_response.get_result() == ['foobar', 'baz'] assert detailed_response.get_headers() == {'Content-Type': 'application/json'} assert detailed_response.get_status_code() == 200 response_str = clean(str(detailed_response)) assert clean(str(detailed_response.get_result())) in response_str # assert clean(str(detailed_response.get_headers())) in response_str assert clean(str(detailed_response.get_status_code())) in response_str ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_iam_authenticator.py0000664000372000037200000001300000000000000024113 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import json import jwt import pytest import responses from ibm_cloud_sdk_core.authenticators import IAMAuthenticator, Authenticator def test_iam_authenticator(): authenticator = IAMAuthenticator(apikey='my_apikey') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.url == 'https://iam.cloud.ibm.com' assert authenticator.token_manager.client_id is None assert authenticator.token_manager.client_secret is None assert authenticator.token_manager.disable_ssl_verification is False assert authenticator.token_manager.headers is None assert authenticator.token_manager.proxies is None assert authenticator.token_manager.apikey == 'my_apikey' assert authenticator.token_manager.scope is None authenticator.set_client_id_and_secret('tom', 'jerry') assert authenticator.token_manager.client_id == 'tom' assert authenticator.token_manager.client_secret == 'jerry' authenticator.set_scope('scope1 scope2 scope3') assert authenticator.token_manager.scope == 'scope1 scope2 scope3' with pytest.raises(TypeError) as err: authenticator.set_headers('dummy') assert str(err.value) == 'headers must be a dictionary' authenticator.set_headers({'dummy': 'headers'}) assert authenticator.token_manager.headers == {'dummy': 'headers'} with pytest.raises(TypeError) as err: authenticator.set_proxies('dummy') assert str(err.value) == 'proxies must be a dictionary' authenticator.set_proxies({'dummy': 'proxies'}) assert authenticator.token_manager.proxies == {'dummy': 'proxies'} authenticator.set_disable_ssl_verification(True) assert authenticator.token_manager.disable_ssl_verification def test_disable_ssl_verification(): authenticator = IAMAuthenticator(apikey='my_apikey', disable_ssl_verification=True) assert authenticator.token_manager.disable_ssl_verification is True authenticator.set_disable_ssl_verification(False) assert authenticator.token_manager.disable_ssl_verification is False def test_invalid_disable_ssl_verification_type(): with pytest.raises(TypeError) as err: authenticator = IAMAuthenticator(apikey='my_apikey', disable_ssl_verification='True') assert str(err.value) == 'disable_ssl_verification must be a bool' authenticator = IAMAuthenticator(apikey='my_apikey') assert authenticator.token_manager.disable_ssl_verification is False with pytest.raises(TypeError) as err: authenticator.set_disable_ssl_verification('True') assert str(err.value) == 'status must be a bool' def test_iam_authenticator_with_scope(): authenticator = IAMAuthenticator(apikey='my_apikey', scope='scope1 scope2') assert authenticator is not None assert authenticator.token_manager.scope == 'scope1 scope2' def test_iam_authenticator_validate_failed(): with pytest.raises(ValueError) as err: IAMAuthenticator(None) assert str(err.value) == 'The apikey shouldn\'t be None.' with pytest.raises(ValueError) as err: IAMAuthenticator('{apikey}') assert ( str(err.value) == 'The apikey shouldn\'t start or end with curly brackets or quotes. ' 'Please remove any surrounding {, }, or \" characters.' ) with pytest.raises(ValueError) as err: IAMAuthenticator('my_apikey', client_id='my_client_id') assert str(err.value) == 'Both client_id and client_secret should be initialized.' with pytest.raises(ValueError) as err: IAMAuthenticator('my_apikey', client_secret='my_client_secret') assert str(err.value) == 'Both client_id and client_secret should be initialized.' @responses.activate def test_get_token(): url = "https://iam.cloud.ibm.com/identity/token" access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": 1559324664, "exp": 1559324664, } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) response = { "access_token": access_token, "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ", } responses.add(responses.POST, url=url, body=json.dumps(response), status=200) auth_headers = {'Host': 'iam.cloud.ibm.com:443'} authenticator = IAMAuthenticator('my_apikey', headers=auth_headers) # Simulate an SDK API request that needs to be authenticated. request = {'headers': {}} # Trigger the "get token" processing to obtain the access token and add to the "SDK request". authenticator.authenticate(request) # Verify that the "authenticate()" method added the Authorization header assert request['headers']['Authorization'] is not None # Verify that the "get token" call contained the Host header. assert responses.calls[0].request.headers.get('Host') == 'iam.cloud.ibm.com:443' def test_multiple_iam_authenticators(): authenticator_1 = IAMAuthenticator('my_apikey') assert authenticator_1.token_manager.request_payload['apikey'] == 'my_apikey' authenticator_2 = IAMAuthenticator('my_other_apikey') assert authenticator_2.token_manager.request_payload['apikey'] == 'my_other_apikey' assert authenticator_1.token_manager.request_payload['apikey'] == 'my_apikey' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_iam_token_manager.py0000664000372000037200000004026200000000000024065 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021, 2024 IBM 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. # pylint: disable=missing-docstring import os import time import jwt import pytest import responses from ibm_cloud_sdk_core import IAMTokenManager, ApiException, get_authenticator_from_environment # pylint: disable=line-too-long TEST_ACCESS_TOKEN_1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI' TEST_ACCESS_TOKEN_2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJ1c2VybmFtZSI6ImR1bW15Iiwicm9sZSI6IkFkbWluIiwicGVybWlzc2lvbnMiOlsiYWRtaW5pc3RyYXRvciIsIm1hbmFnZV9jYXRhbG9nIl0sInN1YiI6ImFkbWluIiwiaXNzIjoic3NzIiwiYXVkIjoic3NzIiwidWlkIjoic3NzIiwiaWF0IjozNjAwLCJleHAiOjE2MjgwMDcwODF9.zvUDpgqWIWs7S1CuKv40ERw1IZ5FqSFqQXsrwZJyfRM' TEST_REFRESH_TOKEN = 'Xj7Gle500MachEOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI' EXPIRATION_WINDOW = 10 def _get_current_time() -> int: return int(time.time()) def get_access_token() -> str: access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": 3600, "exp": int(time.time()), } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) return access_token @responses.activate def test_request_token_auth_default(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("apikey") token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response @responses.activate def test_request_token_auth_in_ctor(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" default_auth_header = 'Basic Yng6Yng=' responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("apikey", url=iam_url, client_id='foo', client_secret='bar') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers['Authorization'] != default_auth_header assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_ctor_with_scope(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" default_auth_header = 'Basic Yng6Yng=' responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("apikey", url=iam_url, client_id='foo', client_secret='bar', scope='john snow') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers['Authorization'] != default_auth_header assert responses.calls[0].response.text == response assert 'scope=john+snow' in responses.calls[0].response.request.body @responses.activate def test_request_token_unsuccessful(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "context": { "requestId": "38a0e9c226d94764820d92aa623eb0f6", "requestType": "incoming.Identity_Token", "userAgent": "ibm-python-sdk-core-1.0.0", "url": "https://iam.cloud.ibm.com", "instanceId": "iamid-4.5-6788-90b137c-75f48695b5-kl4wx", "threadId": "169de5", "host": "iamid-4.5-6788-90b137c-75f48695b5-kl4wx", "startTime": "29.10.2019 12:31:00:300 GMT", "endTime": "29.10.2019 12:31:00:381 GMT", "elapsedTime": "81", "locale": "en_US", "clusterName": "iam-id-prdal12-8brn" }, "errorCode": "BXNIM0415E", "errorMessage": "Provided API key could not be found" } """ responses.add(responses.POST, url=iam_url, body=response, status=400) token_manager = IAMTokenManager("apikey") with pytest.raises(ApiException): token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].response.text == response @responses.activate def test_request_token_auth_in_ctor_client_id_only(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey", url=iam_url, client_id='foo') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_ctor_secret_only(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey", url=iam_url, client_id=None, client_secret='bar') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_setter(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" default_auth_header = 'Basic Yng6Yng=' responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey") token_manager.set_client_id_and_secret('foo', 'bar') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers['Authorization'] != default_auth_header assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_setter_client_id_only(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey") token_manager.set_client_id_and_secret('foo', None) token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_setter_secret_only(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey") token_manager.set_client_id_and_secret(None, 'bar') token_manager.set_headers({'user': 'header'}) token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response assert 'scope' not in responses.calls[0].response.request.body @responses.activate def test_request_token_auth_in_setter_scope(): iam_url = "https://iam.cloud.ibm.com/identity/token" response = """{ "access_token": "oAeisG8yqPY7sFR_x66Z15", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey") token_manager.set_client_id_and_secret(None, 'bar') token_manager.set_headers({'user': 'header'}) token_manager.set_scope('john snow') token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == iam_url assert responses.calls[0].request.headers.get('Authorization') is None assert responses.calls[0].response.text == response assert 'scope=john+snow' in responses.calls[0].response.request.body @responses.activate def test_get_token_success(): iam_url = "https://iam.cloud.ibm.com/identity/token" # Create two mock responses with different access tokens. response1 = """{ "access_token": "%s", "token_type": "Bearer", "expires_in": 3600, "expiration": 1600003600, "refresh_token": "jy4gl91BQ" }""" % ( TEST_ACCESS_TOKEN_1 ) response2 = """{ "access_token": "%s", "token_type": "Bearer", "expires_in": 3600, "expiration": 1600007200, "refresh_token": "jy4gl91BQ" }""" % ( TEST_ACCESS_TOKEN_2 ) token_manager = IAMTokenManager("iam_apikey") access_token = token_manager.access_token assert access_token is None responses.add(responses.POST, url=iam_url, body=response1, status=200) access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Verify that the token manager returns the cached value. # Before we call `get_token` again, set the expiration and refresh time # so that we do not fetch a new access token. # This is necessary because we are using a fixed JWT response. token_manager.expire_time = _get_current_time() + 1000 token_manager.refresh_time = _get_current_time() + 1000 access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Force expiration to get the second token. # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs) # because we want the access token to be considered as "expired" # when we reach the IAM-server reported expiration time minus 10 secs. responses.add(responses.POST, url=iam_url, body=response2, status=200) token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW token_manager.refresh_time = _get_current_time() + 1000 access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_2 assert token_manager.access_token == TEST_ACCESS_TOKEN_2 @responses.activate def test_get_refresh_token(): iam_url = "https://iam.cloud.ibm.com/identity/token" access_token_str = get_access_token() response = """{ "access_token": "%s", "token_type": "Bearer", "expires_in": 3600, "expiration": 1524167011, "refresh_token": "jy4gl91BQ" }""" % ( access_token_str ) responses.add(responses.POST, url=iam_url, body=response, status=200) token_manager = IAMTokenManager("iam_apikey") token_manager.get_token() assert len(responses.calls) == 2 assert token_manager.refresh_token == "jy4gl91BQ" # # In order to run the following integration test with a live IAM server: # # 1. Create file "iamtest.env" in the project root. # It should look like this: # IAMTEST1_AUTH_URL= e.g. https://iam.cloud.ibm.com # IAMTEST1_AUTH_TYPE=iam # IAMTEST1_APIKEY= # IAMTEST2_AUTH_URL= e.g. https://iam.test.cloud.ibm.com # IAMTEST2_AUTH_TYPE=iam # IAMTEST2_APIKEY= # IAMTEST2_CLIENT_ID= # IAMTEST2_CLIENT_SECRET= # # 2. Comment out the "@pytest.mark.skip" decorator below. # # 3. Run this command: # python3 -m pytest -s test -k "test_iam_live_token_server" # (or just run tests like normal and this test function will be invoked) # @pytest.mark.skip(reason="avoid integration test in automated builds") def test_iam_live_token_server(): # Get two iam authenticators from the environment. # "iamtest1" uses the production IAM token server # "iamtest2" uses the staging IAM token server os.environ['IBM_CREDENTIALS_FILE'] = "iamtest.env" # Test "iamtest1" service auth1 = get_authenticator_from_environment("iamtest1") assert auth1 is not None assert auth1.token_manager is not None assert auth1.token_manager.url is not None request = {'method': "GET"} request["url"] = "" request["headers"] = {} assert auth1.token_manager.refresh_token is None auth1.authenticate(request) assert request.get("headers") is not None assert request["headers"].get("Authorization") is not None assert "Bearer " in request["headers"].get("Authorization") # Test "iamtest2" service auth2 = get_authenticator_from_environment("iamtest2") assert auth2 is not None assert auth2.token_manager is not None assert auth2.token_manager.url is not None request = {'method': "GET"} request["url"] = "" request["headers"] = {} assert auth2.token_manager.refresh_token is None auth2.authenticate(request) assert auth2.token_manager.refresh_token is not None assert request.get("headers") is not None assert request["headers"].get("Authorization") is not None assert "Bearer " in request["headers"].get("Authorization") # print('Refresh token: ', auth2.token_manager.refresh_token) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_jwt_token_manager.py0000664000372000037200000000700500000000000024121 0ustar00travistravis00000000000000# pylint: disable=missing-docstring,protected-access,abstract-class-instantiated import time import threading from typing import Optional import jwt import pytest from ibm_cloud_sdk_core import JWTTokenManager, DetailedResponse class JWTTokenManagerMockImpl(JWTTokenManager): def __init__(self, url: Optional[str] = None, access_token: Optional[str] = None) -> None: self.url = url self.access_token = access_token self.request_count = 0 # just for tests to see how many times request was called super().__init__(url, disable_ssl_verification=access_token, token_name='access_token') def request_token(self) -> DetailedResponse: self.request_count += 1 current_time = int(time.time()) token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": current_time, "exp": current_time + 3600, } access_token = jwt.encode( token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) response = { "access_token": access_token, "token_type": "Bearer", "expires_in": 3600, "expiration": current_time + 3600, "refresh_token": "jy4gl91BQ", "from_token_manager": True, } time.sleep(0.5) return response def _get_current_time() -> int: return int(time.time()) def test_get_token(): url = "https://iam.cloud.ibm.com/identity/token" token_manager = JWTTokenManagerMockImpl(url) old_token = token_manager.get_token() assert token_manager.token_info.get('expires_in') == 3600 assert token_manager._is_token_expired() is False token_manager.token_info = { "access_token": "old_dummy", "token_type": "Bearer", "expires_in": 3600, "expiration": time.time(), "refresh_token": "jy4gl91BQ", } token = token_manager.get_token() assert token == old_token # expired token: token_manager.expire_time = _get_current_time() - 300 token = token_manager.get_token() assert token != "old_dummy" assert token_manager.request_count == 2 def test_paced_get_token(): url = "https://iam.cloud.ibm.com/identity/token" token_manager = JWTTokenManagerMockImpl(url) threads = [] for _ in range(10): thread = threading.Thread(target=token_manager.get_token) thread.start() threads.append(thread) for thread in threads: thread.join() assert token_manager.request_count == 1 def test_is_token_expired(): token_manager = JWTTokenManagerMockImpl(None, access_token=None) assert token_manager._is_token_expired() is True token_manager.expire_time = _get_current_time() + 3600 assert token_manager._is_token_expired() is False token_manager.expire_time = _get_current_time() - 3600 assert token_manager._is_token_expired() def test_abstract_class_instantiation(): with pytest.raises(TypeError) as err: JWTTokenManager(None) assert str(err.value).startswith("Can't instantiate abstract class JWTTokenManager with abstract") def test_disable_ssl_verification(): token_manager = JWTTokenManagerMockImpl('https://iam.cloud.ibm.com/identity/token') token_manager.set_disable_ssl_verification(True) assert token_manager.disable_ssl_verification is True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_mcsp_authenticator.py0000664000372000037200000001574700000000000024333 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import json import time import jwt import pytest import responses from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator, Authenticator OPERATION_PATH = '/siusermgr/api/1.0/apikeys/token' MOCK_URL = 'https://mcsp.ibm.com' def test_mcsp_authenticator(): authenticator = MCSPAuthenticator('my-api-key', MOCK_URL) assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_MCSP assert authenticator.token_manager.url == MOCK_URL assert authenticator.token_manager.disable_ssl_verification is False assert authenticator.token_manager.headers == { 'Accept': 'application/json', 'Content-Type': 'application/json', } assert authenticator.token_manager.proxies is None authenticator.set_disable_ssl_verification(True) assert authenticator.token_manager.disable_ssl_verification is True with pytest.raises(TypeError) as err: authenticator.set_headers('dummy') assert str(err.value) == 'headers must be a dictionary' authenticator.set_headers({'dummy': 'headers'}) assert authenticator.token_manager.headers == {'dummy': 'headers'} with pytest.raises(TypeError) as err: authenticator.set_proxies('dummy') assert str(err.value) == 'proxies must be a dictionary' authenticator.set_proxies({'dummy': 'proxies'}) assert authenticator.token_manager.proxies == {'dummy': 'proxies'} def test_disable_ssl_verification(): authenticator = MCSPAuthenticator('my-api-key', MOCK_URL, disable_ssl_verification=True) assert authenticator.token_manager.disable_ssl_verification is True authenticator.set_disable_ssl_verification(False) assert authenticator.token_manager.disable_ssl_verification is False def test_invalid_disable_ssl_verification_type(): with pytest.raises(TypeError) as err: authenticator = MCSPAuthenticator('my-api-key', MOCK_URL, disable_ssl_verification='True') assert str(err.value) == 'disable_ssl_verification must be a bool' authenticator = MCSPAuthenticator('my-api-key', MOCK_URL) assert authenticator.token_manager.disable_ssl_verification is False with pytest.raises(TypeError) as err: authenticator.set_disable_ssl_verification('True') assert str(err.value) == 'status must be a bool' def test_mcsp_authenticator_validate_failed(): with pytest.raises(ValueError) as err: MCSPAuthenticator(apikey=None, url=MOCK_URL) assert str(err.value) == 'The apikey shouldn\'t be None.' with pytest.raises(ValueError) as err: MCSPAuthenticator(apikey='my-api-key', url=None) assert str(err.value) == 'The url shouldn\'t be None.' # utility function to construct a mock token server response containing an access token. def get_mock_token_response(issued_at, time_to_live) -> str: access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": issued_at, "exp": issued_at + time_to_live, } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) token_server_response = {"token": access_token, "token_type": "jwt", "expires_in": time_to_live} # For convenience, return both the server response and the access_token. return (json.dumps(token_server_response), access_token) @responses.activate def test_get_token(): (response, access_token) = get_mock_token_response(time.time(), 7200) responses.add(responses.POST, MOCK_URL + OPERATION_PATH, body=response, status=200) auth_headers = {'Host': 'mcsp.cloud.ibm.com:443'} authenticator = MCSPAuthenticator(apikey='my-api-key', url=MOCK_URL, headers=auth_headers) # Authenticate the request and verify the Authorization header. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token # Verify that the "get token" request contained the Host header. assert responses.calls[0].request.headers.get('Host') == 'mcsp.cloud.ibm.com:443' @responses.activate def test_get_token_cached(): (response, access_token) = get_mock_token_response(time.time(), 7200) responses.add(responses.POST, MOCK_URL + OPERATION_PATH, body=response, status=200) authenticator = MCSPAuthenticator(apikey='my-api-key', url=MOCK_URL) # Authenticate the request and verify the Authorization header. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token # Authenticate a second request and verify that we used the same access token. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token @responses.activate def test_get_token_background_refresh(): t1 = time.time() t2 = t1 + 7200 # Setup the first token response. (response1, access_token1) = get_mock_token_response(t1, 7200) responses.add(responses.POST, MOCK_URL + OPERATION_PATH, body=response1, status=200) # Setup the second token response. (response2, access_token2) = get_mock_token_response(t2, 7200) responses.add(responses.POST, MOCK_URL + OPERATION_PATH, body=response2, status=200) authenticator = MCSPAuthenticator(apikey="my-api-key", url=MOCK_URL) # Authenticate the request and verify that the first access_token is used. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token1 # Now put the token manager in the refresh window to trigger a background refresh scenario. authenticator.token_manager.refresh_time = t1 - 1 # Authenticate a second request and verify that the correct access token is used. # Note: Ideally, the token manager would trigger the refresh in a separate thread # and it "should" return the first access token for this second authentication request # while the token manager is obtaining a new access token. # Unfortunately, the TokenManager class method does the refresh request synchronously, # so we get back the second access token here instead. # If we "fix" the TokenManager class to refresh asynchronously, we'll need to # change this test case to expect the first access token here. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token2 # Wait for the background refresh to finish. # No need to wait due to the synchronous logic in the TokenManager class mentioned above. # time.sleep(2) # Authenticate another request and verify that the second access token is used again. request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] == 'Bearer ' + access_token2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_mcsp_token_manager.py0000664000372000037200000000421500000000000024257 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import json import time import jwt import pytest import responses from ibm_cloud_sdk_core import MCSPTokenManager, ApiException OPERATION_PATH = '/siusermgr/api/1.0/apikeys/token' MOCK_URL = 'https://mcsp.ibm.com' # utility function to construct a mock token server response containing an access token. def get_mock_token_response(issued_at, time_to_live) -> str: access_token_layout = { "username": "dummy", "role": "Admin", "permissions": ["administrator", "manage_catalog"], "sub": "admin", "iss": "sss", "aud": "sss", "uid": "sss", "iat": issued_at, "exp": issued_at + time_to_live, } access_token = jwt.encode( access_token_layout, 'secret', algorithm='HS256', headers={'kid': '230498151c214b788dd97f22b85410a5'} ) token_server_response = {"token": access_token, "token_type": "jwt", "expires_in": time_to_live} # For convenience, return both the server response and the access_token. return (json.dumps(token_server_response), access_token) @responses.activate def test_request_token(): (response, access_token) = get_mock_token_response(time.time(), 30) responses.add(responses.POST, MOCK_URL + OPERATION_PATH, body=response, status=200) token_manager = MCSPTokenManager(apikey="my-api-key", url=MOCK_URL, disable_ssl_verification=True) token = token_manager.get_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == MOCK_URL + OPERATION_PATH assert token == access_token @responses.activate def test_request_token_unsuccessful(): response = """{ "errorCode": "BXNIM0415E", "errorMessage": "Provided API key could not be found" } """ responses.add(responses.POST, url=MOCK_URL + OPERATION_PATH, body=response, status=400) token_manager = MCSPTokenManager(apikey="bad-api-key", url=MOCK_URL) with pytest.raises(ApiException): token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.url == MOCK_URL + OPERATION_PATH assert responses.calls[0].response.text == response ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_no_auth_authenticator.py0000664000372000037200000000070000000000000025005 0ustar00travistravis00000000000000# pylint: disable=missing-docstring from ibm_cloud_sdk_core.authenticators import NoAuthAuthenticator, Authenticator def test_no_auth_authenticator(): authenticator = NoAuthAuthenticator() assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_NOAUTH authenticator.validate() request = {'headers': {}} authenticator.authenticate(request) assert not request['headers'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_token_manager.py0000664000372000037200000000557700000000000023251 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2020 IBM 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. # pylint: disable=missing-docstring,protected-access,abstract-class-instantiated from types import SimpleNamespace from unittest import mock import pytest from ibm_cloud_sdk_core import ApiException from ibm_cloud_sdk_core.token_managers.token_manager import TokenManager class MockTokenManager(TokenManager): def request_token(self) -> None: response = self._request(method='GET', url=self.url) return response def _save_token_info(self, token_response: dict) -> None: pass def test_abstract_class_instantiation(): with pytest.raises(TypeError) as err: TokenManager(None) assert ( str(err.value) == "Can't instantiate abstract class " "TokenManager with abstract methods " "_save_token_info, " "request_token" ) def requests_request_spy(*args, **kwargs): return SimpleNamespace(status_code=200, request_args=args, request_kwargs=kwargs) @mock.patch('requests.request', side_effect=requests_request_spy) def test_request_passes_disable_ssl_verification(request): # pylint: disable=unused-argument mock_token_manager = MockTokenManager(url="https://example.com", disable_ssl_verification=True) assert mock_token_manager.request_token().request_kwargs['verify'] is False def requests_request_error_mock(*args, **kwargs): # pylint: disable=unused-argument return SimpleNamespace(status_code=300, headers={}, text="") @mock.patch('requests.request', side_effect=requests_request_error_mock) def test_request_raises_for_non_2xx(request): # pylint: disable=unused-argument mock_token_manager = MockTokenManager(url="https://example.com", disable_ssl_verification=True) with pytest.raises(ApiException): mock_token_manager.request_token() def test_set_disable_ssl_verification_success(): token_manager = MockTokenManager(None) assert token_manager.disable_ssl_verification is False token_manager.set_disable_ssl_verification(True) assert token_manager.disable_ssl_verification is True def test_set_disable_ssl_verification_fail(): token_manager = MockTokenManager(None) with pytest.raises(TypeError) as err: token_manager.set_disable_ssl_verification('True') assert str(err.value) == 'status must be a bool' assert token_manager.disable_ssl_verification is False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_utils.py0000664000372000037200000007177600000000000021603 0ustar00travistravis00000000000000# pylint: disable=missing-docstring # coding: utf-8 # Copyright 2019, 2021 IBM All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import os from typing import Optional import pytest from ibm_cloud_sdk_core import string_to_datetime, datetime_to_string, get_authenticator_from_environment from ibm_cloud_sdk_core import string_to_datetime_list, datetime_to_string_list from ibm_cloud_sdk_core import string_to_date, date_to_string from ibm_cloud_sdk_core import convert_model, convert_list from ibm_cloud_sdk_core import get_query_param from ibm_cloud_sdk_core import read_external_sources from ibm_cloud_sdk_core.authenticators import Authenticator, BasicAuthenticator, IAMAuthenticator from ibm_cloud_sdk_core.utils import strip_extra_slashes, is_json_mimetype def datetime_test(datestr: str, expected: str): dt_value = string_to_datetime(datestr) assert dt_value is not None actual = datetime_to_string(dt_value) assert actual == expected def test_datetime(): # RFC 3339 with various flavors of tz-offset datetime_test('2016-06-20T04:25:16.218Z', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16.218+0000', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16.218+00', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16.218-0000', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16.218-00', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T00:25:16.218-0400', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T00:25:16.218-04', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T07:25:16.218+0300', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T07:25:16.218+03', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16Z', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T04:25:16+0000', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T04:25:16-0000', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T01:25:16-0300', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T01:25:16-03:00', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T08:55:16+04:30', '2016-06-20T04:25:16Z') datetime_test('2016-06-20T16:25:16+12:00', '2016-06-20T04:25:16Z') # RFC 3339 with nanoseconds for the Catalog-Managements of the world. datetime_test('2020-03-12T10:52:12.866305005-04:00', '2020-03-12T14:52:12.866305Z') datetime_test('2020-03-12T10:52:12.866305005Z', '2020-03-12T10:52:12.866305Z') datetime_test('2020-03-12T10:52:12.866305005+02:30', '2020-03-12T08:22:12.866305Z') datetime_test('2020-03-12T10:52:12.866305Z', '2020-03-12T10:52:12.866305Z') # UTC datetime with no TZ. datetime_test('2016-06-20T04:25:16.218', '2016-06-20T04:25:16.218000Z') datetime_test('2016-06-20T04:25:16', '2016-06-20T04:25:16Z') # Dialog datetime. datetime_test('2016-06-20 04:25:16', '2016-06-20T04:25:16Z') # IAM Identity Service. datetime_test('2020-11-10T12:28+0000', '2020-11-10T12:28:00Z') datetime_test('2020-11-10T07:28-0500', '2020-11-10T12:28:00Z') datetime_test('2020-11-10T12:28Z', '2020-11-10T12:28:00Z') def test_string_to_datetime(): # If the specified string does not include a timezone, it is assumed to be UTC date = string_to_datetime('2017-03-06 16:00:04.159338') assert date.day == 6 assert date.hour == 16 assert date.tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) # Test date string with TZ specified as '+xxxx' date = string_to_datetime('2017-03-06 16:00:04.159338+0600') assert date.day == 6 assert date.hour == 16 assert date.tzinfo.utcoffset(None).total_seconds() == 6 * 60 * 60 # Test date string with TZ specified as 'Z' date = string_to_datetime('2017-03-06 16:00:04.159338Z') assert date.day == 6 assert date.hour == 16 assert date.tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) def test_datetime_to_string(): # If specified date is None, return None assert datetime_to_string(None) is None # If the specified date is "naive", it is interpreted as a UTC date date = datetime.datetime(2017, 3, 6, 16, 0, 4, 159338) res = datetime_to_string(date) assert res == '2017-03-06T16:00:04.159338Z' # Test date with UTC timezone date = datetime.datetime(2017, 3, 6, 16, 0, 4, 159338, datetime.timezone.utc) res = datetime_to_string(date) assert res == '2017-03-06T16:00:04.159338Z' # Test date with non-UTC timezone tzn = datetime.timezone(datetime.timedelta(hours=-6)) date = datetime.datetime(2017, 3, 6, 10, 0, 4, 159338, tzn) res = datetime_to_string(date) assert res == '2017-03-06T16:00:04.159338Z' def test_string_to_datetime_list(): # Assert ValueError is raised for invalid argument type with pytest.raises(ValueError): string_to_datetime_list(None) # If the specified string does not include a timezone, it is assumed to be UTC date_list = string_to_datetime_list(['2017-03-06 16:00:04.159338']) assert date_list[0].day == 6 assert date_list[0].hour == 16 assert date_list[0].tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) # Test date string with TZ specified as '+xxxx' date_list = string_to_datetime_list(['2017-03-06 16:00:04.159338+0600']) assert date_list[0].day == 6 assert date_list[0].hour == 16 assert date_list[0].tzinfo.utcoffset(None).total_seconds() == 6 * 60 * 60 # Test date string with TZ specified as 'Z' date_list = string_to_datetime_list(['2017-03-06 16:00:04.159338Z']) assert date_list[0].day == 6 assert date_list[0].hour == 16 assert date_list[0].tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) # Test multiple datetimes in a list date_list = string_to_datetime_list(['2017-03-06 16:00:04.159338', '2017-03-07 17:00:04.159338']) assert date_list[0].day == 6 assert date_list[0].hour == 16 assert date_list[0].tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) assert date_list[1].day == 7 assert date_list[1].hour == 17 assert date_list[1].tzinfo.utcoffset(None) == datetime.timezone.utc.utcoffset(None) def test_datetime_to_string_list(): # Assert ValueError is raised for invalid argument type with pytest.raises(ValueError): datetime_to_string_list(None) # If specified datetime list item is None, return list of None assert datetime_to_string_list([None]) == [None] # If specified datetime list is empty, return empty list assert not datetime_to_string_list([]) # If the specified date list item is "naive", it is interpreted as a UTC date date_list = [datetime.datetime(2017, 3, 6, 16, 0, 4, 159338)] res = datetime_to_string_list(date_list) assert res == ['2017-03-06T16:00:04.159338Z'] # Test date list item with UTC timezone date_list = [datetime.datetime(2017, 3, 6, 16, 0, 4, 159338, datetime.timezone.utc)] res = datetime_to_string_list(date_list) assert res == ['2017-03-06T16:00:04.159338Z'] # Test date list item with non-UTC timezone tzn = datetime.timezone(datetime.timedelta(hours=-6)) date_list = [datetime.datetime(2017, 3, 6, 10, 0, 4, 159338, tzn)] res = datetime_to_string_list(date_list) assert res == ['2017-03-06T16:00:04.159338Z'] # Test specified date list with multiple items date_list = [ datetime.datetime(2017, 3, 6, 16, 0, 4, 159338), datetime.datetime(2017, 3, 6, 16, 0, 4, 159338, datetime.timezone.utc), ] res = datetime_to_string_list(date_list) assert res == ['2017-03-06T16:00:04.159338Z', '2017-03-06T16:00:04.159338Z'] def test_date_conversion(): date = string_to_date('2017-03-06') assert date.day == 6 res = date_to_string(date) assert res == '2017-03-06' assert date_to_string(None) is None def test_get_query_param(): # Relative URL next_url = '/api/v1/offerings?start=foo&limit=10' page_token = get_query_param(next_url, 'start') assert page_token == 'foo' # Absolute URL next_url = 'https://acme.com/api/v1/offerings?start=bar&limit=10' page_token = get_query_param(next_url, 'start') assert page_token == 'bar' # Missing param next_url = 'https://acme.com/api/v1/offerings?start=bar&limit=10' page_token = get_query_param(next_url, 'token') assert page_token is None # No URL page_token = get_query_param(None, 'start') assert page_token is None # Empty URL page_token = get_query_param('', 'start') assert page_token is None # No query string next_url = '/api/v1/offerings' page_token = get_query_param(next_url, 'start') assert page_token is None # Bad query string next_url = '/api/v1/offerings?start%XXfoo' with pytest.raises(ValueError): page_token = get_query_param(next_url, 'start') # Duplicate param next_url = '/api/v1/offerings?start=foo&start=bar&limit=10' page_token = get_query_param(next_url, 'start') assert page_token == 'foo' # Bad URL - since the behavior for this case varies based on the version of Python # we allow _either_ a ValueError or that the illegal chars are just ignored next_url = 'https://foo.bar\u2100/api/v1/offerings?start=foo' try: page_token = get_query_param(next_url, 'start') assert page_token == 'foo' except ValueError: # This is okay. pass def test_convert_model(): class MockModel: def __init__(self, xyz: Optional[str] = None) -> None: self.xyz = xyz def to_dict(self) -> dict: _dict = {} if hasattr(self, 'xyz') and self.xyz is not None: _dict['xyz'] = self.xyz return _dict @classmethod def from_dict(cls, _dict): pass mock1 = MockModel('foo') mock1_dict = convert_model(mock1) assert mock1_dict == {'xyz': 'foo'} mock2 = {'foo': 'bar', 'baz': 'qux'} mock2_dict = convert_model(mock2) assert mock2_dict == mock2 mock3 = 'this is not a model' mock3_dict = convert_model(mock3) assert mock3_dict == mock3 def test_convert_list(): temp = ['default', '123'] res_str = convert_list(temp) assert res_str == 'default,123' mock2 = 'default,123' mock2_str = convert_list(mock2) assert mock2_str == mock2 mock3 = {'not': 'a list'} mock3_str = convert_list(mock3) assert mock3_str == mock3 mock4 = ['not', 0, 'list of str'] mock4_str = convert_list(mock4) assert mock4_str == mock4 # pylint: disable=too-many-statements def test_get_authenticator_from_credential_file(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-iam.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('ibm watson') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == '5678efgh' assert authenticator.token_manager.url == 'https://iam.cloud.ibm.com' assert authenticator.token_manager.client_id is None assert authenticator.token_manager.client_secret is None assert authenticator.token_manager.disable_ssl_verification is False assert authenticator.token_manager.scope is None del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-basic.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('watson') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BASIC assert authenticator.username == 'my_username' del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-container.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service 1') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CONTAINER assert authenticator.token_manager.cr_token_filename == 'crtoken.txt' assert authenticator.token_manager.iam_profile_name == 'iam-user-123' assert authenticator.token_manager.iam_profile_id == 'iam-id-123' assert authenticator.token_manager.url == 'https://iamhost/iam/api' assert authenticator.token_manager.scope == 'scope1' assert authenticator.token_manager.client_id == 'iam-client-123' assert authenticator.token_manager.client_secret == 'iam-secret-123' assert authenticator.token_manager.disable_ssl_verification is True authenticator = get_authenticator_from_environment('service 2') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CONTAINER assert authenticator.token_manager.cr_token_filename is None assert authenticator.token_manager.iam_profile_name == 'iam-user-123' assert authenticator.token_manager.iam_profile_id is None assert authenticator.token_manager.url == 'https://iam.cloud.ibm.com' assert authenticator.token_manager.scope is None assert authenticator.token_manager.client_id is None assert authenticator.token_manager.client_secret is None assert authenticator.token_manager.disable_ssl_verification is False del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-cp4d.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('watson') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CP4D assert authenticator.token_manager.username == 'my_username' assert authenticator.token_manager.password == 'my_password' assert authenticator.token_manager.url == 'https://my_url/v1/authorize' assert authenticator.token_manager.apikey is None assert authenticator.token_manager.disable_ssl_verification is False del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-no-auth.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('watson') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_NOAUTH del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-bearer.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('watson') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BEARERTOKEN assert authenticator.bearer_token is not None del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service_1') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == 'V4HXmoUtMjohnsnow=KotN' assert authenticator.token_manager.client_id == 'somefake========id' assert authenticator.token_manager.client_secret == '==my-client-secret==' assert authenticator.token_manager.url == 'https://iamhost/iam/api=' assert authenticator.token_manager.scope is None del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-vpc.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service1') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC assert authenticator.token_manager.iam_profile_crn is None assert authenticator.token_manager.iam_profile_id is None assert authenticator.token_manager.url == 'http://169.254.169.254' del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-vpc.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service2') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC assert authenticator.token_manager.iam_profile_crn == 'crn:iam-profile1' assert authenticator.token_manager.iam_profile_id is None assert authenticator.token_manager.url == 'http://vpc.imds.com/api' del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-vpc.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service3') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC assert authenticator.token_manager.iam_profile_crn is None assert authenticator.token_manager.iam_profile_id == 'iam-profile1-id' assert authenticator.token_manager.url == 'http://169.254.169.254' del os.environ['IBM_CREDENTIALS_FILE'] file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials-mcsp.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service1') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_MCSP assert authenticator.token_manager.url == 'https://mcsp.ibm.com' assert authenticator.token_manager.apikey == 'my-api-key' del os.environ['IBM_CREDENTIALS_FILE'] def test_get_authenticator_from_credential_file_scope(): file_path = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials.env') os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('service_2') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == 'V4HXmoUtMjohnsnow=KotN' assert authenticator.token_manager.client_id == 'somefake========id' assert authenticator.token_manager.client_secret == '==my-client-secret==' assert authenticator.token_manager.url == 'https://iamhost/iam/api=' assert authenticator.token_manager.scope == 'A B C D' del os.environ['IBM_CREDENTIALS_FILE'] def test_get_authenticator_from_env_variables(): os.environ['TEST_APIKEY'] = '5678efgh' authenticator = get_authenticator_from_environment('test') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == '5678efgh' del os.environ['TEST_APIKEY'] os.environ['TEST_IAM_PROFILE_ID'] = 'iam-profile-id1' authenticator = get_authenticator_from_environment('test') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_CONTAINER assert authenticator.token_manager.iam_profile_id == 'iam-profile-id1' del os.environ['TEST_IAM_PROFILE_ID'] os.environ['SERVICE_1_APIKEY'] = 'V4HXmoUtMjohnsnow=KotN' authenticator = get_authenticator_from_environment('service_1') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == 'V4HXmoUtMjohnsnow=KotN' del os.environ['SERVICE_1_APIKEY'] os.environ['SERVICE_2_APIKEY'] = 'johnsnow' os.environ['SERVICE_2_SCOPE'] = 'A B C D' authenticator = get_authenticator_from_environment('service_2') assert authenticator is not None assert authenticator.token_manager.apikey == 'johnsnow' assert authenticator.token_manager.scope == 'A B C D' del os.environ['SERVICE_2_APIKEY'] del os.environ['SERVICE_2_SCOPE'] os.environ['SERVICE3_AUTH_TYPE'] = 'mCsP' os.environ['SERVICE3_AUTH_URL'] = 'https://mcsp.ibm.com' os.environ['SERVICE3_APIKEY'] = 'my-api-key' authenticator = get_authenticator_from_environment('service3') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_MCSP assert authenticator.token_manager.apikey == 'my-api-key' assert authenticator.token_manager.url == 'https://mcsp.ibm.com' del os.environ['SERVICE3_APIKEY'] del os.environ['SERVICE3_AUTH_TYPE'] del os.environ['SERVICE3_AUTH_URL'] def test_vcap_credentials(): vcap_services = '{"test":[{"credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username", \ "password":"bogus password"}}]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert isinstance(authenticator, BasicAuthenticator) assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BASIC assert authenticator.username == 'bogus username' assert authenticator.password == 'bogus password' del os.environ['VCAP_SERVICES'] vcap_services = '{"test":[{"credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "apikey":"bogus apikey"}}]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert isinstance(authenticator, IAMAuthenticator) assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == 'bogus apikey' del os.environ['VCAP_SERVICES'] vcap_services = '{"test":[{"credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "iam_apikey":"bogus apikey"}}]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert isinstance(authenticator, IAMAuthenticator) assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == 'bogus apikey' del os.environ['VCAP_SERVICES'] vcap_services = '{"test":[{"name": "testname",\ "credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username", \ "password":"bogus password"}}]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('testname') assert isinstance(authenticator, BasicAuthenticator) assert authenticator.authentication_type() == Authenticator.AUTHTYPE_BASIC assert authenticator.username == 'bogus username' assert authenticator.password == 'bogus password' del os.environ['VCAP_SERVICES'] def test_vcap_credentials_2(): vcap_services = '{\ "test":[{"name": "testname",\ "credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username2", \ "password":"bogus password2"}},\ {"name": "othertestname",\ "credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username3", \ "password":"bogus password3"}}],\ "testname":[{"name": "nottestname",\ "credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username", \ "password":"bogus password"}}],\ "equals_sign_test":[{"name": "equals_sign_test",\ "credentials":{ \ "iam_apikey": "V4HXmoUtMjohnsnow=KotN",\ "iam_apikey_description": "Auto generated apikey...",\ "iam_apikey_name": "auto-generated-apikey-111-222-333",\ "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",\ "iam_serviceid_crn": "crn:v1:staging:public:iam-identity::a/::serviceid:ServiceID-1234",\ "url": "https://gateway.watsonplatform.net/testService",\ "auth_url": "https://iamhost/iam/api="}}]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('testname') assert isinstance(authenticator, BasicAuthenticator) assert authenticator.username == 'bogus username2' assert authenticator.password == 'bogus password2' authenticator = get_authenticator_from_environment('equals_sign_test') assert isinstance(authenticator, IAMAuthenticator) assert authenticator.token_manager.apikey == 'V4HXmoUtMjohnsnow=KotN' del os.environ['VCAP_SERVICES'] vcap_services = '{"test":[{\ "credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username", \ "password":"bogus password"}},\ {"credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username2", \ "password":"bogus password2"}}\ ]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert isinstance(authenticator, BasicAuthenticator) assert authenticator.username == 'bogus username' assert authenticator.password == 'bogus password' del os.environ['VCAP_SERVICES'] vcap_services = '{"first":[],\ "test":[{"credentials":{ \ "url":"https://gateway.watsonplatform.net/compare-comply/api",\ "username":"bogus username", \ "password":"bogus password"}}],\ "last":[]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert isinstance(authenticator, BasicAuthenticator) assert authenticator.username == 'bogus username' assert authenticator.password == 'bogus password' del os.environ['VCAP_SERVICES'] vcap_services = '{"test":[],\ "last":[]}' os.environ['VCAP_SERVICES'] = vcap_services authenticator = get_authenticator_from_environment('test') assert authenticator is None del os.environ['VCAP_SERVICES'] def test_multi_word_service_name(): os.environ['PERSONALITY_INSIGHTS_APIKEY'] = '5678efgh' authenticator = get_authenticator_from_environment('personality-insights') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_IAM assert authenticator.token_manager.apikey == '5678efgh' del os.environ['PERSONALITY_INSIGHTS_APIKEY'] def test_read_external_sources_1(): # Set IBM_CREDENTIALS_FILE to a non-existent file (should be silently ignored). bad_file_path = os.path.join(os.path.dirname(__file__), 'NOT_A_FILE') os.environ['IBM_CREDENTIALS_FILE'] = bad_file_path # This env var should take precendence since the config file wasn't found. os.environ['SERVICE_1_URL'] = 'https://good-url.com' config = read_external_sources('service_1') assert config.get('URL') == 'https://good-url.com' def test_read_external_sources_2(): # The config file should take precedence over the env variable. config_file = os.path.join(os.path.dirname(__file__), '../resources/ibm-credentials.env') os.environ['IBM_CREDENTIALS_FILE'] = config_file # This should be ignored since IBM_CREDENTIALS_FILE points to a valid file. os.environ['SERVICE_1_URL'] = 'wrong-url' config = read_external_sources('service_1') assert config.get('URL') == 'service1.com/api' def test_strip_extra_slashes(): assert strip_extra_slashes('') == '' assert strip_extra_slashes('//') == '/' assert strip_extra_slashes('/////') == '/' assert strip_extra_slashes('https://host') == 'https://host' assert strip_extra_slashes('https://host/') == 'https://host/' assert strip_extra_slashes('https://host//') == 'https://host/' assert strip_extra_slashes('https://host/path') == 'https://host/path' assert strip_extra_slashes('https://host/path/') == 'https://host/path/' assert strip_extra_slashes('https://host/path//') == 'https://host/path/' assert strip_extra_slashes('https://host//path//') == 'https://host//path/' assert strip_extra_slashes('https://host//path//////////') == 'https://host//path/' def test_is_json_mimetype(): assert is_json_mimetype(None) is False assert is_json_mimetype('') is False assert is_json_mimetype('application/octet-stream') is False assert is_json_mimetype('ApPlIcAtION/JsoN') is False assert is_json_mimetype('applicaiton/json; charset=utf8') is False assert is_json_mimetype('fooapplication/json; charset=utf8; foo=bar') is False assert is_json_mimetype('application/json') is True assert is_json_mimetype('application/json; charset=utf8') is True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_vpc_instance_authenticator.py0000664000372000037200000000512000000000000026025 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import pytest from ibm_cloud_sdk_core.authenticators import VPCInstanceAuthenticator, Authenticator TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123' TEST_IAM_PROFILE_ID = 'iam-id-123' def test_constructor(): authenticator = VPCInstanceAuthenticator(iam_profile_id=TEST_IAM_PROFILE_ID, url='someurl.com') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC assert authenticator.token_manager.iam_profile_crn is None assert authenticator.token_manager.iam_profile_id == TEST_IAM_PROFILE_ID assert authenticator.token_manager.url == 'someurl.com' def test_setters(): authenticator = VPCInstanceAuthenticator(iam_profile_id=TEST_IAM_PROFILE_ID, url='someurl.com') assert authenticator is not None assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC assert authenticator.token_manager.iam_profile_crn is None assert authenticator.token_manager.iam_profile_id == TEST_IAM_PROFILE_ID assert authenticator.token_manager.url == 'someurl.com' # Set the IAM profile CRN to trigger a validation which will fail, # because at most one of iam_profile_crn or iam_profile_id may be specified. with pytest.raises(ValueError) as err: authenticator.set_iam_profile_crn(TEST_IAM_PROFILE_CRN) assert str(err.value) == 'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.' authenticator.set_iam_profile_id(None) assert authenticator.token_manager.iam_profile_id is None authenticator.set_iam_profile_crn(TEST_IAM_PROFILE_CRN) assert authenticator.token_manager.iam_profile_crn == TEST_IAM_PROFILE_CRN def test_constructor_validate_failed(): with pytest.raises(ValueError) as err: VPCInstanceAuthenticator( iam_profile_crn=TEST_IAM_PROFILE_CRN, iam_profile_id=TEST_IAM_PROFILE_ID, ) assert str(err.value) == 'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.' def test_authenticate(): def mock_get_token(): return 'mock_token' authenticator = VPCInstanceAuthenticator(iam_profile_crn=TEST_IAM_PROFILE_CRN) authenticator.token_manager.get_token = mock_get_token # Simulate an SDK API request that needs to be authenticated. request = {'headers': {}} # Trigger the "get token" processing to obtain the access token and add to the "SDK request". authenticator.authenticate(request) # Verify that the "authenticate()" method added the Authorization header assert request['headers']['Authorization'] == 'Bearer mock_token' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test/test_vpc_instance_token_manager.py0000664000372000037200000003054600000000000025777 0ustar00travistravis00000000000000# coding: utf-8 # Copyright 2021, 2024 IBM 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. # pylint: disable=missing-docstring import json import logging import time import pytest import responses from ibm_cloud_sdk_core import ApiException, VPCInstanceTokenManager # pylint: disable=line-too-long TEST_ACCESS_TOKEN_1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI' TEST_ACCESS_TOKEN_2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJ1c2VybmFtZSI6ImR1bW15Iiwicm9sZSI6IkFkbWluIiwicGVybWlzc2lvbnMiOlsiYWRtaW5pc3RyYXRvciIsIm1hbmFnZV9jYXRhbG9nIl0sInN1YiI6ImFkbWluIiwiaXNzIjoic3NzIiwiYXVkIjoic3NzIiwidWlkIjoic3NzIiwiaWF0IjozNjAwLCJleHAiOjE2MjgwMDcwODF9.zvUDpgqWIWs7S1CuKv40ERw1IZ5FqSFqQXsrwZJyfRM' TEST_TOKEN = 'abc123' TEST_IAM_TOKEN = 'iam-abc123' TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123' TEST_IAM_PROFILE_ID = 'iam-id-123' EXPIRATION_WINDOW = 10 def _get_current_time() -> int: return int(time.time()) def test_constructor(): token_manager = VPCInstanceTokenManager( iam_profile_crn=TEST_IAM_PROFILE_CRN, ) assert token_manager.iam_profile_crn is TEST_IAM_PROFILE_CRN assert token_manager.iam_profile_id is None assert token_manager.access_token is None def test_setters(): token_manager = VPCInstanceTokenManager( iam_profile_crn=TEST_IAM_PROFILE_CRN, ) assert token_manager.iam_profile_crn is TEST_IAM_PROFILE_CRN assert token_manager.iam_profile_id is None assert token_manager.access_token is None token_manager.set_iam_profile_crn(None) assert token_manager.iam_profile_crn is None token_manager.set_iam_profile_id(TEST_IAM_PROFILE_ID) assert token_manager.iam_profile_id == TEST_IAM_PROFILE_ID @responses.activate def test_retrieve_instance_identity_token(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager( iam_profile_crn=TEST_IAM_PROFILE_CRN, url='http://someurl.com', ) response = { 'access_token': TEST_TOKEN, } responses.add(responses.PUT, 'http://someurl.com/instance_identity/v1/token', body=json.dumps(response), status=200) ii_token = token_manager.retrieve_instance_identity_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Content-Type'] == 'application/json' assert responses.calls[0].request.headers['Accept'] == 'application/json' assert responses.calls[0].request.headers['Metadata-Flavor'] == 'ibm' assert responses.calls[0].request.params['version'] == '2022-03-01' assert responses.calls[0].request.body == '{"expires_in": 300}' assert ii_token == TEST_TOKEN # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token' ) assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_access_token\' operation."' @responses.activate def test_retrieve_instance_identity_token_failed(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager( iam_profile_crn=TEST_IAM_PROFILE_CRN, url='http://someurl.com', ) response = { 'errors': ['Ooops'], } responses.add(responses.PUT, 'http://someurl.com/instance_identity/v1/token', body=json.dumps(response), status=400) with pytest.raises(ApiException): token_manager.retrieve_instance_identity_token() assert len(responses.calls) == 1 # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token' ) @responses.activate def test_request_token_with_crn(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager( iam_profile_crn=TEST_IAM_PROFILE_CRN, ) # Mock the retrieve instance identity token method. def mock_retrieve_instance_identity_token(): return TEST_TOKEN token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token response = { 'access_token': TEST_IAM_TOKEN, } responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response), status=200 ) response = token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Content-Type'] == 'application/json' assert responses.calls[0].request.headers['Accept'] == 'application/json' assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN assert responses.calls[0].request.body == '{"trusted_profile": {"crn": "crn:iam-profile:123"}}' assert responses.calls[0].request.params['version'] == '2022-03-01' # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token' ) assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."' @responses.activate def test_request_token_with_id(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager( iam_profile_id=TEST_IAM_PROFILE_ID, ) # Mock the retrieve instance identity token method. def mock_retrieve_instance_identity_token(): return TEST_TOKEN token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token response = { 'access_token': TEST_IAM_TOKEN, } responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response), status=200 ) response = token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Content-Type'] == 'application/json' assert responses.calls[0].request.headers['Accept'] == 'application/json' assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN assert responses.calls[0].request.body == '{"trusted_profile": {"id": "iam-id-123"}}' assert responses.calls[0].request.params['version'] == '2022-03-01' # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token' ) assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."' @responses.activate def test_request_token(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager() # Mock the retrieve instance identity token method. def mock_retrieve_instance_identity_token(): return TEST_TOKEN token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token response = { 'access_token': TEST_IAM_TOKEN, } responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response), status=200 ) response = token_manager.request_token() assert len(responses.calls) == 1 assert responses.calls[0].request.headers['Content-Type'] == 'application/json' assert responses.calls[0].request.headers['Accept'] == 'application/json' assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN assert responses.calls[0].request.body is None assert responses.calls[0].request.params['version'] == '2022-03-01' # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token' ) assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."' @responses.activate def test_request_token_failed(caplog): caplog.set_level(logging.DEBUG) token_manager = VPCInstanceTokenManager( iam_profile_id=TEST_IAM_PROFILE_ID, ) # Mock the retrieve instance identity token method. def mock_retrieve_instance_identity_token(): return TEST_TOKEN token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token response = { 'errors': ['Ooops'], } responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response), status=400 ) with pytest.raises(ApiException): token_manager.request_token() assert len(responses.calls) == 1 # Check the logs. # pylint: disable=line-too-long assert ( caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token' ) @responses.activate def test_access_token(): token_manager = VPCInstanceTokenManager( iam_profile_id=TEST_IAM_PROFILE_ID, ) response_ii = { 'access_token': TEST_TOKEN, } response_iam = { 'access_token': TEST_ACCESS_TOKEN_1, } responses.add( responses.PUT, 'http://169.254.169.254/instance_identity/v1/token', body=json.dumps(response_ii), status=200 ) responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response_iam), status=200, ) assert token_manager.access_token is None assert token_manager.expire_time == 0 assert token_manager.refresh_time == 0 token_manager.get_token() assert token_manager.access_token == TEST_ACCESS_TOKEN_1 assert token_manager.expire_time > 0 assert token_manager.refresh_time > 0 @responses.activate def test_get_token_success(): token_manager = VPCInstanceTokenManager() # Mock the retrieve instance identity token method. def mock_retrieve_instance_identity_token(): return TEST_TOKEN token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token response1 = { 'access_token': TEST_ACCESS_TOKEN_1, } response2 = { 'access_token': TEST_ACCESS_TOKEN_2, } responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response1), status=200 ) access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Verify that the token manager returns the cached value. # Before we call `get_token` again, set the expiration and refresh time # so that we do not fetch a new access token. # This is necessary because we are using a fixed JWT response. token_manager.expire_time = _get_current_time() + 1000 token_manager.refresh_time = _get_current_time() + 1000 access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_1 assert token_manager.access_token == TEST_ACCESS_TOKEN_1 # Force expiration to get the second token. # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs) # because we want the access token to be considered as "expired" # when we reach the IAM-server reported expiration time minus 10 secs. responses.add( responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response2), status=200 ) token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW access_token = token_manager.get_token() assert access_token == TEST_ACCESS_TOKEN_2 assert token_manager.access_token == TEST_ACCESS_TOKEN_2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709138948.1407251 ibm-cloud-sdk-core-3.19.2/test_integration/0000775000372000037200000000000000000000000021413 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test_integration/__init__.py0000664000372000037200000000000000000000000023512 0ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test_integration/test_cp4d_authenticator_integration.py0000664000372000037200000000320700000000000031215 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import os from ibm_cloud_sdk_core import get_authenticator_from_environment # Note: Only the unit tests are run by default. # # In order to test with a live CP4D server, rename "ibm-credentials-cp4dtest.env.example" to # "ibm-credentials-cp4dtest.env" in the resources folder and populate the fields. # Then run this command: # pytest test_integration/test_cp4d_authenticator_integration.py IBM_CREDENTIALS_FILE = '../resources/ibm-credentials-cp4dtest.env' def test_cp4d_authenticator_password(): file_path = os.path.join(os.path.dirname(__file__), IBM_CREDENTIALS_FILE) os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('cp4d_password_test') assert authenticator is not None assert authenticator.token_manager.password is not None assert authenticator.token_manager.apikey is None request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] is not None assert 'Bearer' in request['headers']['Authorization'] def test_cp4d_authenticator_apikey(): file_path = os.path.join(os.path.dirname(__file__), IBM_CREDENTIALS_FILE) os.environ['IBM_CREDENTIALS_FILE'] = file_path authenticator = get_authenticator_from_environment('cp4d_apikey_test') assert authenticator is not None assert authenticator.token_manager.password is None assert authenticator.token_manager.apikey is not None request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] is not None assert 'Bearer' in request['headers']['Authorization'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test_integration/test_iam_authenticator_integration.py0000664000372000037200000000154600000000000031135 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import os from ibm_cloud_sdk_core import get_authenticator_from_environment # Note: Only the unit tests are run by default. # # In order to test with a live IAM server, create file "iamtest.env" in the project root. # It should look like this: # # IAMTEST1_AUTH_URL= e.g. https://iam.cloud.ibm.com # IAMTEST1_AUTH_TYPE=iam # IAMTEST1_APIKEY= # # Then run this command: # pytest test_integration/test_iam_authenticator_integration.py def test_iam_authenticator(): os.environ['IBM_CREDENTIALS_FILE'] = 'iamtest.env' authenticator = get_authenticator_from_environment('iamtest1') assert authenticator is not None request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] is not None assert 'Bearer' in request['headers']['Authorization'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test_integration/test_mcsp_authenticator_integration.py0000664000372000037200000000156300000000000031330 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import os from ibm_cloud_sdk_core import get_authenticator_from_environment # Note: Only the unit tests are run by default. # # In order to test with a live MCSP token server, create file "mcsptest.env" in the project root. # It should look like this: # # MCSPTEST_AUTH_URL= e.g. https://iam.cloud.ibm.com # MCSPTEST_AUTH_TYPE=mcsp # MCSPTEST_APIKEY= # # Then run this command: # pytest test_integration/test_mcsp_authenticator_integration.py def test_mcsp_authenticator(): os.environ['IBM_CREDENTIALS_FILE'] = 'mcsptest.env' authenticator = get_authenticator_from_environment('mcsptest1') assert authenticator is not None request = {'headers': {}} authenticator.authenticate(request) assert request['headers']['Authorization'] is not None assert 'Bearer' in request['headers']['Authorization'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709138823.0 ibm-cloud-sdk-core-3.19.2/test_integration/test_ssl_verification.py0000664000372000037200000000434000000000000026370 0ustar00travistravis00000000000000# pylint: disable=missing-docstring import os import threading from http.server import HTTPServer, SimpleHTTPRequestHandler from ssl import PROTOCOL_TLS_SERVER, SSLContext import pytest from requests.exceptions import SSLError from ibm_cloud_sdk_core.base_service import BaseService from ibm_cloud_sdk_core.authenticators import NoAuthAuthenticator # The certificate files that are used in this tests are generated by this command: # openssl req \ # -new \ # -newkey rsa:4096 \ # -days 36500 \ # -nodes \ # -x509 \ # -subj "/C=US/CN=localhost" \ # -keyout test_ssl.key \ # -out test_ssl.cert def test_ssl_verification(): # Load the certificate and the key files. cert = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.cert') key = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.key') # Build the SSL context for the server. ssl_context = SSLContext(PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(certfile=cert, keyfile=key) # Create and start the server on a separate thread. server = HTTPServer(('127.0.0.1', 3333), SimpleHTTPRequestHandler) server.socket = ssl_context.wrap_socket(server.socket, server_side=True) t = threading.Thread(target=server.serve_forever) t.start() # We run everything in a big try-except-finally block to make sure we always # shutdown the HTTP server gracefully. try: service = BaseService(service_url='https://127.0.0.1:3333', authenticator=NoAuthAuthenticator()) # # First call the server with the default configuration. # It should fail due to the invalid SSL cert. assert service.disable_ssl_verification is False prepped = service.prepare_request('GET', url='/') with pytest.raises(SSLError): res = service.send(prepped) # Now disable the SSL verification. The request shouldn't raise any issue. service.set_disable_ssl_verification(True) assert service.disable_ssl_verification is True prepped = service.prepare_request('GET', url='/') res = service.send(prepped) assert res is not None except Exception: # pylint: disable=try-except-raise raise finally: server.shutdown()