ibm-cloud-sdk-core 3.20.6__py3-none-any.whl → 3.22.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.
- ibm_cloud_sdk_core/__init__.py +2 -0
- ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
- ibm_cloud_sdk_core/authenticators/authenticator.py +1 -0
- ibm_cloud_sdk_core/authenticators/basic_authenticator.py +7 -1
- ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +6 -1
- ibm_cloud_sdk_core/authenticators/container_authenticator.py +1 -0
- ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +5 -2
- ibm_cloud_sdk_core/authenticators/iam_assume_authenticator.py +146 -0
- ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +5 -1
- ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +5 -1
- ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +5 -1
- ibm_cloud_sdk_core/base_service.py +28 -10
- ibm_cloud_sdk_core/get_authenticator.py +22 -1
- ibm_cloud_sdk_core/logger.py +85 -0
- ibm_cloud_sdk_core/token_managers/container_token_manager.py +3 -3
- ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +5 -0
- ibm_cloud_sdk_core/token_managers/iam_assume_token_manager.py +150 -0
- ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +8 -1
- ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +7 -1
- ibm_cloud_sdk_core/token_managers/token_manager.py +7 -0
- ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +4 -5
- ibm_cloud_sdk_core/utils.py +6 -2
- ibm_cloud_sdk_core/version.py +1 -1
- ibm_cloud_sdk_core-3.22.0.dist-info/METADATA +160 -0
- ibm_cloud_sdk_core-3.22.0.dist-info/RECORD +37 -0
- {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/WHEEL +1 -1
- ibm_cloud_sdk_core-3.20.6.dist-info/METADATA +0 -124
- ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +0 -34
- {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/LICENSE +0 -0
- {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/top_level.txt +0 -0
ibm_cloud_sdk_core/__init__.py
CHANGED
|
@@ -18,6 +18,7 @@ classes:
|
|
|
18
18
|
BaseService: Abstract class for common functionality between each service.
|
|
19
19
|
DetailedResponse: The object returned from successful service operations.
|
|
20
20
|
IAMTokenManager: Requests and refreshes IAM tokens using an apikey, and optionally a client_id and client_secret.
|
|
21
|
+
IAMAssumeTokenManager: Requests and refreshes IAM tokens using an apikey and a trusted profile.
|
|
21
22
|
JWTTokenManager: Abstract class for common functionality between each JWT token manager.
|
|
22
23
|
CP4DTokenManager: Requests and refreshes CP4D tokens given a username and password.
|
|
23
24
|
ApiException: Custom exception class for errors returned from service operations.
|
|
@@ -39,6 +40,7 @@ functions:
|
|
|
39
40
|
from .base_service import BaseService
|
|
40
41
|
from .detailed_response import DetailedResponse
|
|
41
42
|
from .token_managers.iam_token_manager import IAMTokenManager
|
|
43
|
+
from .token_managers.iam_assume_token_manager import IAMAssumeTokenManager
|
|
42
44
|
from .token_managers.jwt_token_manager import JWTTokenManager
|
|
43
45
|
from .token_managers.cp4d_token_manager import CP4DTokenManager
|
|
44
46
|
from .token_managers.container_token_manager import ContainerTokenManager
|
|
@@ -39,6 +39,7 @@ from .bearer_token_authenticator import BearerTokenAuthenticator
|
|
|
39
39
|
from .container_authenticator import ContainerAuthenticator
|
|
40
40
|
from .cp4d_authenticator import CloudPakForDataAuthenticator
|
|
41
41
|
from .iam_authenticator import IAMAuthenticator
|
|
42
|
+
from .iam_assume_authenticator import IAMAssumeAuthenticator
|
|
42
43
|
from .vpc_instance_authenticator import VPCInstanceAuthenticator
|
|
43
44
|
from .no_auth_authenticator import NoAuthAuthenticator
|
|
44
45
|
from .mcsp_authenticator import MCSPAuthenticator
|
|
@@ -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.
|
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
import base64
|
|
18
|
+
|
|
18
19
|
from requests import Request
|
|
19
20
|
|
|
21
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
20
22
|
from .authenticator import Authenticator
|
|
21
23
|
from ..utils import has_bad_first_or_last_char
|
|
22
24
|
|
|
25
|
+
logger = get_logger()
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
class BasicAuthenticator(Authenticator):
|
|
25
29
|
"""The BasicAuthenticator is used to add basic authentication information to requests.
|
|
@@ -41,6 +45,7 @@ class BasicAuthenticator(Authenticator):
|
|
|
41
45
|
self.password = password
|
|
42
46
|
self.validate()
|
|
43
47
|
self.authorization_header = self.__construct_basic_auth_header()
|
|
48
|
+
logger.debug('Created new BasicAuthenticator instance!')
|
|
44
49
|
|
|
45
50
|
def authentication_type(self) -> str:
|
|
46
51
|
"""Returns this authenticator's type ('basic')."""
|
|
@@ -81,3 +86,4 @@ class BasicAuthenticator(Authenticator):
|
|
|
81
86
|
"""
|
|
82
87
|
headers = req.get('headers')
|
|
83
88
|
headers['Authorization'] = self.authorization_header
|
|
89
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
@@ -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.
|
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
from requests import Request
|
|
18
18
|
|
|
19
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
19
20
|
from .authenticator import Authenticator
|
|
20
21
|
|
|
22
|
+
logger = get_logger()
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
class BearerTokenAuthenticator(Authenticator):
|
|
23
26
|
"""The BearerTokenAuthenticator will add a user-supplied bearer token
|
|
@@ -37,6 +40,7 @@ class BearerTokenAuthenticator(Authenticator):
|
|
|
37
40
|
def __init__(self, bearer_token: str) -> None:
|
|
38
41
|
self.bearer_token = bearer_token
|
|
39
42
|
self.validate()
|
|
43
|
+
logger.debug('Created BearerTokenAuthenticator instance!')
|
|
40
44
|
|
|
41
45
|
def authentication_type(self) -> str:
|
|
42
46
|
"""Returns this authenticator's type ('bearertoken')."""
|
|
@@ -66,6 +70,7 @@ class BearerTokenAuthenticator(Authenticator):
|
|
|
66
70
|
"""
|
|
67
71
|
headers = req.get('headers')
|
|
68
72
|
headers['Authorization'] = 'Bearer {0}'.format(self.bearer_token)
|
|
73
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
69
74
|
|
|
70
75
|
def set_bearer_token(self, bearer_token: str) -> None:
|
|
71
76
|
"""Set a new bearer token to be sent in subsequent service operations.
|
|
@@ -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.
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
from typing import Dict, Optional
|
|
18
|
-
|
|
19
18
|
from requests import Request
|
|
20
19
|
|
|
20
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
21
21
|
from .authenticator import Authenticator
|
|
22
22
|
from ..token_managers.cp4d_token_manager import CP4DTokenManager
|
|
23
23
|
from ..utils import has_bad_first_or_last_char
|
|
24
24
|
|
|
25
|
+
logger = get_logger()
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
class CloudPakForDataAuthenticator(Authenticator):
|
|
27
29
|
"""The CloudPakForDataAuthenticator utilizes a username and password pair to
|
|
@@ -133,6 +135,7 @@ class CloudPakForDataAuthenticator(Authenticator):
|
|
|
133
135
|
headers = req.get('headers')
|
|
134
136
|
bearer_token = self.token_manager.get_token()
|
|
135
137
|
headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
|
|
138
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
136
139
|
|
|
137
140
|
def set_disable_ssl_verification(self, status: bool = False) -> None:
|
|
138
141
|
"""Set the flag that indicates whether verification of the server's SSL certificate should be
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
|
|
17
|
+
from typing import Any, Dict, Optional
|
|
18
|
+
|
|
19
|
+
from ibm_cloud_sdk_core.authenticators.iam_authenticator import IAMAuthenticator
|
|
20
|
+
from ibm_cloud_sdk_core.token_managers.iam_assume_token_manager import IAMAssumeTokenManager
|
|
21
|
+
|
|
22
|
+
from .authenticator import Authenticator
|
|
23
|
+
from .iam_request_based_authenticator import IAMRequestBasedAuthenticator
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IAMAssumeAuthenticator(IAMRequestBasedAuthenticator):
|
|
27
|
+
"""IAMAssumeAuthenticator obtains an IAM access token using the IAM "get-token" operation's
|
|
28
|
+
"assume" grant type. The authenticator obtains an initial IAM access token from a
|
|
29
|
+
user-supplied apikey, then exchanges this initial IAM access token for another IAM access token
|
|
30
|
+
that has "assumed the identity" of the specified trusted profile.
|
|
31
|
+
|
|
32
|
+
The bearer token will be sent as an Authorization header in the form:
|
|
33
|
+
|
|
34
|
+
Authorization: Bearer <bearer-token>
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
apikey: The IAM api key.
|
|
38
|
+
|
|
39
|
+
Keyword Args:
|
|
40
|
+
iam_profile_id: the ID of the trusted profile
|
|
41
|
+
iam_profile_crn: the CRN of the trusted profile
|
|
42
|
+
iam_profile_name: the name of the trusted profile (must be used together with `iam_account_id`)
|
|
43
|
+
iam_account_id: the ID of the trusted profile (must be used together with `iam_profile_name`)
|
|
44
|
+
url: The URL representing the IAM token service endpoint. If not specified, a suitable default value is used.
|
|
45
|
+
client_id: The client_id and client_secret fields are used to form
|
|
46
|
+
a "basic" authorization header for IAM token requests. Defaults to None.
|
|
47
|
+
client_secret: The client_id and client_secret fields are used to form
|
|
48
|
+
a "basic" authorization header for IAM token requests. Defaults to None.
|
|
49
|
+
disable_ssl_verification: A flag that indicates whether verification of
|
|
50
|
+
the server's SSL certificate should be disabled or not. Defaults to False.
|
|
51
|
+
headers: Default headers to be sent with every IAM token request. Defaults to None.
|
|
52
|
+
proxies: Dictionary for mapping request protocol to proxy URL. Defaults to None.
|
|
53
|
+
proxies.http (optional): The proxy endpoint to use for HTTP requests.
|
|
54
|
+
proxies.https (optional): The proxy endpoint to use for HTTPS requests.
|
|
55
|
+
scope: The "scope" to use when fetching the bearer token from the IAM token server.
|
|
56
|
+
This can be used to obtain an access token with a specific scope.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
token_manager (IAMTokenManager): Retrieves and manages IAM tokens from the endpoint specified by the url.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
TypeError: The `disable_ssl_verification` is not a bool.
|
|
63
|
+
ValueError: The `apikey`, `client_id`, and/or `client_secret` are not valid for IAM token requests or the
|
|
64
|
+
following keyword arguments are incorrectly specified:
|
|
65
|
+
`iam_profile_id`, `iam_profile_crn`, `iam_profile_name`, `iam_account_id`.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
apikey: str,
|
|
71
|
+
*,
|
|
72
|
+
iam_profile_id: Optional[str] = None,
|
|
73
|
+
iam_profile_crn: Optional[str] = None,
|
|
74
|
+
iam_profile_name: Optional[str] = None,
|
|
75
|
+
iam_account_id: Optional[str] = None,
|
|
76
|
+
url: Optional[str] = None,
|
|
77
|
+
client_id: Optional[str] = None,
|
|
78
|
+
client_secret: Optional[str] = None,
|
|
79
|
+
disable_ssl_verification: bool = False,
|
|
80
|
+
headers: Optional[Dict[str, str]] = None,
|
|
81
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
82
|
+
scope: Optional[str] = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
# Check the type of `disable_ssl_verification`. Must be a bool.
|
|
85
|
+
if not isinstance(disable_ssl_verification, bool):
|
|
86
|
+
raise TypeError('disable_ssl_verification must be a bool')
|
|
87
|
+
|
|
88
|
+
self.token_manager = IAMAssumeTokenManager(
|
|
89
|
+
apikey,
|
|
90
|
+
iam_profile_id=iam_profile_id,
|
|
91
|
+
iam_profile_crn=iam_profile_crn,
|
|
92
|
+
iam_profile_name=iam_profile_name,
|
|
93
|
+
iam_account_id=iam_account_id,
|
|
94
|
+
url=url,
|
|
95
|
+
client_id=client_id,
|
|
96
|
+
client_secret=client_secret,
|
|
97
|
+
disable_ssl_verification=disable_ssl_verification,
|
|
98
|
+
headers=headers,
|
|
99
|
+
proxies=proxies,
|
|
100
|
+
scope=scope,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.validate()
|
|
104
|
+
|
|
105
|
+
# Disable all setter methods, inherited from the parent class.
|
|
106
|
+
def __getattribute__(self, name: str) -> Any:
|
|
107
|
+
if name.startswith("set_"):
|
|
108
|
+
raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
|
|
109
|
+
|
|
110
|
+
return super().__getattribute__(name)
|
|
111
|
+
|
|
112
|
+
def authentication_type(self) -> str:
|
|
113
|
+
"""Returns this authenticator's type ('iamAssume')."""
|
|
114
|
+
return Authenticator.AUTHTYPE_IAM_ASSUME
|
|
115
|
+
|
|
116
|
+
def validate(self) -> None:
|
|
117
|
+
"""Validates the provided IAM related arguments.
|
|
118
|
+
|
|
119
|
+
Ensure the following:
|
|
120
|
+
- `apikey` of the IAMTokenManager is not `None`, and has no bad characters
|
|
121
|
+
- both `client_id` and `client_secret` are set if either of them are defined
|
|
122
|
+
- the correct number and type of IAM profile and IAM account options are specified
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests.
|
|
126
|
+
"""
|
|
127
|
+
# Create a temporary IAM authenticator that we can use to validate our delegate.
|
|
128
|
+
tmp_authenticator = IAMAuthenticator("")
|
|
129
|
+
tmp_authenticator.token_manager = self.token_manager.iam_delegate
|
|
130
|
+
tmp_authenticator.validate()
|
|
131
|
+
del tmp_authenticator
|
|
132
|
+
|
|
133
|
+
# Only one of the following arguments must be specified.
|
|
134
|
+
mutually_exclusive_attributes = [
|
|
135
|
+
self.token_manager.iam_profile_id,
|
|
136
|
+
self.token_manager.iam_profile_crn,
|
|
137
|
+
self.token_manager.iam_profile_name,
|
|
138
|
+
]
|
|
139
|
+
if list(map(bool, mutually_exclusive_attributes)).count(True) != 1:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
'Exactly one of `iam_profile_id`, `iam_profile_crn`, or `iam_profile_name` must be specified.'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# `iam_account_id` must be specified iff `iam_profile_name` is used.
|
|
145
|
+
if self.token_manager.iam_profile_name and not self.token_manager.iam_account_id:
|
|
146
|
+
raise ValueError('`iam_profile_name` and `iam_account_id` must be provided together, or not at all.')
|
|
@@ -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,8 +18,11 @@ from typing import Dict
|
|
|
18
18
|
|
|
19
19
|
from requests import Request
|
|
20
20
|
|
|
21
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
21
22
|
from .authenticator import Authenticator
|
|
22
23
|
|
|
24
|
+
logger = get_logger()
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
class IAMRequestBasedAuthenticator(Authenticator):
|
|
25
28
|
"""The IAMRequestBasedAuthenticator class contains code that is common to all authenticators
|
|
@@ -60,6 +63,7 @@ class IAMRequestBasedAuthenticator(Authenticator):
|
|
|
60
63
|
headers = req.get('headers')
|
|
61
64
|
bearer_token = self.token_manager.get_token()
|
|
62
65
|
headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
|
|
66
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
63
67
|
|
|
64
68
|
def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None:
|
|
65
69
|
"""Set the client_id and client_secret pair the token manager will use for IAM token requests.
|
|
@@ -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.
|
|
@@ -18,9 +18,12 @@ from typing import Dict, Optional
|
|
|
18
18
|
|
|
19
19
|
from requests import Request
|
|
20
20
|
|
|
21
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
21
22
|
from .authenticator import Authenticator
|
|
22
23
|
from ..token_managers.mcsp_token_manager import MCSPTokenManager
|
|
23
24
|
|
|
25
|
+
logger = get_logger()
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class MCSPAuthenticator(Authenticator):
|
|
26
29
|
"""The MCSPAuthenticator uses an apikey to obtain an access token from the MCSP token server.
|
|
@@ -98,6 +101,7 @@ class MCSPAuthenticator(Authenticator):
|
|
|
98
101
|
headers = req.get('headers')
|
|
99
102
|
bearer_token = self.token_manager.get_token()
|
|
100
103
|
headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
|
|
104
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
101
105
|
|
|
102
106
|
def set_disable_ssl_verification(self, status: bool = False) -> None:
|
|
103
107
|
"""Set the flag that indicates whether verification of the server's SSL certificate should be
|
|
@@ -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,9 +18,12 @@ from typing import Optional
|
|
|
18
18
|
|
|
19
19
|
from requests import Request
|
|
20
20
|
|
|
21
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
21
22
|
from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
|
|
22
23
|
from .authenticator import Authenticator
|
|
23
24
|
|
|
25
|
+
logger = get_logger()
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class VPCInstanceAuthenticator(Authenticator):
|
|
26
29
|
"""VPCInstanceAuthenticator implements an authentication scheme in which it
|
|
@@ -89,6 +92,7 @@ class VPCInstanceAuthenticator(Authenticator):
|
|
|
89
92
|
headers = req.get('headers')
|
|
90
93
|
bearer_token = self.token_manager.get_token()
|
|
91
94
|
headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
|
|
95
|
+
logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
|
|
92
96
|
|
|
93
97
|
def set_iam_profile_crn(self, iam_profile_crn: str) -> None:
|
|
94
98
|
"""Sets CRN of the IAM profile.
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
import gzip
|
|
18
18
|
import io
|
|
19
|
-
import json as json_import
|
|
20
19
|
import logging
|
|
20
|
+
import json as json_import
|
|
21
21
|
from http.cookiejar import CookieJar
|
|
22
|
+
from http import client
|
|
22
23
|
from os.path import basename
|
|
23
24
|
from typing import Dict, List, Optional, Tuple, Union
|
|
24
25
|
from urllib3.util.retry import Retry
|
|
@@ -42,13 +43,12 @@ from .utils import (
|
|
|
42
43
|
GzipStream,
|
|
43
44
|
)
|
|
44
45
|
from .private_helpers import _build_user_agent
|
|
46
|
+
from .logger import (
|
|
47
|
+
get_logger,
|
|
48
|
+
LoggingFilter,
|
|
49
|
+
)
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
# import http.client as http_client
|
|
48
|
-
# http_client.HTTPConnection.debuglevel = 1
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
logger = logging.getLogger(__name__)
|
|
51
|
+
logger = get_logger()
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
# pylint: disable=too-many-instance-attributes
|
|
@@ -92,7 +92,7 @@ class BaseService:
|
|
|
92
92
|
self,
|
|
93
93
|
*,
|
|
94
94
|
service_url: str = None,
|
|
95
|
-
authenticator: Authenticator = None,
|
|
95
|
+
authenticator: Optional[Authenticator] = None,
|
|
96
96
|
disable_ssl_verification: bool = False,
|
|
97
97
|
enable_gzip_compression: bool = False,
|
|
98
98
|
) -> None:
|
|
@@ -114,6 +114,12 @@ class BaseService:
|
|
|
114
114
|
|
|
115
115
|
self.http_client.mount('http://', self.http_adapter)
|
|
116
116
|
self.http_client.mount('https://', self.http_adapter)
|
|
117
|
+
# If debug logging is requested, then trigger HTTP message logging as well.
|
|
118
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
119
|
+
client.HTTPConnection.debuglevel = 1
|
|
120
|
+
# Replace the `print` function in the HTTPClient module to
|
|
121
|
+
# use the debug logger instead of the bare Python print.
|
|
122
|
+
client.print = lambda *args: logger.debug(LoggingFilter.filter_message(" ".join(args)))
|
|
117
123
|
|
|
118
124
|
def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) -> None:
|
|
119
125
|
"""Enable automatic retries on the underlying http client used by the BaseService instance.
|
|
@@ -141,6 +147,7 @@ class BaseService:
|
|
|
141
147
|
)
|
|
142
148
|
self.http_client.mount('http://', self.http_adapter)
|
|
143
149
|
self.http_client.mount('https://', self.http_adapter)
|
|
150
|
+
logger.debug('Enabled retries; max_retries=%d, max_retry_interval=%f', max_retries, retry_interval)
|
|
144
151
|
|
|
145
152
|
def disable_retries(self):
|
|
146
153
|
"""Remove retry config from http_adapter"""
|
|
@@ -148,6 +155,7 @@ class BaseService:
|
|
|
148
155
|
self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
|
|
149
156
|
self.http_client.mount('http://', self.http_adapter)
|
|
150
157
|
self.http_client.mount('https://', self.http_adapter)
|
|
158
|
+
logger.debug('Disabled retries')
|
|
151
159
|
|
|
152
160
|
def configure_service(self, service_name: str) -> None:
|
|
153
161
|
"""Look for external configuration of a service. Set service properties.
|
|
@@ -166,6 +174,8 @@ class BaseService:
|
|
|
166
174
|
if not isinstance(service_name, str):
|
|
167
175
|
raise ValueError('Service_name must be of type string.')
|
|
168
176
|
|
|
177
|
+
logger.debug('Configuring BaseService instance with service name: %s', service_name)
|
|
178
|
+
|
|
169
179
|
config = read_external_sources(service_name)
|
|
170
180
|
if config.get('URL'):
|
|
171
181
|
self.set_service_url(config.get('URL'))
|
|
@@ -184,6 +194,7 @@ class BaseService:
|
|
|
184
194
|
|
|
185
195
|
def _set_user_agent_header(self, user_agent_string: str) -> None:
|
|
186
196
|
self.user_agent_header = {'User-Agent': user_agent_string}
|
|
197
|
+
logger.debug('Set User-Agent: %s', user_agent_string)
|
|
187
198
|
|
|
188
199
|
def set_http_config(self, http_config: dict) -> None:
|
|
189
200
|
"""Sets the http config dictionary.
|
|
@@ -225,6 +236,7 @@ class BaseService:
|
|
|
225
236
|
)
|
|
226
237
|
self.http_client.mount('http://', self.http_adapter)
|
|
227
238
|
self.http_client.mount('https://', self.http_adapter)
|
|
239
|
+
logger.debug('Disabled SSL verification in HTTP client')
|
|
228
240
|
|
|
229
241
|
def set_service_url(self, service_url: str) -> None:
|
|
230
242
|
"""Set the url the service will make HTTP requests too.
|
|
@@ -243,6 +255,7 @@ class BaseService:
|
|
|
243
255
|
if service_url is not None:
|
|
244
256
|
service_url = service_url.rstrip('/')
|
|
245
257
|
self.service_url = service_url
|
|
258
|
+
logger.debug('Set service URL: %s', service_url)
|
|
246
259
|
|
|
247
260
|
def get_http_client(self) -> requests.sessions.Session:
|
|
248
261
|
"""Get the http client session currently used by the service.
|
|
@@ -305,7 +318,7 @@ class BaseService:
|
|
|
305
318
|
# Check to see if the caller specified the 'stream' argument.
|
|
306
319
|
stream_response = kwargs.get('stream') or False
|
|
307
320
|
|
|
308
|
-
# Remove the keys we set manually, don't let the user
|
|
321
|
+
# Remove the keys we set manually, don't let the user overwrite these.
|
|
309
322
|
reserved_keys = ['method', 'url', 'headers', 'params', 'cookies']
|
|
310
323
|
silent_keys = ['headers']
|
|
311
324
|
for key in reserved_keys:
|
|
@@ -314,8 +327,12 @@ class BaseService:
|
|
|
314
327
|
if key not in silent_keys:
|
|
315
328
|
logger.warning('"%s" has been removed from the request', key)
|
|
316
329
|
try:
|
|
330
|
+
logger.debug('Sending HTTP request message')
|
|
331
|
+
|
|
317
332
|
response = self.http_client.request(**request, cookies=self.jar, **kwargs)
|
|
318
333
|
|
|
334
|
+
logger.debug('Received HTTP response message, status code %d', response.status_code)
|
|
335
|
+
|
|
319
336
|
# Process a "success" response.
|
|
320
337
|
if 200 <= response.status_code <= 299:
|
|
321
338
|
if response.status_code == 204 or request['method'] == 'HEAD':
|
|
@@ -455,6 +472,7 @@ class BaseService:
|
|
|
455
472
|
file_tuple = (filename, file_tuple[1], file_tuple[2])
|
|
456
473
|
new_files.append((part_name, file_tuple))
|
|
457
474
|
request['files'] = new_files
|
|
475
|
+
logger.debug('Prepared request [%s %s]', request['method'], request['url'])
|
|
458
476
|
return request
|
|
459
477
|
|
|
460
478
|
@staticmethod
|
|
@@ -490,4 +508,4 @@ class BaseService:
|
|
|
490
508
|
|
|
491
509
|
@staticmethod
|
|
492
510
|
def _encode_path_vars(*args) -> None:
|
|
493
|
-
return
|
|
511
|
+
return BaseService.encode_path_vars(*args)
|
|
@@ -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.
|
|
@@ -21,11 +21,15 @@ from .authenticators import (
|
|
|
21
21
|
ContainerAuthenticator,
|
|
22
22
|
CloudPakForDataAuthenticator,
|
|
23
23
|
IAMAuthenticator,
|
|
24
|
+
IAMAssumeAuthenticator,
|
|
24
25
|
NoAuthAuthenticator,
|
|
25
26
|
VPCInstanceAuthenticator,
|
|
26
27
|
MCSPAuthenticator,
|
|
27
28
|
)
|
|
28
29
|
from .utils import read_external_sources
|
|
30
|
+
from .logger import get_logger
|
|
31
|
+
|
|
32
|
+
logger = get_logger()
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
def get_authenticator_from_environment(service_name: str) -> Authenticator:
|
|
@@ -42,13 +46,17 @@ def get_authenticator_from_environment(service_name: str) -> Authenticator:
|
|
|
42
46
|
Returns:
|
|
43
47
|
The authenticator found from service information.
|
|
44
48
|
"""
|
|
49
|
+
logger.debug('Get authenticator from environment, key=%s', service_name)
|
|
45
50
|
authenticator = None
|
|
46
51
|
config = read_external_sources(service_name)
|
|
47
52
|
if config:
|
|
48
53
|
authenticator = __construct_authenticator(config)
|
|
54
|
+
if authenticator is not None:
|
|
55
|
+
logger.debug('Returning authenticator, type=%s', authenticator.authentication_type())
|
|
49
56
|
return authenticator
|
|
50
57
|
|
|
51
58
|
|
|
59
|
+
# pylint: disable=too-many-branches
|
|
52
60
|
def __construct_authenticator(config: dict) -> Authenticator:
|
|
53
61
|
# Determine the authentication type if not specified explicitly.
|
|
54
62
|
if config.get('AUTH_TYPE'):
|
|
@@ -98,6 +106,19 @@ def __construct_authenticator(config: dict) -> Authenticator:
|
|
|
98
106
|
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
99
107
|
scope=config.get('SCOPE'),
|
|
100
108
|
)
|
|
109
|
+
elif auth_type == Authenticator.AUTHTYPE_IAM_ASSUME.lower():
|
|
110
|
+
authenticator = IAMAssumeAuthenticator(
|
|
111
|
+
apikey=config.get('APIKEY'),
|
|
112
|
+
iam_profile_id=config.get('IAM_PROFILE_ID'),
|
|
113
|
+
iam_profile_crn=config.get('IAM_PROFILE_CRN'),
|
|
114
|
+
iam_profile_name=config.get('IAM_PROFILE_NAME'),
|
|
115
|
+
iam_account_id=config.get('IAM_ACCOUNT_ID'),
|
|
116
|
+
url=config.get('AUTH_URL'),
|
|
117
|
+
client_id=config.get('CLIENT_ID'),
|
|
118
|
+
client_secret=config.get('CLIENT_SECRET'),
|
|
119
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
120
|
+
scope=config.get('SCOPE'),
|
|
121
|
+
)
|
|
101
122
|
elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
|
|
102
123
|
authenticator = VPCInstanceAuthenticator(
|
|
103
124
|
iam_profile_crn=config.get('IAM_PROFILE_CRN'),
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# This is the name of the primary logger used by the library.
|
|
22
|
+
LOGGER_NAME = 'ibm-cloud-sdk-core'
|
|
23
|
+
# Keywords that are redacted.
|
|
24
|
+
REDACTED_KEYWORDS = [
|
|
25
|
+
"apikey",
|
|
26
|
+
"api_key",
|
|
27
|
+
"passcode",
|
|
28
|
+
"password",
|
|
29
|
+
"token",
|
|
30
|
+
"aadClientId",
|
|
31
|
+
"aadClientSecret",
|
|
32
|
+
"auth",
|
|
33
|
+
"auth_provider_x509_cert_url",
|
|
34
|
+
"auth_uri",
|
|
35
|
+
"client_email",
|
|
36
|
+
"client_id",
|
|
37
|
+
"client_x509_cert_url",
|
|
38
|
+
"key",
|
|
39
|
+
"project_id",
|
|
40
|
+
"secret",
|
|
41
|
+
"subscriptionId",
|
|
42
|
+
"tenantId",
|
|
43
|
+
"thumbprint",
|
|
44
|
+
"token_uri",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LoggingFilter:
|
|
49
|
+
"""Functions used to filter messages before they are logged."""
|
|
50
|
+
|
|
51
|
+
redacted_tokens = "|".join(REDACTED_KEYWORDS)
|
|
52
|
+
auth_header_pattern = re.compile(r"(?m)(Authorization|X-Auth\S*): ((.*?)(\r\n.*)|(.*))")
|
|
53
|
+
property_settings_pattern = re.compile(r"(?i)(" + redacted_tokens + r")=[^&]*(&|$)")
|
|
54
|
+
json_field_pattern = re.compile(r'(?i)"([^"]*(' + redacted_tokens + r')[^"_]*)":\s*"[^\,]*"')
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def redact_secrets(cls, text: str) -> str:
|
|
58
|
+
"""Replaces values of potential secret keywords with a placeholder value.
|
|
59
|
+
Args:
|
|
60
|
+
text (str): the string to check and process
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
str: the safe, redacted string with all secrets masked out
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
placeholder = "[redacted]"
|
|
67
|
+
redacted = cls.auth_header_pattern.sub(r"\1: " + placeholder + r"\4", text)
|
|
68
|
+
redacted = cls.property_settings_pattern.sub(r"\1=" + placeholder + r"\2", redacted)
|
|
69
|
+
redacted = cls.json_field_pattern.sub(r'"\1":"' + placeholder + r'"', redacted)
|
|
70
|
+
return redacted
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def filter_message(cls, s: str) -> str:
|
|
74
|
+
"""Filters 's' prior to logging it as a debug message"""
|
|
75
|
+
# Redact secrets
|
|
76
|
+
s = LoggingFilter.redact_secrets(s)
|
|
77
|
+
|
|
78
|
+
# Replace CRLF characters with an actual newline to make the message more readable.
|
|
79
|
+
s = s.replace('\\r\\n', '\n')
|
|
80
|
+
return s
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_logger() -> logging.Logger:
|
|
84
|
+
"""Returns the primary logger object instance used by the library."""
|
|
85
|
+
return logging.getLogger(LOGGER_NAME)
|