ibm-cloud-sdk-core 3.16.0__py3-none-any.whl → 3.21.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. ibm_cloud_sdk_core/__init__.py +1 -0
  2. ibm_cloud_sdk_core/api_exception.py +18 -4
  3. ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
  4. ibm_cloud_sdk_core/authenticators/authenticator.py +2 -1
  5. ibm_cloud_sdk_core/authenticators/basic_authenticator.py +12 -7
  6. ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +7 -2
  7. ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
  8. ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +38 -23
  9. ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
  10. ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +10 -8
  11. ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +134 -0
  12. ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +12 -11
  13. ibm_cloud_sdk_core/base_service.py +137 -103
  14. ibm_cloud_sdk_core/detailed_response.py +21 -15
  15. ibm_cloud_sdk_core/get_authenticator.py +35 -17
  16. ibm_cloud_sdk_core/http_adapter.py +28 -0
  17. ibm_cloud_sdk_core/logger.py +85 -0
  18. ibm_cloud_sdk_core/private_helpers.py +34 -0
  19. ibm_cloud_sdk_core/token_managers/container_token_manager.py +63 -33
  20. ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +35 -22
  21. ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +50 -21
  22. ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
  23. ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
  24. ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +108 -0
  25. ibm_cloud_sdk_core/token_managers/token_manager.py +20 -23
  26. ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +37 -18
  27. ibm_cloud_sdk_core/utils.py +127 -48
  28. ibm_cloud_sdk_core/version.py +1 -1
  29. ibm_cloud_sdk_core-3.21.0.dist-info/METADATA +159 -0
  30. ibm_cloud_sdk_core-3.21.0.dist-info/RECORD +35 -0
  31. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/WHEEL +1 -1
  32. ibm_cloud_sdk_core-3.21.0.dist-info/top_level.txt +1 -0
  33. ibm_cloud_sdk_core-3.16.0.dist-info/METADATA +0 -112
  34. ibm_cloud_sdk_core-3.16.0.dist-info/RECORD +0 -52
  35. ibm_cloud_sdk_core-3.16.0.dist-info/top_level.txt +0 -3
  36. ibm_cloud_sdk_core-3.16.0.dist-info/zip-safe +0 -1
  37. test/__init__.py +0 -0
  38. test/test_api_exception.py +0 -73
  39. test/test_authenticator.py +0 -21
  40. test/test_base_service.py +0 -933
  41. test/test_basic_authenticator.py +0 -36
  42. test/test_bearer_authenticator.py +0 -28
  43. test/test_container_authenticator.py +0 -105
  44. test/test_container_token_manager.py +0 -283
  45. test/test_cp4d_authenticator.py +0 -171
  46. test/test_cp4d_token_manager.py +0 -56
  47. test/test_detailed_response.py +0 -57
  48. test/test_iam_authenticator.py +0 -157
  49. test/test_iam_token_manager.py +0 -362
  50. test/test_jwt_token_manager.py +0 -109
  51. test/test_no_auth_authenticator.py +0 -15
  52. test/test_token_manager.py +0 -84
  53. test/test_utils.py +0 -634
  54. test/test_vpc_instance_authenticator.py +0 -66
  55. test/test_vpc_instance_token_manager.py +0 -266
  56. test_integration/__init__.py +0 -0
  57. test_integration/test_cp4d_authenticator_integration.py +0 -45
  58. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
17
17
  from typing import Dict, Optional
18
18
 
19
19
  from .iam_request_based_token_manager import IAMRequestBasedTokenManager
20
+ from ..private_helpers import _build_user_agent
20
21
 
21
22
 
22
23
  class IAMTokenManager(IAMRequestBasedTokenManager):
@@ -60,19 +61,27 @@ class IAMTokenManager(IAMRequestBasedTokenManager):
60
61
  This can be used to obtain an access token with a specific scope.
61
62
  """
62
63
 
63
- def __init__(self,
64
- apikey: str,
65
- *,
66
- url: Optional[str] = None,
67
- client_id: Optional[str] = None,
68
- client_secret: Optional[str] = None,
69
- disable_ssl_verification: bool = False,
70
- headers: Optional[Dict[str, str]] = None,
71
- proxies: Optional[Dict[str, str]] = None,
72
- scope: Optional[str] = None) -> None:
64
+ def __init__(
65
+ self,
66
+ apikey: str,
67
+ *,
68
+ url: Optional[str] = None,
69
+ client_id: Optional[str] = None,
70
+ client_secret: Optional[str] = None,
71
+ disable_ssl_verification: bool = False,
72
+ headers: Optional[Dict[str, str]] = None,
73
+ proxies: Optional[Dict[str, str]] = None,
74
+ scope: Optional[str] = None,
75
+ ) -> None:
73
76
  super().__init__(
74
- url=url, client_id=client_id, client_secret=client_secret,
75
- disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope)
77
+ url=url,
78
+ client_id=client_id,
79
+ client_secret=client_secret,
80
+ disable_ssl_verification=disable_ssl_verification,
81
+ headers=headers,
82
+ proxies=proxies,
83
+ scope=scope,
84
+ )
76
85
 
77
86
  self.apikey = apikey
78
87
 
@@ -80,3 +89,5 @@ class IAMTokenManager(IAMRequestBasedTokenManager):
80
89
  self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:apikey'
81
90
  self.request_payload['apikey'] = self.apikey
82
91
  self.request_payload['response_type'] = 'cloud_iam'
92
+
93
+ self._set_user_agent(_build_user_agent('iam-authenticator'))
@@ -75,15 +75,7 @@ class JWTTokenManager(TokenManager, ABC):
75
75
  buffer = (exp - iat) * 0.2
76
76
  self.refresh_time = self.expire_time - buffer
77
77
 
78
- def _request(self,
79
- method,
80
- url,
81
- *,
82
- headers=None,
83
- params=None,
84
- data=None,
85
- auth_tuple=None,
86
- **kwargs) -> dict:
78
+ def _request(self, method, url, *, headers=None, params=None, data=None, auth_tuple=None, **kwargs) -> dict:
87
79
  kwargs = dict({"timeout": 60}, **kwargs)
88
80
  kwargs = dict(kwargs, **self.http_config)
89
81
 
@@ -91,13 +83,8 @@ class JWTTokenManager(TokenManager, ABC):
91
83
  kwargs['verify'] = False
92
84
 
93
85
  response = requests.request(
94
- method=method,
95
- url=url,
96
- headers=headers,
97
- params=params,
98
- data=data,
99
- auth=auth_tuple,
100
- **kwargs)
86
+ method=method, url=url, headers=headers, params=params, data=data, auth=auth_tuple, **kwargs
87
+ )
101
88
  if 200 <= response.status_code <= 299:
102
89
  return response.json()
103
90
 
@@ -0,0 +1,108 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2023, 2024 IBM All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import json
18
+ from typing import Dict, Optional
19
+
20
+ from ibm_cloud_sdk_core.logger import get_logger
21
+ from ..private_helpers import _build_user_agent
22
+ from .jwt_token_manager import JWTTokenManager
23
+
24
+ logger = get_logger()
25
+
26
+
27
+ class MCSPTokenManager(JWTTokenManager):
28
+ """The MCSPTokenManager accepts a user-supplied apikey and performs the necessary interactions with
29
+ the Multi-Cloud Saas Platform (MCSP) token service to obtain an MCSP access token (a bearer token).
30
+ When the access token expires, a new access token is obtained from the token server.
31
+
32
+ Keyword Arguments:
33
+ apikey: The apikey for authentication [required].
34
+ url: The endpoint for JWT token requests [required].
35
+ disable_ssl_verification: Disable ssl verification. Defaults to False.
36
+ headers: Headers to be sent with every service token request. Defaults to None.
37
+ proxies: Proxies to use for making request. Defaults to None.
38
+ proxies.http (optional): The proxy endpoint to use for HTTP requests.
39
+ proxies.https (optional): The proxy endpoint to use for HTTPS requests.
40
+ """
41
+
42
+ TOKEN_NAME = 'token'
43
+ OPERATION_PATH = '/siusermgr/api/1.0/apikeys/token'
44
+
45
+ def __init__(
46
+ self,
47
+ apikey: str,
48
+ url: str,
49
+ *,
50
+ disable_ssl_verification: bool = False,
51
+ headers: Optional[Dict[str, str]] = None,
52
+ proxies: Optional[Dict[str, str]] = None,
53
+ ) -> None:
54
+ self.apikey = apikey
55
+ self.headers = headers
56
+ if self.headers is None:
57
+ self.headers = {}
58
+ self.headers['Content-Type'] = 'application/json'
59
+ self.headers['Accept'] = 'application/json'
60
+ self.proxies = proxies
61
+ super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME)
62
+ self._set_user_agent(_build_user_agent('mcsp-authenticator'))
63
+
64
+ def request_token(self) -> dict:
65
+ """Makes a request for a token."""
66
+ required_headers = {
67
+ 'User-Agent': self.user_agent,
68
+ }
69
+ request_headers = {}
70
+ if self.headers is not None and isinstance(self.headers, dict):
71
+ request_headers.update(self.headers)
72
+ request_headers.update(required_headers)
73
+
74
+ request_url = self.url + self.OPERATION_PATH
75
+ logger.debug('Invoking MCSP token service operation: %s', request_url)
76
+ response = self._request(
77
+ method='POST',
78
+ headers=request_headers,
79
+ url=request_url,
80
+ data=json.dumps({"apikey": self.apikey}),
81
+ proxies=self.proxies,
82
+ )
83
+ logger.debug('Returned from MCSP token service operation')
84
+ return response
85
+
86
+ def set_headers(self, headers: Dict[str, str]) -> None:
87
+ """Headers to be sent with every MCSP token request.
88
+
89
+ Args:
90
+ headers: The headers to be sent with every MCSP token request.
91
+ """
92
+ if isinstance(headers, dict):
93
+ self.headers = headers
94
+ else:
95
+ raise TypeError('headers must be a dictionary')
96
+
97
+ def set_proxies(self, proxies: Dict[str, str]) -> None:
98
+ """Sets the proxies the token manager will use to communicate with MCSP on behalf of the host.
99
+
100
+ Args:
101
+ proxies: Proxies to use for making request. Defaults to None.
102
+ proxies.http (optional): The proxy endpoint to use for HTTP requests.
103
+ proxies.https (optional): The proxy endpoint to use for HTTPS requests.
104
+ """
105
+ if isinstance(proxies, dict):
106
+ self.proxies = proxies
107
+ else:
108
+ raise TypeError('proxies must be a dictionary')
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2020 IBM All Rights Reserved.
3
+ # Copyright 2020, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -21,8 +21,11 @@ from threading import Lock
21
21
 
22
22
  import requests
23
23
 
24
+ from ibm_cloud_sdk_core.logger import get_logger
24
25
  from ..api_exception import ApiException
25
26
 
27
+ logger = get_logger()
28
+
26
29
 
27
30
  # pylint: disable=too-many-instance-attributes
28
31
  class TokenManager(ABC):
@@ -49,14 +52,10 @@ class TokenManager(ABC):
49
52
  lock (Lock): Lock variable to serialize access to refresh/request times
50
53
  http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests.
51
54
  access_token (str): The latest stored access token
55
+ user_agent (str): The User-Agent header value to be included in each outbound token request
52
56
  """
53
57
 
54
- def __init__(
55
- self,
56
- url: str,
57
- *,
58
- disable_ssl_verification: bool = False
59
- ):
58
+ def __init__(self, url: str, *, disable_ssl_verification: bool = False):
60
59
  self.url = url
61
60
  self.disable_ssl_verification = disable_ssl_verification
62
61
  self.expire_time = 0
@@ -65,6 +64,7 @@ class TokenManager(ABC):
65
64
  self.lock = Lock()
66
65
  self.http_config = {}
67
66
  self.access_token = None
67
+ self.user_agent = None
68
68
 
69
69
  def get_token(self) -> str:
70
70
  """Get a token to be used for authentication.
@@ -78,11 +78,15 @@ class TokenManager(ABC):
78
78
  str: A valid access token
79
79
  """
80
80
  if self._is_token_expired():
81
+ logger.debug('Performing synchronous token fetch')
81
82
  self.paced_request_token()
82
83
 
83
84
  if self._token_needs_refresh():
85
+ logger.debug('Performing background asynchronous token fetch')
84
86
  token_response = self.request_token()
85
87
  self._save_token_info(token_response)
88
+ else:
89
+ logger.debug('Using cached access token')
86
90
 
87
91
  return self.access_token
88
92
 
@@ -100,6 +104,12 @@ class TokenManager(ABC):
100
104
  else:
101
105
  raise TypeError('status must be a bool')
102
106
 
107
+ def _set_user_agent(self, user_agent: str = None) -> None:
108
+ self.user_agent = user_agent
109
+
110
+ def _get_user_agent(self) -> str:
111
+ return self.user_agent
112
+
103
113
  def paced_request_token(self) -> None:
104
114
  """
105
115
  Paces requests to request_token.
@@ -190,15 +200,7 @@ class TokenManager(ABC):
190
200
  """
191
201
  pass
192
202
 
193
- def _request(self,
194
- method,
195
- url,
196
- *,
197
- headers=None,
198
- params=None,
199
- data=None,
200
- auth_tuple=None,
201
- **kwargs):
203
+ def _request(self, method, url, *, headers=None, params=None, data=None, auth_tuple=None, **kwargs):
202
204
  kwargs = dict({"timeout": 60}, **kwargs)
203
205
  kwargs = dict(kwargs, **self.http_config)
204
206
 
@@ -206,13 +208,8 @@ class TokenManager(ABC):
206
208
  kwargs['verify'] = False
207
209
 
208
210
  response = requests.request(
209
- method=method,
210
- url=url,
211
- headers=headers,
212
- params=params,
213
- data=data,
214
- auth=auth_tuple,
215
- **kwargs)
211
+ method=method, url=url, headers=headers, params=params, data=data, auth=auth_tuple, **kwargs
212
+ )
216
213
  if 200 <= response.status_code <= 299:
217
214
  return response
218
215
 
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2021 IBM All Rights Reserved.
3
+ # Copyright 2021, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -15,13 +15,13 @@
15
15
  # limitations under the License.
16
16
 
17
17
  import json
18
- import logging
19
18
  from typing import Optional
20
19
 
20
+ from ibm_cloud_sdk_core.logger import get_logger
21
+ from ..private_helpers import _build_user_agent
21
22
  from .jwt_token_manager import JWTTokenManager
22
23
 
23
-
24
- logger = logging.getLogger(__name__)
24
+ logger = get_logger()
25
25
 
26
26
 
27
27
  class VPCInstanceTokenManager(JWTTokenManager):
@@ -52,18 +52,19 @@ class VPCInstanceTokenManager(JWTTokenManager):
52
52
  url (str, optional): The VPC Instance Metadata Service's base endpoint URL.
53
53
  """
54
54
 
55
- METADATA_SERVICE_VERSION = '2021-09-20'
55
+ METADATA_SERVICE_VERSION = '2022-03-01'
56
56
  DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
57
57
  TOKEN_NAME = 'access_token'
58
+ IAM_EXPIRATION_WINDOW = 10
58
59
 
59
- def __init__(self,
60
- iam_profile_crn: Optional[str] = None,
61
- iam_profile_id: Optional[str] = None,
62
- url: Optional[str] = None) -> None:
60
+ def __init__(
61
+ self, iam_profile_crn: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None
62
+ ) -> None:
63
63
  if not url:
64
64
  url = self.DEFAULT_IMS_ENDPOINT
65
65
 
66
66
  super().__init__(url, token_name=self.TOKEN_NAME)
67
+ self._set_user_agent(_build_user_agent('vpc-instance-authenticator'))
67
68
 
68
69
  self.iam_profile_crn = iam_profile_crn
69
70
  self.iam_profile_id = iam_profile_id
@@ -91,18 +92,19 @@ class VPCInstanceTokenManager(JWTTokenManager):
91
92
  headers = {
92
93
  'Content-Type': 'application/json',
93
94
  'Accept': 'application/json',
94
- 'Authorization': 'Bearer ' + instance_identity_token
95
+ 'Authorization': 'Bearer ' + instance_identity_token,
96
+ 'User-Agent': self._get_user_agent(),
95
97
  }
96
98
 
97
- logger.debug(
98
- 'Invoking VPC \'create_iam_token\' operation: %s', url)
99
+ logger.debug('Invoking VPC \'create_iam_token\' operation: %s', url)
99
100
  response = self._request(
100
101
  method='POST',
101
102
  url=url,
102
103
  headers=headers,
103
104
  params={'version': self.METADATA_SERVICE_VERSION},
104
- data=json.dumps(request_payload) if request_payload else None)
105
- logger.debug('Returned from VPC \'create_iam_token\' operation."')
105
+ data=json.dumps(request_payload) if request_payload else None,
106
+ )
107
+ logger.debug('Returned from VPC \'create_iam_token\' operation.')
106
108
 
107
109
  return response
108
110
 
@@ -138,18 +140,35 @@ class VPCInstanceTokenManager(JWTTokenManager):
138
140
  'Content-type': 'application/json',
139
141
  'Accept': 'application/json',
140
142
  'Metadata-Flavor': 'ibm',
143
+ 'User-Agent': self._get_user_agent(),
141
144
  }
142
145
 
143
146
  request_body = {'expires_in': 300}
144
147
 
145
- logger.debug(
146
- 'Invoking VPC \'create_access_token\' operation: %s', url)
148
+ logger.debug('Invoking VPC \'create_access_token\' operation: %s', url)
147
149
  response = self._request(
148
150
  method='PUT',
149
151
  url=url,
150
152
  headers=headers,
151
153
  params={'version': self.METADATA_SERVICE_VERSION},
152
- data=json.dumps(request_body))
153
- logger.debug('Returned from VPC \'create_access_token\' operation."')
154
+ data=json.dumps(request_body),
155
+ )
156
+ logger.debug('Returned from VPC \'create_access_token\' operation.')
154
157
 
155
158
  return response['access_token']
159
+
160
+ def _is_token_expired(self) -> bool:
161
+ """
162
+ Returns true iff the current cached token is expired.
163
+ We'll consider an access token as expired when we reach its IAM server-reported expiration time
164
+ minus our expiration window (10 secs).
165
+ We do this to avoid using an access token that might expire in the middle of a long-running transaction
166
+ within an IBM Cloud service.
167
+
168
+ Returns
169
+ -------
170
+ bool
171
+ True if token is expired; False otherwise
172
+ """
173
+ current_time = self._get_current_time()
174
+ return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW)