ibm-cloud-sdk-core 3.19.1__tar.gz → 3.20.0__tar.gz

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 (70) hide show
  1. {ibm-cloud-sdk-core-3.19.1/ibm_cloud_sdk_core.egg-info → ibm-cloud-sdk-core-3.20.0}/PKG-INFO +2 -2
  2. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/README.md +1 -1
  3. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/api_exception.py +1 -1
  4. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/base_service.py +3 -14
  5. ibm-cloud-sdk-core-3.20.0/ibm_cloud_sdk_core/private_helpers.py +34 -0
  6. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/container_token_manager.py +3 -1
  7. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +12 -2
  8. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +27 -4
  9. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/iam_token_manager.py +4 -1
  10. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +12 -2
  11. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/token_manager.py +9 -1
  12. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +22 -1
  13. ibm-cloud-sdk-core-3.20.0/ibm_cloud_sdk_core/version.py +1 -0
  14. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0/ibm_cloud_sdk_core.egg-info}/PKG-INFO +2 -2
  15. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core.egg-info/SOURCES.txt +1 -0
  16. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/requirements-dev.txt +1 -1
  17. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/setup.py +1 -1
  18. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_base_service.py +18 -2
  19. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_container_token_manager.py +40 -12
  20. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_cp4d_token_manager.py +17 -0
  21. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_iam_token_manager.py +83 -0
  22. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_mcsp_token_manager.py +17 -0
  23. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_vpc_instance_token_manager.py +61 -4
  24. ibm-cloud-sdk-core-3.19.1/ibm_cloud_sdk_core/version.py +0 -1
  25. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/LICENSE +0 -0
  26. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/MANIFEST.in +0 -0
  27. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/__init__.py +0 -0
  28. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/__init__.py +0 -0
  29. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/authenticator.py +0 -0
  30. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/basic_authenticator.py +0 -0
  31. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +0 -0
  32. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/container_authenticator.py +0 -0
  33. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +0 -0
  34. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/iam_authenticator.py +0 -0
  35. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +0 -0
  36. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +0 -0
  37. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/no_auth_authenticator.py +0 -0
  38. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +0 -0
  39. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/detailed_response.py +0 -0
  40. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/get_authenticator.py +0 -0
  41. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/__init__.py +0 -0
  42. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +0 -0
  43. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core/utils.py +0 -0
  44. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core.egg-info/dependency_links.txt +0 -0
  45. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core.egg-info/requires.txt +0 -0
  46. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core.egg-info/top_level.txt +0 -0
  47. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/ibm_cloud_sdk_core.egg-info/zip-safe +0 -0
  48. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/pyproject.toml +0 -0
  49. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/requirements.txt +0 -0
  50. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/setup.cfg +0 -0
  51. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/__init__.py +0 -0
  52. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_api_exception.py +0 -0
  53. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_authenticator.py +0 -0
  54. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_basic_authenticator.py +0 -0
  55. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_bearer_authenticator.py +0 -0
  56. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_container_authenticator.py +0 -0
  57. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_cp4d_authenticator.py +0 -0
  58. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_detailed_response.py +0 -0
  59. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_iam_authenticator.py +0 -0
  60. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_jwt_token_manager.py +0 -0
  61. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_mcsp_authenticator.py +0 -0
  62. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_no_auth_authenticator.py +0 -0
  63. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_token_manager.py +0 -0
  64. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_utils.py +0 -0
  65. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test/test_vpc_instance_authenticator.py +0 -0
  66. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test_integration/__init__.py +0 -0
  67. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test_integration/test_cp4d_authenticator_integration.py +0 -0
  68. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test_integration/test_iam_authenticator_integration.py +0 -0
  69. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test_integration/test_mcsp_authenticator_integration.py +0 -0
  70. {ibm-cloud-sdk-core-3.19.1 → ibm-cloud-sdk-core-3.20.0}/test_integration/test_ssl_verification.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ibm-cloud-sdk-core
3
- Version: 3.19.1
3
+ Version: 3.20.0
4
4
  Summary: Core library used by SDKs for IBM Cloud Services
5
5
  Home-page: https://github.com/IBM/python-sdk-core
6
6
  Author: IBM
@@ -29,7 +29,7 @@ License-File: LICENSE
29
29
  [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core)
30
30
  [![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)
31
31
 
32
- # IBM Python SDK Core Version 3.19.1
32
+ # IBM Python SDK Core Version 3.20.0
33
33
  This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator
34
34
  (openapi-sdkgen).
35
35
 
@@ -4,7 +4,7 @@
4
4
  [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core)
5
5
  [![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)
6
6
 
7
- # IBM Python SDK Core Version 3.19.1
7
+ # IBM Python SDK Core Version 3.20.0
8
8
  This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator
9
9
  (openapi-sdkgen).
10
10
 
@@ -54,7 +54,7 @@ class ApiException(Exception):
54
54
  """The old `code` property with a deprecation warning."""
55
55
 
56
56
  warnings.warn(
57
- 'Using the `code` attribute on the `ApiException` is deprecated and'
57
+ 'Using the `code` attribute on the `ApiException` is deprecated and '
58
58
  'will be removed in the future. Use `status_code` instead.',
59
59
  DeprecationWarning,
60
60
  )
@@ -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.
@@ -18,7 +18,6 @@ import gzip
18
18
  import io
19
19
  import json as json_import
20
20
  import logging
21
- import platform
22
21
  from http.cookiejar import CookieJar
23
22
  from os.path import basename
24
23
  from typing import Dict, List, Optional, Tuple, Union
@@ -42,7 +41,7 @@ from .utils import (
42
41
  SSLHTTPAdapter,
43
42
  GzipStream,
44
43
  )
45
- from .version import __version__
44
+ from .private_helpers import _build_user_agent
46
45
 
47
46
  # Uncomment this to enable http debugging
48
47
  # import http.client as http_client
@@ -82,7 +81,6 @@ class BaseService:
82
81
  ValueError: If Authenticator is not provided or invalid type.
83
82
  """
84
83
 
85
- SDK_NAME = 'ibm-python-sdk-core'
86
84
  ERROR_MSG_DISABLE_SSL = (
87
85
  'The connection failed because the SSL certificate is not valid. To use a self-signed '
88
86
  'certificate, disable verification of the server\'s SSL certificate by invoking the '
@@ -106,7 +104,7 @@ class BaseService:
106
104
  self.disable_ssl_verification = disable_ssl_verification
107
105
  self.default_headers = None
108
106
  self.enable_gzip_compression = enable_gzip_compression
109
- self._set_user_agent_header(self._build_user_agent())
107
+ self._set_user_agent_header(_build_user_agent())
110
108
  self.retry_config = None
111
109
  self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
112
110
  if not self.authenticator:
@@ -151,15 +149,6 @@ class BaseService:
151
149
  self.http_client.mount('http://', self.http_adapter)
152
150
  self.http_client.mount('https://', self.http_adapter)
153
151
 
154
- @staticmethod
155
- def _get_system_info() -> str:
156
- return '{0} {1} {2}'.format(
157
- platform.system(), platform.release(), platform.python_version() # OS # OS version # Python version
158
- )
159
-
160
- def _build_user_agent(self) -> str:
161
- return '{0}-{1} {2}'.format(self.SDK_NAME, __version__, self._get_system_info())
162
-
163
152
  def configure_service(self, service_name: str) -> None:
164
153
  """Look for external configuration of a service. Set service properties.
165
154
 
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 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
+ # from ibm_cloud_sdk_core.authenticators import Authenticator
17
+
18
+ import platform
19
+ from .version import __version__
20
+
21
+ SDK_NAME = 'ibm-python-sdk-core'
22
+
23
+
24
+ def _get_system_info() -> str:
25
+ return 'os.name={0} os.version={1} python.version={2}'.format(
26
+ platform.system(), platform.release(), platform.python_version()
27
+ )
28
+
29
+
30
+ def _build_user_agent(component: str = None) -> str:
31
+ sub_component = ""
32
+ if component is not None:
33
+ sub_component = '/{0}'.format(component)
34
+ return '{0}{1}-{2} {3}'.format(SDK_NAME, sub_component, __version__, _get_system_info())
@@ -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.
@@ -18,6 +18,7 @@ import logging
18
18
  from typing import Dict, Optional
19
19
 
20
20
  from .iam_request_based_token_manager import IAMRequestBasedTokenManager
21
+ from ..private_helpers import _build_user_agent
21
22
 
22
23
 
23
24
  logger = logging.getLogger(__name__)
@@ -111,6 +112,7 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
111
112
  self.iam_profile_id = iam_profile_id
112
113
 
113
114
  self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:cr-token'
115
+ self._set_user_agent(_build_user_agent('container-authenticator'))
114
116
 
115
117
  def retrieve_cr_token(self) -> str:
116
118
  """Retrieves the CR token for the current compute resource by reading it from the local file system.
@@ -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
  import json
18
18
  from typing import Dict, Optional
19
19
 
20
+ from ..private_helpers import _build_user_agent
20
21
  from .jwt_token_manager import JWTTokenManager
21
22
 
22
23
 
@@ -76,12 +77,21 @@ class CP4DTokenManager(JWTTokenManager):
76
77
  self.headers['Content-Type'] = 'application/json'
77
78
  self.proxies = proxies
78
79
  super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME)
80
+ self._set_user_agent(_build_user_agent('cp4d-authenticator'))
79
81
 
80
82
  def request_token(self) -> dict:
81
83
  """Makes a request for a token."""
84
+ required_headers = {
85
+ 'User-Agent': self.user_agent,
86
+ }
87
+ request_headers = {}
88
+ if self.headers is not None and isinstance(self.headers, dict):
89
+ request_headers.update(self.headers)
90
+ request_headers.update(required_headers)
91
+
82
92
  response = self._request(
83
93
  method='POST',
84
- headers=self.headers,
94
+ headers=request_headers,
85
95
  url=self.url,
86
96
  data=json.dumps({"username": self.username, "password": self.password, "api_key": self.apikey}),
87
97
  proxies=self.proxies,
@@ -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.
@@ -63,6 +63,7 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
63
63
 
64
64
  DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com'
65
65
  OPERATION_PATH = "/identity/token"
66
+ IAM_EXPIRATION_WINDOW = 10
66
67
 
67
68
  def __init__(
68
69
  self,
@@ -97,9 +98,15 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
97
98
  Returns:
98
99
  A dictionary containing the bearer token to be subsequently used service requests.
99
100
  """
100
- headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}
101
+ required_headers = {
102
+ 'Content-Type': 'application/x-www-form-urlencoded',
103
+ 'Accept': 'application/json',
104
+ 'User-Agent': self._get_user_agent(),
105
+ }
106
+ request_headers = {}
101
107
  if self.headers is not None and isinstance(self.headers, dict):
102
- headers.update(self.headers)
108
+ request_headers.update(self.headers)
109
+ request_headers.update(required_headers)
103
110
 
104
111
  data = dict(self.request_payload)
105
112
 
@@ -114,7 +121,7 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
114
121
  response = self._request(
115
122
  method='POST',
116
123
  url=(self.url + self.OPERATION_PATH) if self.url else self.url,
117
- headers=headers,
124
+ headers=request_headers,
118
125
  data=data,
119
126
  auth_tuple=auth_tuple,
120
127
  proxies=self.proxies,
@@ -167,3 +174,19 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
167
174
  value: A space seperated string that makes up the scope parameter.
168
175
  """
169
176
  self.scope = value
177
+
178
+ def _is_token_expired(self) -> bool:
179
+ """
180
+ Returns true iff the current cached token is expired.
181
+ We'll consider an access token as expired when we reach its IAM server-reported expiration time
182
+ minus our expiration window (10 secs).
183
+ We do this to avoid using an access token that might expire in the middle of a long-running transaction
184
+ within an IBM Cloud service.
185
+
186
+ Returns
187
+ -------
188
+ bool
189
+ True if token is expired; False otherwise
190
+ """
191
+ current_time = self._get_current_time()
192
+ return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW)
@@ -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):
@@ -88,3 +89,5 @@ class IAMTokenManager(IAMRequestBasedTokenManager):
88
89
  self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:apikey'
89
90
  self.request_payload['apikey'] = self.apikey
90
91
  self.request_payload['response_type'] = 'cloud_iam'
92
+
93
+ self._set_user_agent(_build_user_agent('iam-authenticator'))
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2023 IBM All Rights Reserved.
3
+ # Copyright 2023, 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
  import json
18
18
  from typing import Dict, Optional
19
19
 
20
+ from ..private_helpers import _build_user_agent
20
21
  from .jwt_token_manager import JWTTokenManager
21
22
 
22
23
 
@@ -55,12 +56,21 @@ class MCSPTokenManager(JWTTokenManager):
55
56
  self.headers['Accept'] = 'application/json'
56
57
  self.proxies = proxies
57
58
  super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME)
59
+ self._set_user_agent(_build_user_agent('mcsp-authenticator'))
58
60
 
59
61
  def request_token(self) -> dict:
60
62
  """Makes a request for a token."""
63
+ required_headers = {
64
+ 'User-Agent': self.user_agent,
65
+ }
66
+ request_headers = {}
67
+ if self.headers is not None and isinstance(self.headers, dict):
68
+ request_headers.update(self.headers)
69
+ request_headers.update(required_headers)
70
+
61
71
  response = self._request(
62
72
  method='POST',
63
- headers=self.headers,
73
+ headers=request_headers,
64
74
  url=self.url + self.OPERATION_PATH,
65
75
  data=json.dumps({"apikey": self.apikey}),
66
76
  proxies=self.proxies,
@@ -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.
@@ -49,6 +49,7 @@ class TokenManager(ABC):
49
49
  lock (Lock): Lock variable to serialize access to refresh/request times
50
50
  http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests.
51
51
  access_token (str): The latest stored access token
52
+ user_agent (str): The User-Agent header value to be included in each outbound token request
52
53
  """
53
54
 
54
55
  def __init__(self, url: str, *, disable_ssl_verification: bool = False):
@@ -60,6 +61,7 @@ class TokenManager(ABC):
60
61
  self.lock = Lock()
61
62
  self.http_config = {}
62
63
  self.access_token = None
64
+ self.user_agent = None
63
65
 
64
66
  def get_token(self) -> str:
65
67
  """Get a token to be used for authentication.
@@ -95,6 +97,12 @@ class TokenManager(ABC):
95
97
  else:
96
98
  raise TypeError('status must be a bool')
97
99
 
100
+ def _set_user_agent(self, user_agent: str = None) -> None:
101
+ self.user_agent = user_agent
102
+
103
+ def _get_user_agent(self) -> str:
104
+ return self.user_agent
105
+
98
106
  def paced_request_token(self) -> None:
99
107
  """
100
108
  Paces requests to request_token.
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2021, 2023 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.
@@ -18,6 +18,7 @@ import json
18
18
  import logging
19
19
  from typing import Optional
20
20
 
21
+ from ..private_helpers import _build_user_agent
21
22
  from .jwt_token_manager import JWTTokenManager
22
23
 
23
24
 
@@ -55,6 +56,7 @@ class VPCInstanceTokenManager(JWTTokenManager):
55
56
  METADATA_SERVICE_VERSION = '2022-03-01'
56
57
  DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
57
58
  TOKEN_NAME = 'access_token'
59
+ IAM_EXPIRATION_WINDOW = 10
58
60
 
59
61
  def __init__(
60
62
  self, iam_profile_crn: Optional[str] = None, iam_profile_id: Optional[str] = None, url: Optional[str] = None
@@ -63,6 +65,7 @@ class VPCInstanceTokenManager(JWTTokenManager):
63
65
  url = self.DEFAULT_IMS_ENDPOINT
64
66
 
65
67
  super().__init__(url, token_name=self.TOKEN_NAME)
68
+ self._set_user_agent(_build_user_agent('vpc-instance-authenticator'))
66
69
 
67
70
  self.iam_profile_crn = iam_profile_crn
68
71
  self.iam_profile_id = iam_profile_id
@@ -91,6 +94,7 @@ class VPCInstanceTokenManager(JWTTokenManager):
91
94
  'Content-Type': 'application/json',
92
95
  'Accept': 'application/json',
93
96
  'Authorization': 'Bearer ' + instance_identity_token,
97
+ 'User-Agent': self._get_user_agent(),
94
98
  }
95
99
 
96
100
  logger.debug('Invoking VPC \'create_iam_token\' operation: %s', url)
@@ -137,6 +141,7 @@ class VPCInstanceTokenManager(JWTTokenManager):
137
141
  'Content-type': 'application/json',
138
142
  'Accept': 'application/json',
139
143
  'Metadata-Flavor': 'ibm',
144
+ 'User-Agent': self._get_user_agent(),
140
145
  }
141
146
 
142
147
  request_body = {'expires_in': 300}
@@ -152,3 +157,19 @@ class VPCInstanceTokenManager(JWTTokenManager):
152
157
  logger.debug('Returned from VPC \'create_access_token\' operation."')
153
158
 
154
159
  return response['access_token']
160
+
161
+ def _is_token_expired(self) -> bool:
162
+ """
163
+ Returns true iff the current cached token is expired.
164
+ We'll consider an access token as expired when we reach its IAM server-reported expiration time
165
+ minus our expiration window (10 secs).
166
+ We do this to avoid using an access token that might expire in the middle of a long-running transaction
167
+ within an IBM Cloud service.
168
+
169
+ Returns
170
+ -------
171
+ bool
172
+ True if token is expired; False otherwise
173
+ """
174
+ current_time = self._get_current_time()
175
+ return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW)
@@ -0,0 +1 @@
1
+ __version__ = '3.20.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ibm-cloud-sdk-core
3
- Version: 3.19.1
3
+ Version: 3.20.0
4
4
  Summary: Core library used by SDKs for IBM Cloud Services
5
5
  Home-page: https://github.com/IBM/python-sdk-core
6
6
  Author: IBM
@@ -29,7 +29,7 @@ License-File: LICENSE
29
29
  [![CLA assistant](https://cla-assistant.io/readme/badge/ibm/python-sdk-core)](https://cla-assistant.io/ibm/python-sdk-core)
30
30
  [![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)
31
31
 
32
- # IBM Python SDK Core Version 3.19.1
32
+ # IBM Python SDK Core Version 3.20.0
33
33
  This project contains core functionality required by Python code generated by the IBM Cloud OpenAPI SDK Generator
34
34
  (openapi-sdkgen).
35
35
 
@@ -10,6 +10,7 @@ ibm_cloud_sdk_core/api_exception.py
10
10
  ibm_cloud_sdk_core/base_service.py
11
11
  ibm_cloud_sdk_core/detailed_response.py
12
12
  ibm_cloud_sdk_core/get_authenticator.py
13
+ ibm_cloud_sdk_core/private_helpers.py
13
14
  ibm_cloud_sdk_core/utils.py
14
15
  ibm_cloud_sdk_core/version.py
15
16
  ibm_cloud_sdk_core.egg-info/PKG-INFO
@@ -3,4 +3,4 @@ pylint>=3.0.0,<4.0.0
3
3
  pytest>=7.4.2,<8.0.0
4
4
  pytest-cov>=4.1.0,<5.0.0
5
5
  responses>=0.23.3,<1.0.0
6
- black>=23.9.1
6
+ black>=24.0.0,<25.0.0
@@ -19,7 +19,7 @@ import pkg_resources
19
19
  from setuptools import setup, find_packages
20
20
  from setuptools.command.test import test as TestCommand
21
21
 
22
- __version__ = '3.19.1'
22
+ __version__ = '3.20.0'
23
23
 
24
24
  if sys.argv[-1] == 'publish':
25
25
  # test server
@@ -1,5 +1,21 @@
1
- # coding=utf-8
1
+ # coding: utf-8
2
+
3
+ # Copyright 2019, 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
+
2
17
  # pylint: disable=missing-docstring,protected-access,too-few-public-methods,too-many-lines
18
+
3
19
  import gzip
4
20
  import json
5
21
  import os
@@ -802,7 +818,7 @@ def test_user_agent_header():
802
818
  service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
803
819
  user_agent_header = service.user_agent_header
804
820
  assert user_agent_header is not None
805
- assert user_agent_header['User-Agent'] is not None
821
+ assert user_agent_header['User-Agent'].startswith('ibm-python-sdk-core-')
806
822
 
807
823
  responses.add(responses.GET, 'https://gateway.watsonplatform.net/test/api', status=200, body='some text')
808
824
  prepped = service.prepare_request('GET', url='', headers={'user-agent': 'my_user_agent'})
@@ -1,3 +1,19 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2021, 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
+
1
17
  # pylint: disable=missing-docstring
2
18
  import json
3
19
  import os
@@ -17,6 +33,7 @@ TEST_REFRESH_TOKEN = 'Xj7Gle500MachEOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI
17
33
  MOCK_IAM_PROFILE_NAME = 'iam-user-123'
18
34
  MOCK_CLIENT_ID = 'client-id-1'
19
35
  MOCK_CLIENT_SECRET = 'client-secret-1'
36
+ EXPIRATION_WINDOW = 10
20
37
 
21
38
  cr_token_file = os.path.join(os.path.dirname(__file__), '../resources/cr-token.txt')
22
39
 
@@ -94,6 +111,9 @@ def test_request_token_auth_default():
94
111
  assert len(responses.calls) == 1
95
112
  assert responses.calls[0].request.url == iam_url
96
113
  assert responses.calls[0].request.headers.get('Authorization') is None
114
+ assert (
115
+ responses.calls[0].request.headers.get('User-Agent').startswith('ibm-python-sdk-core/container-authenticator')
116
+ )
97
117
  assert json.loads(responses.calls[0].response.text)['access_token'] == TEST_ACCESS_TOKEN_1
98
118
 
99
119
 
@@ -169,18 +189,22 @@ def test_get_token_success():
169
189
  assert access_token == TEST_ACCESS_TOKEN_1
170
190
  assert token_manager.access_token == TEST_ACCESS_TOKEN_1
171
191
 
172
- # Verify the token manager return the cached value.
173
- # Before we call the `get_token` again, set the expiration and time.
174
- # This is necessary because we are using a fix JWT response.
175
- token_manager.expire_time = _get_current_time() + 3600
176
- token_manager.refresh_time = _get_current_time() + 3600
192
+ # Verify that the token manager returns the cached value.
193
+ # Before we call `get_token` again, set the expiration and refresh time
194
+ # so that we do not fetch a new access token.
195
+ # This is necessary because we are using a fixed JWT response.
196
+ token_manager.expire_time = _get_current_time() + 1000
197
+ token_manager.refresh_time = _get_current_time() + 1000
177
198
  token_manager.set_scope('send-second-token')
178
199
  access_token = token_manager.get_token()
179
200
  assert access_token == TEST_ACCESS_TOKEN_1
180
201
  assert token_manager.access_token == TEST_ACCESS_TOKEN_1
181
202
 
182
203
  # Force expiration to get the second token.
183
- token_manager.expire_time = _get_current_time() - 1
204
+ # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs)
205
+ # because we want the access token to be considered as "expired"
206
+ # when we reach the IAM-server reported expiration time minus 10 secs.
207
+ token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW
184
208
  access_token = token_manager.get_token()
185
209
  assert access_token == TEST_ACCESS_TOKEN_2
186
210
  assert token_manager.access_token == TEST_ACCESS_TOKEN_2
@@ -206,17 +230,21 @@ def test_authenticate_success():
206
230
  authenticator.authenticate(request)
207
231
  assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_1
208
232
 
209
- # Verify the token manager return the cached value.
210
- # Before we call the `get_token` again, set the expiration and time.
211
- # This is necessary because we are using a fix JWT response.
212
- authenticator.token_manager.expire_time = _get_current_time() + 3600
213
- authenticator.token_manager.refresh_time = _get_current_time() + 3600
233
+ # Verify that the token manager returns the cached value.
234
+ # Before we call `get_token` again, set the expiration and refresh time
235
+ # so that we do not fetch a new access token.
236
+ # This is necessary because we are using a fixed JWT response.
237
+ authenticator.token_manager.expire_time = _get_current_time() + 1000
238
+ authenticator.token_manager.refresh_time = _get_current_time() + 1000
214
239
  authenticator.token_manager.set_scope('send-second-token')
215
240
  authenticator.authenticate(request)
216
241
  assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_1
217
242
 
218
243
  # Force expiration to get the second token.
219
- authenticator.token_manager.expire_time = _get_current_time() - 1
244
+ # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs)
245
+ # because we want the access token to be considered as "expired"
246
+ # when we reach the IAM-server reported expiration time minus 10 secs.
247
+ authenticator.token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW
220
248
  authenticator.authenticate(request)
221
249
  assert request['headers']['Authorization'] == 'Bearer ' + TEST_ACCESS_TOKEN_2
222
250
 
@@ -1,3 +1,19 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2019, 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
+
1
17
  # pylint: disable=missing-docstring
2
18
  import json
3
19
  import time
@@ -38,6 +54,7 @@ def test_request_token():
38
54
 
39
55
  assert len(responses.calls) == 1
40
56
  assert responses.calls[0].request.url == url + '/v1/authorize'
57
+ assert responses.calls[0].request.headers.get('User-Agent').startswith('ibm-python-sdk-core/cp4d-authenticator')
41
58
  assert token == access_token
42
59
 
43
60
  token_manager = CP4DTokenManager("username", "password", url + '/v1/authorize')
@@ -1,3 +1,19 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2019, 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
+
1
17
  # pylint: disable=missing-docstring
2
18
  import os
3
19
  import time
@@ -8,6 +24,16 @@ import responses
8
24
 
9
25
  from ibm_cloud_sdk_core import IAMTokenManager, ApiException, get_authenticator_from_environment
10
26
 
27
+ # pylint: disable=line-too-long
28
+ TEST_ACCESS_TOKEN_1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI'
29
+ TEST_ACCESS_TOKEN_2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJ1c2VybmFtZSI6ImR1bW15Iiwicm9sZSI6IkFkbWluIiwicGVybWlzc2lvbnMiOlsiYWRtaW5pc3RyYXRvciIsIm1hbmFnZV9jYXRhbG9nIl0sInN1YiI6ImFkbWluIiwiaXNzIjoic3NzIiwiYXVkIjoic3NzIiwidWlkIjoic3NzIiwiaWF0IjozNjAwLCJleHAiOjE2MjgwMDcwODF9.zvUDpgqWIWs7S1CuKv40ERw1IZ5FqSFqQXsrwZJyfRM'
30
+ TEST_REFRESH_TOKEN = 'Xj7Gle500MachEOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI'
31
+ EXPIRATION_WINDOW = 10
32
+
33
+
34
+ def _get_current_time() -> int:
35
+ return int(time.time())
36
+
11
37
 
12
38
  def get_access_token() -> str:
13
39
  access_token_layout = {
@@ -46,6 +72,7 @@ def test_request_token_auth_default():
46
72
  assert len(responses.calls) == 1
47
73
  assert responses.calls[0].request.url == iam_url
48
74
  assert responses.calls[0].request.headers.get('Authorization') is None
75
+ assert responses.calls[0].request.headers.get('User-Agent').startswith('ibm-python-sdk-core/iam-authenticator')
49
76
  assert responses.calls[0].response.text == response
50
77
 
51
78
 
@@ -268,6 +295,62 @@ def test_request_token_auth_in_setter_scope():
268
295
  assert 'scope=john+snow' in responses.calls[0].response.request.body
269
296
 
270
297
 
298
+ @responses.activate
299
+ def test_get_token_success():
300
+ iam_url = "https://iam.cloud.ibm.com/identity/token"
301
+
302
+ # Create two mock responses with different access tokens.
303
+ response1 = """{
304
+ "access_token": "%s",
305
+ "token_type": "Bearer",
306
+ "expires_in": 3600,
307
+ "expiration": 1600003600,
308
+ "refresh_token": "jy4gl91BQ"
309
+ }""" % (
310
+ TEST_ACCESS_TOKEN_1
311
+ )
312
+ response2 = """{
313
+ "access_token": "%s",
314
+ "token_type": "Bearer",
315
+ "expires_in": 3600,
316
+ "expiration": 1600007200,
317
+ "refresh_token": "jy4gl91BQ"
318
+ }""" % (
319
+ TEST_ACCESS_TOKEN_2
320
+ )
321
+
322
+ token_manager = IAMTokenManager("iam_apikey")
323
+
324
+ access_token = token_manager.access_token
325
+ assert access_token is None
326
+
327
+ responses.add(responses.POST, url=iam_url, body=response1, status=200)
328
+ access_token = token_manager.get_token()
329
+ assert access_token == TEST_ACCESS_TOKEN_1
330
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_1
331
+
332
+ # Verify that the token manager returns the cached value.
333
+ # Before we call `get_token` again, set the expiration and refresh time
334
+ # so that we do not fetch a new access token.
335
+ # This is necessary because we are using a fixed JWT response.
336
+ token_manager.expire_time = _get_current_time() + 1000
337
+ token_manager.refresh_time = _get_current_time() + 1000
338
+ access_token = token_manager.get_token()
339
+ assert access_token == TEST_ACCESS_TOKEN_1
340
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_1
341
+
342
+ # Force expiration to get the second token.
343
+ # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs)
344
+ # because we want the access token to be considered as "expired"
345
+ # when we reach the IAM-server reported expiration time minus 10 secs.
346
+ responses.add(responses.POST, url=iam_url, body=response2, status=200)
347
+ token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW
348
+ token_manager.refresh_time = _get_current_time() + 1000
349
+ access_token = token_manager.get_token()
350
+ assert access_token == TEST_ACCESS_TOKEN_2
351
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_2
352
+
353
+
271
354
  @responses.activate
272
355
  def test_get_refresh_token():
273
356
  iam_url = "https://iam.cloud.ibm.com/identity/token"
@@ -1,3 +1,19 @@
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
+
1
17
  # pylint: disable=missing-docstring
2
18
  import json
3
19
  import time
@@ -45,6 +61,7 @@ def test_request_token():
45
61
 
46
62
  assert len(responses.calls) == 1
47
63
  assert responses.calls[0].request.url == MOCK_URL + OPERATION_PATH
64
+ assert responses.calls[0].request.headers.get('User-Agent').startswith('ibm-python-sdk-core/mcsp-authenticator')
48
65
  assert token == access_token
49
66
 
50
67
 
@@ -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.
@@ -17,6 +17,7 @@
17
17
  # pylint: disable=missing-docstring
18
18
  import json
19
19
  import logging
20
+ import time
20
21
 
21
22
  import pytest
22
23
  import responses
@@ -25,11 +26,17 @@ from ibm_cloud_sdk_core import ApiException, VPCInstanceTokenManager
25
26
 
26
27
 
27
28
  # pylint: disable=line-too-long
28
- TEST_ACCESS_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI'
29
+ TEST_ACCESS_TOKEN_1 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI'
30
+ TEST_ACCESS_TOKEN_2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJ1c2VybmFtZSI6ImR1bW15Iiwicm9sZSI6IkFkbWluIiwicGVybWlzc2lvbnMiOlsiYWRtaW5pc3RyYXRvciIsIm1hbmFnZV9jYXRhbG9nIl0sInN1YiI6ImFkbWluIiwiaXNzIjoic3NzIiwiYXVkIjoic3NzIiwidWlkIjoic3NzIiwiaWF0IjozNjAwLCJleHAiOjE2MjgwMDcwODF9.zvUDpgqWIWs7S1CuKv40ERw1IZ5FqSFqQXsrwZJyfRM'
29
31
  TEST_TOKEN = 'abc123'
30
32
  TEST_IAM_TOKEN = 'iam-abc123'
31
33
  TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123'
32
34
  TEST_IAM_PROFILE_ID = 'iam-id-123'
35
+ EXPIRATION_WINDOW = 10
36
+
37
+
38
+ def _get_current_time() -> int:
39
+ return int(time.time())
33
40
 
34
41
 
35
42
  def test_constructor():
@@ -78,6 +85,7 @@ def test_retrieve_instance_identity_token(caplog):
78
85
  assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
79
86
  assert responses.calls[0].request.headers['Accept'] == 'application/json'
80
87
  assert responses.calls[0].request.headers['Metadata-Flavor'] == 'ibm'
88
+ assert responses.calls[0].request.headers['User-Agent'].startswith('ibm-python-sdk-core/vpc-instance-authenticator')
81
89
  assert responses.calls[0].request.params['version'] == '2022-03-01'
82
90
  assert responses.calls[0].request.body == '{"expires_in": 300}'
83
91
  assert ii_token == TEST_TOKEN
@@ -144,6 +152,7 @@ def test_request_token_with_crn(caplog):
144
152
  assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
145
153
  assert responses.calls[0].request.headers['Accept'] == 'application/json'
146
154
  assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
155
+ assert responses.calls[0].request.headers['User-Agent'].startswith('ibm-python-sdk-core/vpc-instance-authenticator')
147
156
  assert responses.calls[0].request.body == '{"trusted_profile": {"crn": "crn:iam-profile:123"}}'
148
157
  assert responses.calls[0].request.params['version'] == '2022-03-01'
149
158
  # Check the logs.
@@ -272,7 +281,7 @@ def test_access_token():
272
281
  'access_token': TEST_TOKEN,
273
282
  }
274
283
  response_iam = {
275
- 'access_token': TEST_ACCESS_TOKEN,
284
+ 'access_token': TEST_ACCESS_TOKEN_1,
276
285
  }
277
286
 
278
287
  responses.add(
@@ -290,6 +299,54 @@ def test_access_token():
290
299
  assert token_manager.refresh_time == 0
291
300
 
292
301
  token_manager.get_token()
293
- assert token_manager.access_token == TEST_ACCESS_TOKEN
302
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_1
294
303
  assert token_manager.expire_time > 0
295
304
  assert token_manager.refresh_time > 0
305
+
306
+
307
+ @responses.activate
308
+ def test_get_token_success():
309
+ token_manager = VPCInstanceTokenManager()
310
+
311
+ # Mock the retrieve instance identity token method.
312
+ def mock_retrieve_instance_identity_token():
313
+ return TEST_TOKEN
314
+
315
+ token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token
316
+
317
+ response1 = {
318
+ 'access_token': TEST_ACCESS_TOKEN_1,
319
+ }
320
+ response2 = {
321
+ 'access_token': TEST_ACCESS_TOKEN_2,
322
+ }
323
+
324
+ responses.add(
325
+ responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response1), status=200
326
+ )
327
+
328
+ access_token = token_manager.get_token()
329
+ assert access_token == TEST_ACCESS_TOKEN_1
330
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_1
331
+
332
+ # Verify that the token manager returns the cached value.
333
+ # Before we call `get_token` again, set the expiration and refresh time
334
+ # so that we do not fetch a new access token.
335
+ # This is necessary because we are using a fixed JWT response.
336
+ token_manager.expire_time = _get_current_time() + 1000
337
+ token_manager.refresh_time = _get_current_time() + 1000
338
+ access_token = token_manager.get_token()
339
+ assert access_token == TEST_ACCESS_TOKEN_1
340
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_1
341
+
342
+ # Force expiration to get the second token.
343
+ # We'll set the expiration time to be current-time + EXPIRATION_WINDOW (10 secs)
344
+ # because we want the access token to be considered as "expired"
345
+ # when we reach the IAM-server reported expiration time minus 10 secs.
346
+ responses.add(
347
+ responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token', body=json.dumps(response2), status=200
348
+ )
349
+ token_manager.expire_time = _get_current_time() + EXPIRATION_WINDOW
350
+ access_token = token_manager.get_token()
351
+ assert access_token == TEST_ACCESS_TOKEN_2
352
+ assert token_manager.access_token == TEST_ACCESS_TOKEN_2
@@ -1 +0,0 @@
1
- __version__ = '3.19.1'