ibm-cloud-sdk-core-3.12.0/0000775000372000037200000000000014132362500016013 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/MANIFEST.in0000664000372000037200000000010614132362372017555 0ustar travistravis00000000000000include requirements.txt include requirements-dev.txt include LICENSE ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/0000775000372000037200000000000014132362500021621 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/base_service.py0000664000372000037200000004645214132362372024647 0ustar travistravis00000000000000# 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 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.adapters import HTTPAdapter from requests.structures import CaseInsensitiveDict 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, remove_null_values, cleanup_values, read_external_sources, strip_extra_slashes) from .version import __version__ # Uncomment this to enable http debugging # import http.client as http_client # http_client.HTTPConnection.debuglevel = 1 #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 = HTTPAdapter() if not self.authenticator: raise ValueError('authenticator must be provided') if not isinstance(self.authenticator, Authenticator): raise ValueError('authenticator should be of type Authenticator') def enable_retries(self, max_retries: int = 4, retry_interval: float = 1.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 default wait time (in seconds) to use for the first retry attempt. 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 the retry_interval and retry attempt number: wait_time = retry_interval * (2 ^ (n-1)), where n is the retry attempt number """ self.retry_config = Retry( total=max_retries, backoff_factor=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 = HTTPAdapter(max_retries=self.retry_config) 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 = HTTPAdapter() 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(), # OS platform.release(), # OS version platform.python_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}) """ self.disable_ssl_verification = status 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' ) 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'] for key in reserved_keys: if key in kwargs: del kwargs[key] logging.warning('"%s" has been removed from the request', key) try: response = self.http_client.request(**request, cookies=self.jar, **kwargs) if 200 <= response.status_code <= 299: if response.status_code == 204 or request['method'] == 'HEAD': # There is no body content for a HEAD request or a 204 response result = None elif stream_response: result = response elif not response.text: result = None else: try: result = response.json() except: result = response return DetailedResponse(response=result, headers=response.headers, status_code=response.status_code) raise ApiException(response.status_code, http_response=response) except requests.exceptions.SSLError: logging.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') 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' uncompressed_data = request['data'] request_body = gzip.compress(uncompressed_data) request['data'] = request_body request['headers'] = headers # 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) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/0000775000372000037200000000000014132362500024616 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py0000664000372000037200000001653414132362372033556 0ustar travistravis00000000000000# 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 .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" 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 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/iam_token_manager.py0000664000372000037200000001020614132362372030636 0ustar travistravis00000000000000# 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' ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/jwt_token_manager.py0000664000372000037200000000762014132362372030702 0ustar travistravis00000000000000# 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) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py0000664000372000037200000001050714132362372030726 0ustar travistravis00000000000000# 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. 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. """ 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) -> None: self.username = username self.password = password 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) 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') ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/token_manager.py0000664000372000037200000001675414132362372030026 0ustar travistravis00000000000000# 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) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/__init__.py0000664000372000037200000000113614132362372026737 0ustar travistravis00000000000000# 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. ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/token_managers/container_token_manager.py0000664000372000037200000002032314132362372032053 0ustar travistravis00000000000000# 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 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_FILENAME = '/var/run/secrets/tokens/vault-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: Cannot retrieve the compute resource token from. Returns: A string which contains the compute resource token. """ cr_token_filename = self.cr_token_filename if self.cr_token_filename else self.DEFAULT_CR_TOKEN_FILENAME logging.debug('Attempting to read CR token from file: %s', cr_token_filename) try: with open(cr_token_filename, 'r', encoding='utf-8') as file: cr_token = file.read() return cr_token # pylint: disable=broad-except except Exception as ex: raise Exception( 'Unable to retrieve the CR token value from file {}: {}'.format(cr_token_filename, 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. """ # Retrieve the CR token for this compute resource. cr_token = self.retrieve_cr_token() # Set the request payload. self.request_payload['cr_token'] = 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 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/get_authenticator.py0000664000372000037200000001000114132362372025703 0ustar travistravis00000000000000# 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) 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_NOAUTH.lower(): authenticator = NoAuthAuthenticator() return authenticator ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/0000775000372000037200000000000014132362500024656 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/basic_authenticator.py0000664000372000037200000000611314132362372031253 0ustar travistravis00000000000000# 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 too. Must contain a key to a dictionary called headers. """ headers = req.get('headers') headers['Authorization'] = self.authorization_header ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py0000664000372000037200000001073014132362372033646 0ustar travistravis00000000000000# 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 too. 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) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/iam_authenticator.py0000664000372000037200000001074414132362372030745 0ustar travistravis00000000000000# 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.') ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py0000664000372000037200000001465714132362372031040 0ustar travistravis00000000000000# 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. 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) -> 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) 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 too. 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) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/authenticator.py0000664000372000037200000000364614132362372030122 0ustar travistravis00000000000000# 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 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_NOAUTH = 'noAuth' 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 # pylint: disable=R0201 def authentication_type(self) -> str: """Returns the authenticator's type. This method should be overridden by each authenticator implementation.""" return Authenticator.AUTHTYPE_UNKNOWN ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py0000664000372000037200000000510014132362372032625 0ustar travistravis00000000000000# 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 too. 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() ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/container_authenticator.py0000664000372000037200000001560014132362372032155 0ustar travistravis00000000000000# 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() ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/__init__.py0000664000372000037200000000374214132362372027004 0ustar travistravis00000000000000# 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 .no_auth_authenticator import NoAuthAuthenticator ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/authenticators/no_auth_authenticator.py0000664000372000037200000000172314132362372031631 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/utils.py0000664000372000037200000003043314132362372023345 0ustar travistravis00000000000000# 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 json as json_import 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 import dateutil.parser as date_parser 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 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/version.py0000664000372000037200000000002714132362372023666 0ustar travistravis00000000000000__version__ = '3.12.0' ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/detailed_response.py0000664000372000037200000000526314132362372025701 0ustar travistravis00000000000000# 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 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 there response, defaults to None. Attributes: response (requests.Response): 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[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) -> requests.Response: """Get the HTTP response of the service request. Returns: The response to the service request """ return self.result def get_headers(self) -> dict: """The HTTP response headers of the service request. Returns: A dictionary of response headers """ return self.headers def get_status_code(self) -> int: """The HTTP status code of the service request. Returns: The status code of 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__) ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/__init__.py0000664000372000037200000000522614132362372023746 0ustar travistravis00000000000000# 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 .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 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core/api_exception.py0000664000372000037200000000615414132362372025037 0ustar travistravis00000000000000# 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 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.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) def __str__(self) -> str: msg = 'Error: ' + str(self.message) + ', Code: ' + str(self.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() 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 ibm-cloud-sdk-core-3.12.0/requirements.txt0000664000372000037200000000010514132362372021302 0ustar travistravis00000000000000requests>=2.20,<3.0 python_dateutil>=2.5.3,<3.0.0 PyJWT>=2.0.1,<3.0.0ibm-cloud-sdk-core-3.12.0/PKG-INFO0000664000372000037200000001052414132362500017112 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: ibm-cloud-sdk-core Version: 3.12.0 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: watson,ibm,cloud,ibm cloud services Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 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)[![codecov](https://codecov.io/gh/IBM/python-sdk-core/branch/main/graph/badge.svg)](https://codecov.io/gh/IBM/python-sdk-core) # IBM Python SDK Core Version 3.12.0 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.6. ## Installation To install, use `pip` or `easy_install`: ```bash pip install --upgrade ibm-cloud-sdk-core ``` or ```bash easy_install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token - Identity and Access Management (IAM) - Cloud Pak for Data - Container - No Authentication 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). ibm-cloud-sdk-core-3.12.0/README.md0000664000372000037200000000653014132362372017305 0ustar travistravis00000000000000[![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)[![codecov](https://codecov.io/gh/IBM/python-sdk-core/branch/main/graph/badge.svg)](https://codecov.io/gh/IBM/python-sdk-core) # IBM Python SDK Core Version 3.12.0 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.6. ## Installation To install, use `pip` or `easy_install`: ```bash pip install --upgrade ibm-cloud-sdk-core ``` or ```bash easy_install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token - Identity and Access Management (IAM) - Cloud Pak for Data - Container - No Authentication 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). ibm-cloud-sdk-core-3.12.0/requirements-dev.txt0000664000372000037200000000022414132362372022060 0ustar travistravis00000000000000codecov>=2.1.0,<3.0.0 coverage>=4.5.4 pylint>=2.6.0,<3.0.0 pytest>=6.2.1,<7.0.0 pytest-cov>=2.2.1,<3.0.0 responses>=0.12.1,<1.0.0 tox>=3.2.0,<4.0.0 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/0000775000372000037200000000000014132362500023313 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/PKG-INFO0000664000372000037200000001052414132362500024412 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: ibm-cloud-sdk-core Version: 3.12.0 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: watson,ibm,cloud,ibm cloud services Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 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)[![codecov](https://codecov.io/gh/IBM/python-sdk-core/branch/main/graph/badge.svg)](https://codecov.io/gh/IBM/python-sdk-core) # IBM Python SDK Core Version 3.12.0 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.6. ## Installation To install, use `pip` or `easy_install`: ```bash pip install --upgrade ibm-cloud-sdk-core ``` or ```bash easy_install --upgrade ibm-cloud-sdk-core ``` ## Authentication The python-sdk-core project supports the following types of authentication: - Basic Authentication - Bearer Token - Identity and Access Management (IAM) - Cloud Pak for Data - Container - No Authentication 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). ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/zip-safe0000664000372000037200000000000114132362427024753 0ustar travistravis00000000000000 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/dependency_links.txt0000664000372000037200000000000114132362500027361 0ustar travistravis00000000000000 ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/top_level.txt0000664000372000037200000000005114132362500026041 0ustar travistravis00000000000000ibm_cloud_sdk_core test test_integration ibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/SOURCES.txt0000664000372000037200000000402114132362500025174 0ustar travistravis00000000000000LICENSE MANIFEST.in README.md 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/no_auth_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/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_no_auth_authenticator.py test/test_token_manager.py test/test_utils.py test_integration/__init__.py test_integration/test_cp4d_authenticator_integration.pyibm-cloud-sdk-core-3.12.0/ibm_cloud_sdk_core.egg-info/requires.txt0000664000372000037200000000010614132362500025710 0ustar travistravis00000000000000requests<3.0,>=2.20 python_dateutil<3.0.0,>=2.5.3 PyJWT<3.0.0,>=2.0.1 ibm-cloud-sdk-core-3.12.0/setup.cfg0000664000372000037200000000004614132362500017634 0ustar travistravis00000000000000[egg_info] tag_build = tag_date = 0 ibm-cloud-sdk-core-3.12.0/LICENSE0000664000372000037200000002613514132362372017036 0ustar travistravis00000000000000 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. ibm-cloud-sdk-core-3.12.0/test/0000775000372000037200000000000014132362500016772 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/test/test_basic_authenticator.py0000664000372000037200000000310514132362372024424 0ustar travistravis00000000000000# 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.' ibm-cloud-sdk-core-3.12.0/test/test_cp4d_authenticator.py0000664000372000037200000001527414132362372024207 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/test/test_base_service.py0000664000372000037200000010230014132362372023040 0ustar travistravis00000000000000# coding=utf-8 # pylint: disable=missing-docstring,protected-access,too-few-public-methods import gzip import json import os 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) from ibm_cloud_sdk_core.utils import strip_extra_slashes 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(): 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()) try: prepped = service.prepare_request('GET', url='') service.send(prepped) except ApiException as err: assert err.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_response(): 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"} @responses.activate def test_request_fail_401(): 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()) try: prepped = service.prepare_request('GET', url='') service.send(prepped) except ApiException as err: assert err.message == 'Unauthorized: Access is denied due to invalid credentials' 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_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.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, 0.3) assert service.retry_config.total == 2 assert service.retry_config.backoff_factor == 0.3 # 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 == 0.2 # 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.__getitem__( 'user-agent') == 'my_user_agent' prepped = service.prepare_request('GET', url='', headers=None) response = service.send(prepped) assert response.get_result().request.headers.__getitem__( '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.__getitem__('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] == '"headers" has been removed from the request' assert caplog.record_tuples[3][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_trailing_slash(): 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/' 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/' 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 == '/' req = service.prepare_request('POST', url='/', headers={'X-opt-out': True}, data={'hello': 'world'}) assert req.get('url') == '/' 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.' ibm-cloud-sdk-core-3.12.0/test/test_authenticator.py0000664000372000037200000000112014132362372023256 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/test/test_bearer_authenticator.py0000664000372000037200000000205514132362372024606 0ustar travistravis00000000000000# 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.' ibm-cloud-sdk-core-3.12.0/test/test_detailed_response.py0000664000372000037200000000450114132362372024103 0ustar travistravis00000000000000# 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') 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(detailed_response.__str__()) assert clean(detailed_response.get_result().__str__()) in response_str #assert clean(detailed_response.get_headers().__str__()) in response_str assert clean(detailed_response.get_status_code().__str__()) 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') 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(detailed_response.__str__()) assert clean(detailed_response.get_result().__str__()) in response_str #assert clean(detailed_response.get_headers().__str__()) in response_str assert clean(detailed_response.get_status_code().__str__()) in response_str ibm-cloud-sdk-core-3.12.0/test/test_iam_token_manager.py0000664000372000037200000003043114132362372024053 0ustar travistravis00000000000000# 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 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_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) ibm-cloud-sdk-core-3.12.0/test/test_iam_authenticator.py0000664000372000037200000001314114132362372024112 0ustar travistravis00000000000000# 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' ibm-cloud-sdk-core-3.12.0/test/test_container_token_manager.py0000664000372000037200000002462414132362372025276 0ustar travistravis00000000000000# 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' 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 value 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 the token manager return the cached value. # Before we call the `get_token` again, set the expiration and time. # This is necessary because we are using a fix JWT response. token_manager.expire_time = _get_current_time() + 3600 token_manager.refresh_time = _get_current_time() + 3600 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. token_manager.expire_time = _get_current_time() - 1 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 the token manager return the cached value. # Before we call the `get_token` again, set the expiration and time. # This is necessary because we are using a fix JWT response. authenticator.token_manager.expire_time = _get_current_time() + 3600 authenticator.token_manager.refresh_time = _get_current_time() + 3600 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. authenticator.token_manager.expire_time = _get_current_time() - 1 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 value 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, 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 ibm-cloud-sdk-core-3.12.0/test/test_cp4d_token_manager.py0000664000372000037200000000330714132362372024141 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/test/test_container_authenticator.py0000664000372000037200000001050614132362372025330 0ustar travistravis00000000000000# 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.' ibm-cloud-sdk-core-3.12.0/test/__init__.py0000664000372000037200000000000014132362372021100 0ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/test/test_no_auth_authenticator.py0000664000372000037200000000070214132362372025000 0ustar travistravis00000000000000# 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 request['headers'] == {} ibm-cloud-sdk-core-3.12.0/test/test_jwt_token_manager.py0000664000372000037200000000735414132362372024121 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/test/test_utils.py0000664000372000037200000006271314132362372021563 0ustar travistravis00000000000000# 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 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 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'] 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'] 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' ibm-cloud-sdk-core-3.12.0/test/test_token_manager.py0000664000372000037200000000572714132362372023237 0ustar travistravis00000000000000# 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 ibm-cloud-sdk-core-3.12.0/test/test_api_exception.py0000664000372000037200000000544714132362372023253 0ustar travistravis00000000000000# 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') 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') 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') 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') 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') 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') exception = ApiException(500, http_response=mock_response) assert exception.message == 'plain text error' assert exception.__str__() == 'Error: plain text error, Code: 500 , X-global-transaction-id: xx' ibm-cloud-sdk-core-3.12.0/test_integration/0000775000372000037200000000000014132362500021375 5ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/test_integration/test_cp4d_authenticator_integration.py0000664000372000037200000000316214132362372031206 0ustar travistravis00000000000000# 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 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'] ibm-cloud-sdk-core-3.12.0/test_integration/__init__.py0000664000372000037200000000000014132362372023503 0ustar travistravis00000000000000ibm-cloud-sdk-core-3.12.0/setup.py0000664000372000037200000000570614132362372017544 0ustar travistravis00000000000000#!/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.12.0' 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') as f: install_requires = [str(req) for req in pkg_resources.parse_requirements(f)] with open('requirements-dev.txt') as f: tests_require = [str(req) for req in pkg_resources.parse_requirements(f)] class PyTest(TestCommand): 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") 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='watson, ibm, cloud, ibm cloud services', classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', '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 )