ibm-cloud-sdk-core 3.16.0__py3-none-any.whl → 3.20.6__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 (56) 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 +5 -6
  6. ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +1 -1
  7. ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
  8. ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +33 -21
  9. ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
  10. ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +5 -7
  11. ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +130 -0
  12. ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +7 -10
  13. ibm_cloud_sdk_core/base_service.py +107 -91
  14. ibm_cloud_sdk_core/detailed_response.py +21 -15
  15. ibm_cloud_sdk_core/get_authenticator.py +28 -16
  16. ibm_cloud_sdk_core/http_adapter.py +28 -0
  17. ibm_cloud_sdk_core/private_helpers.py +34 -0
  18. ibm_cloud_sdk_core/token_managers/container_token_manager.py +61 -30
  19. ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +30 -22
  20. ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +43 -20
  21. ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
  22. ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
  23. ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +102 -0
  24. ibm_cloud_sdk_core/token_managers/token_manager.py +13 -23
  25. ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +33 -13
  26. ibm_cloud_sdk_core/utils.py +121 -46
  27. ibm_cloud_sdk_core/version.py +1 -1
  28. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/METADATA +40 -28
  29. ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +34 -0
  30. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/WHEEL +1 -1
  31. ibm_cloud_sdk_core-3.20.6.dist-info/top_level.txt +1 -0
  32. ibm_cloud_sdk_core-3.16.0.dist-info/RECORD +0 -52
  33. ibm_cloud_sdk_core-3.16.0.dist-info/top_level.txt +0 -3
  34. ibm_cloud_sdk_core-3.16.0.dist-info/zip-safe +0 -1
  35. test/__init__.py +0 -0
  36. test/test_api_exception.py +0 -73
  37. test/test_authenticator.py +0 -21
  38. test/test_base_service.py +0 -933
  39. test/test_basic_authenticator.py +0 -36
  40. test/test_bearer_authenticator.py +0 -28
  41. test/test_container_authenticator.py +0 -105
  42. test/test_container_token_manager.py +0 -283
  43. test/test_cp4d_authenticator.py +0 -171
  44. test/test_cp4d_token_manager.py +0 -56
  45. test/test_detailed_response.py +0 -57
  46. test/test_iam_authenticator.py +0 -157
  47. test/test_iam_token_manager.py +0 -362
  48. test/test_jwt_token_manager.py +0 -109
  49. test/test_no_auth_authenticator.py +0 -15
  50. test/test_token_manager.py +0 -84
  51. test/test_utils.py +0 -634
  52. test/test_vpc_instance_authenticator.py +0 -66
  53. test/test_vpc_instance_token_manager.py +0 -266
  54. test_integration/__init__.py +0 -0
  55. test_integration/test_cp4d_authenticator_integration.py +0 -45
  56. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/LICENSE +0 -0
@@ -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__)
@@ -79,51 +80,64 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
79
80
  scope: The "scope" to use when fetching the bearer token from the IAM token server.
80
81
  This can be used to obtain an access token with a specific scope.
81
82
  """
82
- DEFAULT_CR_TOKEN_FILENAME = '/var/run/secrets/tokens/vault-token'
83
-
84
- def __init__(self,
85
- cr_token_filename: Optional[str] = None,
86
- iam_profile_name: Optional[str] = None,
87
- iam_profile_id: Optional[str] = None,
88
- url: Optional[str] = None,
89
- client_id: Optional[str] = None,
90
- client_secret: Optional[str] = None,
91
- disable_ssl_verification: bool = False,
92
- scope: Optional[str] = None,
93
- proxies: Optional[Dict[str, str]] = None,
94
- headers: Optional[Dict[str, str]] = None) -> None:
83
+
84
+ DEFAULT_CR_TOKEN_FILENAME1 = '/var/run/secrets/tokens/vault-token'
85
+ DEFAULT_CR_TOKEN_FILENAME2 = '/var/run/secrets/tokens/sa-token'
86
+
87
+ def __init__(
88
+ self,
89
+ cr_token_filename: Optional[str] = None,
90
+ iam_profile_name: Optional[str] = None,
91
+ iam_profile_id: Optional[str] = None,
92
+ url: Optional[str] = None,
93
+ client_id: Optional[str] = None,
94
+ client_secret: Optional[str] = None,
95
+ disable_ssl_verification: bool = False,
96
+ scope: Optional[str] = None,
97
+ proxies: Optional[Dict[str, str]] = None,
98
+ headers: Optional[Dict[str, str]] = None,
99
+ ) -> None:
95
100
  super().__init__(
96
- url=url, client_id=client_id, client_secret=client_secret,
97
- disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope)
101
+ url=url,
102
+ client_id=client_id,
103
+ client_secret=client_secret,
104
+ disable_ssl_verification=disable_ssl_verification,
105
+ headers=headers,
106
+ proxies=proxies,
107
+ scope=scope,
108
+ )
98
109
 
99
110
  self.cr_token_filename = cr_token_filename
100
111
  self.iam_profile_name = iam_profile_name
101
112
  self.iam_profile_id = iam_profile_id
102
113
 
103
114
  self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:cr-token'
115
+ self._set_user_agent(_build_user_agent('container-authenticator'))
104
116
 
105
117
  def retrieve_cr_token(self) -> str:
106
118
  """Retrieves the CR token for the current compute resource by reading it from the local file system.
107
119
 
108
120
  Raises:
109
- Exception: Cannot retrieve the compute resource token from.
121
+ Exception: Error retrieving the compute resource token.
110
122
 
111
123
  Returns:
112
124
  A string which contains the compute resource token.
113
125
  """
114
- cr_token_filename = self.cr_token_filename if self.cr_token_filename else self.DEFAULT_CR_TOKEN_FILENAME
115
-
116
- logger.debug('Attempting to read CR token from file: %s',
117
- cr_token_filename)
118
-
119
126
  try:
120
- with open(cr_token_filename, 'r', encoding='utf-8') as file:
121
- cr_token = file.read()
127
+ cr_token = None
128
+ if self.cr_token_filename:
129
+ # If the user specified a filename, then use that.
130
+ cr_token = self.read_file(self.cr_token_filename)
131
+ else:
132
+ # If the user didn't specify a filename, then try our two defaults.
133
+ try:
134
+ cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME1)
135
+ except:
136
+ cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME2)
122
137
  return cr_token
123
- # pylint: disable=broad-except
124
138
  except Exception as ex:
125
- raise Exception(
126
- 'Unable to retrieve the CR token value from file {}: {}'.format(cr_token_filename, ex)) from None
139
+ # pylint: disable=broad-exception-raised
140
+ raise Exception('Unable to retrieve the CR token: {}'.format(ex)) from None
127
141
 
128
142
  def request_token(self) -> dict:
129
143
  """Retrieves a CR token value from the current compute resource,
@@ -132,11 +146,9 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
132
146
  Returns:
133
147
  A dictionary containing the bearer token to be subsequently used service requests.
134
148
  """
135
- # Retrieve the CR token for this compute resource.
136
- cr_token = self.retrieve_cr_token()
137
149
 
138
150
  # Set the request payload.
139
- self.request_payload['cr_token'] = cr_token
151
+ self.request_payload['cr_token'] = self.retrieve_cr_token()
140
152
 
141
153
  if self.iam_profile_id:
142
154
  self.request_payload['profile_id'] = self.iam_profile_id
@@ -168,3 +180,22 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
168
180
  iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token
169
181
  """
170
182
  self.iam_profile_id = iam_profile_id
183
+
184
+ def read_file(self, filename: str) -> str:
185
+ """Read in the specified file and return the contents as a string.
186
+ Args:
187
+ filename: the name of the file to read
188
+ Returns:
189
+ The contents of the file as a string.
190
+ Raises:
191
+ Exception: An error occured reading the file.
192
+ """
193
+ try:
194
+ logger.debug('Attempting to read CR token from file: %s', filename)
195
+ with open(filename, 'r', encoding='utf-8') as file:
196
+ cr_token = file.read()
197
+ logger.debug('Successfully read CR token from file: %s', filename)
198
+ return cr_token
199
+ except Exception as ex:
200
+ # pylint: disable=broad-exception-raised
201
+ raise Exception('Error reading CR token from file {}: {}'.format(filename, ex)) from None
@@ -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
 
@@ -48,19 +49,22 @@ class CP4DTokenManager(JWTTokenManager):
48
49
  proxies.https (str): The proxy endpoint to use for HTTPS requests.
49
50
  verify (str): The path to the certificate to use for HTTPS requests.
50
51
  """
52
+
51
53
  TOKEN_NAME = 'token'
52
54
  VALIDATE_AUTH_PATH = '/v1/authorize'
53
55
 
54
- def __init__(self,
55
- username: str = None,
56
- password: str = None,
57
- url: str = None,
58
- *,
59
- apikey: str = None,
60
- disable_ssl_verification: bool = False,
61
- headers: Optional[Dict[str, str]] = None,
62
- proxies: Optional[Dict[str, str]] = None,
63
- verify: Optional[str] = None) -> None:
56
+ def __init__(
57
+ self,
58
+ username: str = None,
59
+ password: str = None,
60
+ url: str = None,
61
+ *,
62
+ apikey: str = None,
63
+ disable_ssl_verification: bool = False,
64
+ headers: Optional[Dict[str, str]] = None,
65
+ proxies: Optional[Dict[str, str]] = None,
66
+ verify: Optional[str] = None,
67
+ ) -> None:
64
68
  self.username = username
65
69
  self.password = password
66
70
  self.verify = verify
@@ -72,23 +76,27 @@ class CP4DTokenManager(JWTTokenManager):
72
76
  self.headers = {}
73
77
  self.headers['Content-Type'] = 'application/json'
74
78
  self.proxies = proxies
75
- super().__init__(url, disable_ssl_verification=disable_ssl_verification,
76
- token_name=self.TOKEN_NAME)
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'))
77
81
 
78
82
  def request_token(self) -> dict:
79
- """Makes a request for a token.
80
- """
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
+
81
92
  response = self._request(
82
93
  method='POST',
83
- headers=self.headers,
94
+ headers=request_headers,
84
95
  url=self.url,
85
- data=json.dumps({
86
- "username": self.username,
87
- "password": self.password,
88
- "api_key": self.apikey
89
- }),
96
+ data=json.dumps({"username": self.username, "password": self.password, "api_key": self.apikey}),
90
97
  proxies=self.proxies,
91
- verify=self.verify)
98
+ verify=self.verify,
99
+ )
92
100
  return response
93
101
 
94
102
  def set_headers(self, headers: Dict[str, str]) -> None:
@@ -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.
@@ -19,7 +19,7 @@ from typing import Dict, Optional
19
19
  from .jwt_token_manager import JWTTokenManager
20
20
 
21
21
 
22
- #pylint: disable=too-many-instance-attributes
22
+ # pylint: disable=too-many-instance-attributes
23
23
  class IAMRequestBasedTokenManager(JWTTokenManager):
24
24
  """The IamRequestBasedTokenManager class contains code relevant to any token manager that
25
25
  interacts with the IAM service to manage a token. It stores information relevant to all
@@ -60,21 +60,25 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
60
60
  scope: The "scope" to use when fetching the bearer token from the IAM token server.
61
61
  This can be used to obtain an access token with a specific scope.
62
62
  """
63
+
63
64
  DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com'
64
65
  OPERATION_PATH = "/identity/token"
65
-
66
- def __init__(self,
67
- url: Optional[str] = None,
68
- client_id: Optional[str] = None,
69
- client_secret: Optional[str] = None,
70
- disable_ssl_verification: bool = False,
71
- headers: Optional[Dict[str, str]] = None,
72
- proxies: Optional[Dict[str, str]] = None,
73
- scope: Optional[str] = None) -> None:
66
+ IAM_EXPIRATION_WINDOW = 10
67
+
68
+ def __init__(
69
+ self,
70
+ url: Optional[str] = None,
71
+ client_id: Optional[str] = None,
72
+ client_secret: Optional[str] = None,
73
+ disable_ssl_verification: bool = False,
74
+ headers: Optional[Dict[str, str]] = None,
75
+ proxies: Optional[Dict[str, str]] = None,
76
+ scope: Optional[str] = None,
77
+ ) -> None:
74
78
  if not url:
75
79
  url = self.DEFAULT_IAM_URL
76
80
  if url.endswith(self.OPERATION_PATH):
77
- url = url[:-len(self.OPERATION_PATH)]
81
+ url = url[: -len(self.OPERATION_PATH)]
78
82
  self.url = url
79
83
  self.client_id = client_id
80
84
  self.client_secret = client_secret
@@ -83,8 +87,7 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
83
87
  self.proxies = proxies
84
88
  self.scope = scope
85
89
  self.request_payload = {}
86
- super().__init__(
87
- self.url, disable_ssl_verification=disable_ssl_verification, token_name='access_token')
90
+ super().__init__(self.url, disable_ssl_verification=disable_ssl_verification, token_name='access_token')
88
91
 
89
92
  def request_token(self) -> dict:
90
93
  """Request an IAM OAuth token given an API Key.
@@ -95,12 +98,15 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
95
98
  Returns:
96
99
  A dictionary containing the bearer token to be subsequently used service requests.
97
100
  """
98
- headers = {
99
- 'Content-type': 'application/x-www-form-urlencoded',
100
- '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(),
101
105
  }
106
+ request_headers = {}
102
107
  if self.headers is not None and isinstance(self.headers, dict):
103
- headers.update(self.headers)
108
+ request_headers.update(self.headers)
109
+ request_headers.update(required_headers)
104
110
 
105
111
  data = dict(self.request_payload)
106
112
 
@@ -115,10 +121,11 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
115
121
  response = self._request(
116
122
  method='POST',
117
123
  url=(self.url + self.OPERATION_PATH) if self.url else self.url,
118
- headers=headers,
124
+ headers=request_headers,
119
125
  data=data,
120
126
  auth_tuple=auth_tuple,
121
- proxies=self.proxies)
127
+ proxies=self.proxies,
128
+ )
122
129
  return response
123
130
 
124
131
  def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None:
@@ -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):
@@ -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,102 @@
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 ..private_helpers import _build_user_agent
21
+ from .jwt_token_manager import JWTTokenManager
22
+
23
+
24
+ class MCSPTokenManager(JWTTokenManager):
25
+ """The MCSPTokenManager accepts a user-supplied apikey and performs the necessary interactions with
26
+ the Multi-Cloud Saas Platform (MCSP) token service to obtain an MCSP access token (a bearer token).
27
+ When the access token expires, a new access token is obtained from the token server.
28
+
29
+ Keyword Arguments:
30
+ apikey: The apikey for authentication [required].
31
+ url: The endpoint for JWT token requests [required].
32
+ disable_ssl_verification: Disable ssl verification. Defaults to False.
33
+ headers: Headers to be sent with every service token request. Defaults to None.
34
+ proxies: Proxies to use for making request. Defaults to None.
35
+ proxies.http (optional): The proxy endpoint to use for HTTP requests.
36
+ proxies.https (optional): The proxy endpoint to use for HTTPS requests.
37
+ """
38
+
39
+ TOKEN_NAME = 'token'
40
+ OPERATION_PATH = '/siusermgr/api/1.0/apikeys/token'
41
+
42
+ def __init__(
43
+ self,
44
+ apikey: str,
45
+ url: str,
46
+ *,
47
+ disable_ssl_verification: bool = False,
48
+ headers: Optional[Dict[str, str]] = None,
49
+ proxies: Optional[Dict[str, str]] = None,
50
+ ) -> None:
51
+ self.apikey = apikey
52
+ self.headers = headers
53
+ if self.headers is None:
54
+ self.headers = {}
55
+ self.headers['Content-Type'] = 'application/json'
56
+ self.headers['Accept'] = 'application/json'
57
+ self.proxies = proxies
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'))
60
+
61
+ def request_token(self) -> dict:
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
+
71
+ response = self._request(
72
+ method='POST',
73
+ headers=request_headers,
74
+ url=self.url + self.OPERATION_PATH,
75
+ data=json.dumps({"apikey": self.apikey}),
76
+ proxies=self.proxies,
77
+ )
78
+ return response
79
+
80
+ def set_headers(self, headers: Dict[str, str]) -> None:
81
+ """Headers to be sent with every MCSP token request.
82
+
83
+ Args:
84
+ headers: The headers to be sent with every MCSP token request.
85
+ """
86
+ if isinstance(headers, dict):
87
+ self.headers = headers
88
+ else:
89
+ raise TypeError('headers must be a dictionary')
90
+
91
+ def set_proxies(self, proxies: Dict[str, str]) -> None:
92
+ """Sets the proxies the token manager will use to communicate with MCSP on behalf of the host.
93
+
94
+ Args:
95
+ proxies: Proxies to use for making request. Defaults to None.
96
+ proxies.http (optional): The proxy endpoint to use for HTTP requests.
97
+ proxies.https (optional): The proxy endpoint to use for HTTPS requests.
98
+ """
99
+ if isinstance(proxies, dict):
100
+ self.proxies = proxies
101
+ else:
102
+ 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.
@@ -49,14 +49,10 @@ 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
- def __init__(
55
- self,
56
- url: str,
57
- *,
58
- disable_ssl_verification: bool = False
59
- ):
55
+ def __init__(self, url: str, *, disable_ssl_verification: bool = False):
60
56
  self.url = url
61
57
  self.disable_ssl_verification = disable_ssl_verification
62
58
  self.expire_time = 0
@@ -65,6 +61,7 @@ class TokenManager(ABC):
65
61
  self.lock = Lock()
66
62
  self.http_config = {}
67
63
  self.access_token = None
64
+ self.user_agent = None
68
65
 
69
66
  def get_token(self) -> str:
70
67
  """Get a token to be used for authentication.
@@ -100,6 +97,12 @@ class TokenManager(ABC):
100
97
  else:
101
98
  raise TypeError('status must be a bool')
102
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
+
103
106
  def paced_request_token(self) -> None:
104
107
  """
105
108
  Paces requests to request_token.
@@ -190,15 +193,7 @@ class TokenManager(ABC):
190
193
  """
191
194
  pass
192
195
 
193
- def _request(self,
194
- method,
195
- url,
196
- *,
197
- headers=None,
198
- params=None,
199
- data=None,
200
- auth_tuple=None,
201
- **kwargs):
196
+ def _request(self, method, url, *, headers=None, params=None, data=None, auth_tuple=None, **kwargs):
202
197
  kwargs = dict({"timeout": 60}, **kwargs)
203
198
  kwargs = dict(kwargs, **self.http_config)
204
199
 
@@ -206,13 +201,8 @@ class TokenManager(ABC):
206
201
  kwargs['verify'] = False
207
202
 
208
203
  response = requests.request(
209
- method=method,
210
- url=url,
211
- headers=headers,
212
- params=params,
213
- data=data,
214
- auth=auth_tuple,
215
- **kwargs)
204
+ method=method, url=url, headers=headers, params=params, data=data, auth=auth_tuple, **kwargs
205
+ )
216
206
  if 200 <= response.status_code <= 299:
217
207
  return response
218
208