ibm-cloud-sdk-core 3.16.0__py3-none-any.whl → 3.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibm_cloud_sdk_core/__init__.py +1 -0
- ibm_cloud_sdk_core/api_exception.py +18 -4
- ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
- ibm_cloud_sdk_core/authenticators/authenticator.py +2 -1
- ibm_cloud_sdk_core/authenticators/basic_authenticator.py +12 -7
- ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +7 -2
- ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
- ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +38 -23
- ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
- ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +10 -8
- ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +134 -0
- ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +12 -11
- ibm_cloud_sdk_core/base_service.py +137 -103
- ibm_cloud_sdk_core/detailed_response.py +21 -15
- ibm_cloud_sdk_core/get_authenticator.py +35 -17
- ibm_cloud_sdk_core/http_adapter.py +28 -0
- ibm_cloud_sdk_core/logger.py +85 -0
- ibm_cloud_sdk_core/private_helpers.py +34 -0
- ibm_cloud_sdk_core/token_managers/container_token_manager.py +63 -33
- ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +35 -22
- ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +50 -21
- ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
- ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
- ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +108 -0
- ibm_cloud_sdk_core/token_managers/token_manager.py +20 -23
- ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +37 -18
- ibm_cloud_sdk_core/utils.py +127 -48
- ibm_cloud_sdk_core/version.py +1 -1
- ibm_cloud_sdk_core-3.21.0.dist-info/METADATA +159 -0
- ibm_cloud_sdk_core-3.21.0.dist-info/RECORD +35 -0
- {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/WHEEL +1 -1
- ibm_cloud_sdk_core-3.21.0.dist-info/top_level.txt +1 -0
- ibm_cloud_sdk_core-3.16.0.dist-info/METADATA +0 -112
- ibm_cloud_sdk_core-3.16.0.dist-info/RECORD +0 -52
- ibm_cloud_sdk_core-3.16.0.dist-info/top_level.txt +0 -3
- ibm_cloud_sdk_core-3.16.0.dist-info/zip-safe +0 -1
- test/__init__.py +0 -0
- test/test_api_exception.py +0 -73
- test/test_authenticator.py +0 -21
- test/test_base_service.py +0 -933
- test/test_basic_authenticator.py +0 -36
- test/test_bearer_authenticator.py +0 -28
- test/test_container_authenticator.py +0 -105
- test/test_container_token_manager.py +0 -283
- test/test_cp4d_authenticator.py +0 -171
- test/test_cp4d_token_manager.py +0 -56
- test/test_detailed_response.py +0 -57
- test/test_iam_authenticator.py +0 -157
- test/test_iam_token_manager.py +0 -362
- test/test_jwt_token_manager.py +0 -109
- test/test_no_auth_authenticator.py +0 -15
- test/test_token_manager.py +0 -84
- test/test_utils.py +0 -634
- test/test_vpc_instance_authenticator.py +0 -66
- test/test_vpc_instance_token_manager.py +0 -266
- test_integration/__init__.py +0 -0
- test_integration/test_cp4d_authenticator_integration.py +0 -45
- {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
# Copyright 2019 IBM All Rights Reserved.
|
|
3
|
+
# Copyright 2019, 2024 IBM All Rights Reserved.
|
|
4
4
|
#
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
# you may not use this file except in compliance with the License.
|
|
@@ -14,10 +14,21 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
-
from .authenticators import (
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
from .authenticators import (
|
|
18
|
+
Authenticator,
|
|
19
|
+
BasicAuthenticator,
|
|
20
|
+
BearerTokenAuthenticator,
|
|
21
|
+
ContainerAuthenticator,
|
|
22
|
+
CloudPakForDataAuthenticator,
|
|
23
|
+
IAMAuthenticator,
|
|
24
|
+
NoAuthAuthenticator,
|
|
25
|
+
VPCInstanceAuthenticator,
|
|
26
|
+
MCSPAuthenticator,
|
|
27
|
+
)
|
|
20
28
|
from .utils import read_external_sources
|
|
29
|
+
from .logger import get_logger
|
|
30
|
+
|
|
31
|
+
logger = get_logger()
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
def get_authenticator_from_environment(service_name: str) -> Authenticator:
|
|
@@ -34,10 +45,13 @@ def get_authenticator_from_environment(service_name: str) -> Authenticator:
|
|
|
34
45
|
Returns:
|
|
35
46
|
The authenticator found from service information.
|
|
36
47
|
"""
|
|
48
|
+
logger.debug('Get authenticator from environment, key=%s', service_name)
|
|
37
49
|
authenticator = None
|
|
38
50
|
config = read_external_sources(service_name)
|
|
39
51
|
if config:
|
|
40
52
|
authenticator = __construct_authenticator(config)
|
|
53
|
+
if authenticator is not None:
|
|
54
|
+
logger.debug('Returning authenticator, type=%s', authenticator.authentication_type())
|
|
41
55
|
return authenticator
|
|
42
56
|
|
|
43
57
|
|
|
@@ -59,12 +73,9 @@ def __construct_authenticator(config: dict) -> Authenticator:
|
|
|
59
73
|
authenticator = None
|
|
60
74
|
|
|
61
75
|
if auth_type == Authenticator.AUTHTYPE_BASIC.lower():
|
|
62
|
-
authenticator = BasicAuthenticator(
|
|
63
|
-
username=config.get('USERNAME'),
|
|
64
|
-
password=config.get('PASSWORD'))
|
|
76
|
+
authenticator = BasicAuthenticator(username=config.get('USERNAME'), password=config.get('PASSWORD'))
|
|
65
77
|
elif auth_type == Authenticator.AUTHTYPE_BEARERTOKEN.lower():
|
|
66
|
-
authenticator = BearerTokenAuthenticator(
|
|
67
|
-
bearer_token=config.get('BEARER_TOKEN'))
|
|
78
|
+
authenticator = BearerTokenAuthenticator(bearer_token=config.get('BEARER_TOKEN'))
|
|
68
79
|
elif auth_type == Authenticator.AUTHTYPE_CONTAINER.lower():
|
|
69
80
|
authenticator = ContainerAuthenticator(
|
|
70
81
|
cr_token_filename=config.get('CR_TOKEN_FILENAME'),
|
|
@@ -73,30 +84,37 @@ def __construct_authenticator(config: dict) -> Authenticator:
|
|
|
73
84
|
url=config.get('AUTH_URL'),
|
|
74
85
|
client_id=config.get('CLIENT_ID'),
|
|
75
86
|
client_secret=config.get('CLIENT_SECRET'),
|
|
76
|
-
disable_ssl_verification=config.get(
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
88
|
+
scope=config.get('SCOPE'),
|
|
89
|
+
)
|
|
79
90
|
elif auth_type == Authenticator.AUTHTYPE_CP4D.lower():
|
|
80
91
|
authenticator = CloudPakForDataAuthenticator(
|
|
81
92
|
username=config.get('USERNAME'),
|
|
82
93
|
password=config.get('PASSWORD'),
|
|
83
94
|
url=config.get('AUTH_URL'),
|
|
84
95
|
apikey=config.get('APIKEY'),
|
|
85
|
-
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true'
|
|
96
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
97
|
+
)
|
|
86
98
|
elif auth_type == Authenticator.AUTHTYPE_IAM.lower() and config.get('APIKEY'):
|
|
87
99
|
authenticator = IAMAuthenticator(
|
|
88
100
|
apikey=config.get('APIKEY'),
|
|
89
101
|
url=config.get('AUTH_URL'),
|
|
90
102
|
client_id=config.get('CLIENT_ID'),
|
|
91
103
|
client_secret=config.get('CLIENT_SECRET'),
|
|
92
|
-
disable_ssl_verification=config.get(
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
105
|
+
scope=config.get('SCOPE'),
|
|
106
|
+
)
|
|
95
107
|
elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
|
|
96
108
|
authenticator = VPCInstanceAuthenticator(
|
|
97
109
|
iam_profile_crn=config.get('IAM_PROFILE_CRN'),
|
|
98
110
|
iam_profile_id=config.get('IAM_PROFILE_ID'),
|
|
99
|
-
url=config.get('AUTH_URL')
|
|
111
|
+
url=config.get('AUTH_URL'),
|
|
112
|
+
)
|
|
113
|
+
elif auth_type == Authenticator.AUTHTYPE_MCSP.lower():
|
|
114
|
+
authenticator = MCSPAuthenticator(
|
|
115
|
+
apikey=config.get('APIKEY'),
|
|
116
|
+
url=config.get('AUTH_URL'),
|
|
117
|
+
)
|
|
100
118
|
elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower():
|
|
101
119
|
authenticator = NoAuthAuthenticator()
|
|
102
120
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
|
|
3
|
+
from requests import certs
|
|
4
|
+
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
|
|
5
|
+
from urllib3.util.ssl_ import create_urllib3_context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SSLHTTPAdapter(HTTPAdapter):
|
|
9
|
+
"""Wraps the original HTTP adapter and adds additional SSL context."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args, **kwargs):
|
|
12
|
+
self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None)
|
|
13
|
+
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
|
|
17
|
+
"""Create and use custom SSL configuration."""
|
|
18
|
+
|
|
19
|
+
ssl_context = create_urllib3_context()
|
|
20
|
+
# NOTE: https://github.com/psf/requests/pull/6731/files#r1622893724
|
|
21
|
+
ssl_context.load_verify_locations(certs.where())
|
|
22
|
+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
23
|
+
|
|
24
|
+
if self._disable_ssl_verification:
|
|
25
|
+
ssl_context.check_hostname = False
|
|
26
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
27
|
+
|
|
28
|
+
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs)
|
|
@@ -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)
|
|
@@ -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.
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
-
import logging
|
|
18
17
|
from typing import Dict, Optional
|
|
19
18
|
|
|
19
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
20
20
|
from .iam_request_based_token_manager import IAMRequestBasedTokenManager
|
|
21
|
+
from ..private_helpers import _build_user_agent
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
23
|
+
logger = get_logger()
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class ContainerTokenManager(IAMRequestBasedTokenManager):
|
|
@@ -79,51 +79,64 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
|
|
|
79
79
|
scope: The "scope" to use when fetching the bearer token from the IAM token server.
|
|
80
80
|
This can be used to obtain an access token with a specific scope.
|
|
81
81
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
|
|
83
|
+
DEFAULT_CR_TOKEN_FILENAME1 = '/var/run/secrets/tokens/vault-token'
|
|
84
|
+
DEFAULT_CR_TOKEN_FILENAME2 = '/var/run/secrets/tokens/sa-token'
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
cr_token_filename: Optional[str] = None,
|
|
89
|
+
iam_profile_name: Optional[str] = None,
|
|
90
|
+
iam_profile_id: Optional[str] = None,
|
|
91
|
+
url: Optional[str] = None,
|
|
92
|
+
client_id: Optional[str] = None,
|
|
93
|
+
client_secret: Optional[str] = None,
|
|
94
|
+
disable_ssl_verification: bool = False,
|
|
95
|
+
scope: Optional[str] = None,
|
|
96
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
97
|
+
headers: Optional[Dict[str, str]] = None,
|
|
98
|
+
) -> None:
|
|
95
99
|
super().__init__(
|
|
96
|
-
url=url,
|
|
97
|
-
|
|
100
|
+
url=url,
|
|
101
|
+
client_id=client_id,
|
|
102
|
+
client_secret=client_secret,
|
|
103
|
+
disable_ssl_verification=disable_ssl_verification,
|
|
104
|
+
headers=headers,
|
|
105
|
+
proxies=proxies,
|
|
106
|
+
scope=scope,
|
|
107
|
+
)
|
|
98
108
|
|
|
99
109
|
self.cr_token_filename = cr_token_filename
|
|
100
110
|
self.iam_profile_name = iam_profile_name
|
|
101
111
|
self.iam_profile_id = iam_profile_id
|
|
102
112
|
|
|
103
113
|
self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:cr-token'
|
|
114
|
+
self._set_user_agent(_build_user_agent('container-authenticator'))
|
|
104
115
|
|
|
105
116
|
def retrieve_cr_token(self) -> str:
|
|
106
117
|
"""Retrieves the CR token for the current compute resource by reading it from the local file system.
|
|
107
118
|
|
|
108
119
|
Raises:
|
|
109
|
-
Exception:
|
|
120
|
+
Exception: Error retrieving the compute resource token.
|
|
110
121
|
|
|
111
122
|
Returns:
|
|
112
123
|
A string which contains the compute resource token.
|
|
113
124
|
"""
|
|
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
125
|
try:
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
cr_token = None
|
|
127
|
+
if self.cr_token_filename:
|
|
128
|
+
# If the user specified a filename, then use that.
|
|
129
|
+
cr_token = self.read_file(self.cr_token_filename)
|
|
130
|
+
else:
|
|
131
|
+
# If the user didn't specify a filename, then try our two defaults.
|
|
132
|
+
try:
|
|
133
|
+
cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME1)
|
|
134
|
+
except:
|
|
135
|
+
cr_token = self.read_file(self.DEFAULT_CR_TOKEN_FILENAME2)
|
|
122
136
|
return cr_token
|
|
123
|
-
# pylint: disable=broad-except
|
|
124
137
|
except Exception as ex:
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
# pylint: disable=broad-exception-raised
|
|
139
|
+
raise Exception('Unable to retrieve the CR token: {}'.format(ex)) from None
|
|
127
140
|
|
|
128
141
|
def request_token(self) -> dict:
|
|
129
142
|
"""Retrieves a CR token value from the current compute resource,
|
|
@@ -132,11 +145,9 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
|
|
|
132
145
|
Returns:
|
|
133
146
|
A dictionary containing the bearer token to be subsequently used service requests.
|
|
134
147
|
"""
|
|
135
|
-
# Retrieve the CR token for this compute resource.
|
|
136
|
-
cr_token = self.retrieve_cr_token()
|
|
137
148
|
|
|
138
149
|
# Set the request payload.
|
|
139
|
-
self.request_payload['cr_token'] =
|
|
150
|
+
self.request_payload['cr_token'] = self.retrieve_cr_token()
|
|
140
151
|
|
|
141
152
|
if self.iam_profile_id:
|
|
142
153
|
self.request_payload['profile_id'] = self.iam_profile_id
|
|
@@ -168,3 +179,22 @@ class ContainerTokenManager(IAMRequestBasedTokenManager):
|
|
|
168
179
|
iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token
|
|
169
180
|
"""
|
|
170
181
|
self.iam_profile_id = iam_profile_id
|
|
182
|
+
|
|
183
|
+
def read_file(self, filename: str) -> str:
|
|
184
|
+
"""Read in the specified file and return the contents as a string.
|
|
185
|
+
Args:
|
|
186
|
+
filename: the name of the file to read
|
|
187
|
+
Returns:
|
|
188
|
+
The contents of the file as a string.
|
|
189
|
+
Raises:
|
|
190
|
+
Exception: An error occured reading the file.
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
logger.debug('Attempting to read CR token from file: %s', filename)
|
|
194
|
+
with open(filename, 'r', encoding='utf-8') as file:
|
|
195
|
+
cr_token = file.read()
|
|
196
|
+
logger.debug('Successfully read CR token from file: %s', filename)
|
|
197
|
+
return cr_token
|
|
198
|
+
except Exception as ex:
|
|
199
|
+
# pylint: disable=broad-exception-raised
|
|
200
|
+
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,8 +17,12 @@
|
|
|
17
17
|
import json
|
|
18
18
|
from typing import Dict, Optional
|
|
19
19
|
|
|
20
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
21
|
+
from ..private_helpers import _build_user_agent
|
|
20
22
|
from .jwt_token_manager import JWTTokenManager
|
|
21
23
|
|
|
24
|
+
logger = get_logger()
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
class CP4DTokenManager(JWTTokenManager):
|
|
24
28
|
"""Token Manager of CloudPak for data.
|
|
@@ -48,19 +52,22 @@ class CP4DTokenManager(JWTTokenManager):
|
|
|
48
52
|
proxies.https (str): The proxy endpoint to use for HTTPS requests.
|
|
49
53
|
verify (str): The path to the certificate to use for HTTPS requests.
|
|
50
54
|
"""
|
|
55
|
+
|
|
51
56
|
TOKEN_NAME = 'token'
|
|
52
57
|
VALIDATE_AUTH_PATH = '/v1/authorize'
|
|
53
58
|
|
|
54
|
-
def __init__(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
username: str = None,
|
|
62
|
+
password: str = None,
|
|
63
|
+
url: str = None,
|
|
64
|
+
*,
|
|
65
|
+
apikey: str = None,
|
|
66
|
+
disable_ssl_verification: bool = False,
|
|
67
|
+
headers: Optional[Dict[str, str]] = None,
|
|
68
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
69
|
+
verify: Optional[str] = None,
|
|
70
|
+
) -> None:
|
|
64
71
|
self.username = username
|
|
65
72
|
self.password = password
|
|
66
73
|
self.verify = verify
|
|
@@ -72,23 +79,29 @@ class CP4DTokenManager(JWTTokenManager):
|
|
|
72
79
|
self.headers = {}
|
|
73
80
|
self.headers['Content-Type'] = 'application/json'
|
|
74
81
|
self.proxies = proxies
|
|
75
|
-
super().__init__(url, disable_ssl_verification=disable_ssl_verification,
|
|
76
|
-
|
|
82
|
+
super().__init__(url, disable_ssl_verification=disable_ssl_verification, token_name=self.TOKEN_NAME)
|
|
83
|
+
self._set_user_agent(_build_user_agent('cp4d-authenticator'))
|
|
77
84
|
|
|
78
85
|
def request_token(self) -> dict:
|
|
79
|
-
"""Makes a request for a token.
|
|
80
|
-
|
|
86
|
+
"""Makes a request for a token."""
|
|
87
|
+
required_headers = {
|
|
88
|
+
'User-Agent': self.user_agent,
|
|
89
|
+
}
|
|
90
|
+
request_headers = {}
|
|
91
|
+
if self.headers is not None and isinstance(self.headers, dict):
|
|
92
|
+
request_headers.update(self.headers)
|
|
93
|
+
request_headers.update(required_headers)
|
|
94
|
+
|
|
95
|
+
logger.debug('Invoking CP4D token service operation: %s', self.url)
|
|
81
96
|
response = self._request(
|
|
82
97
|
method='POST',
|
|
83
|
-
headers=
|
|
98
|
+
headers=request_headers,
|
|
84
99
|
url=self.url,
|
|
85
|
-
data=json.dumps({
|
|
86
|
-
"username": self.username,
|
|
87
|
-
"password": self.password,
|
|
88
|
-
"api_key": self.apikey
|
|
89
|
-
}),
|
|
100
|
+
data=json.dumps({"username": self.username, "password": self.password, "api_key": self.apikey}),
|
|
90
101
|
proxies=self.proxies,
|
|
91
|
-
verify=self.verify
|
|
102
|
+
verify=self.verify,
|
|
103
|
+
)
|
|
104
|
+
logger.debug('Returned from CP4D token service operation')
|
|
92
105
|
return response
|
|
93
106
|
|
|
94
107
|
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.
|
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
from typing import Dict, Optional
|
|
18
18
|
|
|
19
|
+
from ibm_cloud_sdk_core.logger import get_logger
|
|
19
20
|
from .jwt_token_manager import JWTTokenManager
|
|
20
21
|
|
|
22
|
+
logger = get_logger()
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
# pylint: disable=too-many-instance-attributes
|
|
23
26
|
class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
24
27
|
"""The IamRequestBasedTokenManager class contains code relevant to any token manager that
|
|
25
28
|
interacts with the IAM service to manage a token. It stores information relevant to all
|
|
@@ -60,21 +63,25 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
|
60
63
|
scope: The "scope" to use when fetching the bearer token from the IAM token server.
|
|
61
64
|
This can be used to obtain an access token with a specific scope.
|
|
62
65
|
"""
|
|
66
|
+
|
|
63
67
|
DEFAULT_IAM_URL = 'https://iam.cloud.ibm.com'
|
|
64
68
|
OPERATION_PATH = "/identity/token"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
IAM_EXPIRATION_WINDOW = 10
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
url: Optional[str] = None,
|
|
74
|
+
client_id: Optional[str] = None,
|
|
75
|
+
client_secret: Optional[str] = None,
|
|
76
|
+
disable_ssl_verification: bool = False,
|
|
77
|
+
headers: Optional[Dict[str, str]] = None,
|
|
78
|
+
proxies: Optional[Dict[str, str]] = None,
|
|
79
|
+
scope: Optional[str] = None,
|
|
80
|
+
) -> None:
|
|
74
81
|
if not url:
|
|
75
82
|
url = self.DEFAULT_IAM_URL
|
|
76
83
|
if url.endswith(self.OPERATION_PATH):
|
|
77
|
-
url = url[
|
|
84
|
+
url = url[: -len(self.OPERATION_PATH)]
|
|
78
85
|
self.url = url
|
|
79
86
|
self.client_id = client_id
|
|
80
87
|
self.client_secret = client_secret
|
|
@@ -83,8 +90,7 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
|
83
90
|
self.proxies = proxies
|
|
84
91
|
self.scope = scope
|
|
85
92
|
self.request_payload = {}
|
|
86
|
-
super().__init__(
|
|
87
|
-
self.url, disable_ssl_verification=disable_ssl_verification, token_name='access_token')
|
|
93
|
+
super().__init__(self.url, disable_ssl_verification=disable_ssl_verification, token_name='access_token')
|
|
88
94
|
|
|
89
95
|
def request_token(self) -> dict:
|
|
90
96
|
"""Request an IAM OAuth token given an API Key.
|
|
@@ -95,12 +101,15 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
|
95
101
|
Returns:
|
|
96
102
|
A dictionary containing the bearer token to be subsequently used service requests.
|
|
97
103
|
"""
|
|
98
|
-
|
|
99
|
-
'Content-
|
|
100
|
-
'Accept': 'application/json'
|
|
104
|
+
required_headers = {
|
|
105
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
106
|
+
'Accept': 'application/json',
|
|
107
|
+
'User-Agent': self._get_user_agent(),
|
|
101
108
|
}
|
|
109
|
+
request_headers = {}
|
|
102
110
|
if self.headers is not None and isinstance(self.headers, dict):
|
|
103
|
-
|
|
111
|
+
request_headers.update(self.headers)
|
|
112
|
+
request_headers.update(required_headers)
|
|
104
113
|
|
|
105
114
|
data = dict(self.request_payload)
|
|
106
115
|
|
|
@@ -112,13 +121,17 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
|
112
121
|
if self.client_id and self.client_secret:
|
|
113
122
|
auth_tuple = (self.client_id, self.client_secret)
|
|
114
123
|
|
|
124
|
+
request_url = (self.url + self.OPERATION_PATH) if self.url else self.url
|
|
125
|
+
logger.debug('Invoking IAM get_token operation: %s', request_url)
|
|
115
126
|
response = self._request(
|
|
116
127
|
method='POST',
|
|
117
|
-
url=
|
|
118
|
-
headers=
|
|
128
|
+
url=request_url,
|
|
129
|
+
headers=request_headers,
|
|
119
130
|
data=data,
|
|
120
131
|
auth_tuple=auth_tuple,
|
|
121
|
-
proxies=self.proxies
|
|
132
|
+
proxies=self.proxies,
|
|
133
|
+
)
|
|
134
|
+
logger.debug('Returned from IAM get_token operation')
|
|
122
135
|
return response
|
|
123
136
|
|
|
124
137
|
def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None:
|
|
@@ -167,3 +180,19 @@ class IAMRequestBasedTokenManager(JWTTokenManager):
|
|
|
167
180
|
value: A space seperated string that makes up the scope parameter.
|
|
168
181
|
"""
|
|
169
182
|
self.scope = value
|
|
183
|
+
|
|
184
|
+
def _is_token_expired(self) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Returns true iff the current cached token is expired.
|
|
187
|
+
We'll consider an access token as expired when we reach its IAM server-reported expiration time
|
|
188
|
+
minus our expiration window (10 secs).
|
|
189
|
+
We do this to avoid using an access token that might expire in the middle of a long-running transaction
|
|
190
|
+
within an IBM Cloud service.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
bool
|
|
195
|
+
True if token is expired; False otherwise
|
|
196
|
+
"""
|
|
197
|
+
current_time = self._get_current_time()
|
|
198
|
+
return current_time >= (self.expire_time - self.IAM_EXPIRATION_WINDOW)
|