Skip to content

Commit 4430ed1

Browse files
authored
Merge pull request #17 from IBM/icp4d
feat(icp4d): Add support for icp4d
2 parents 8bc1dd2 + 07b914a commit 4430ed1

12 files changed

+569
-277
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ or
2020
easy_install --upgrade ibm-cloud-sdk-core
2121
```
2222

23+
## Authentication Types
24+
There are several flavors of authentication supported in this package. To specify the intended authentication pattern to use, the user can pass in the parameter `authentication_type`. This parameter is optional, but it may become required in a future major release. The options for this parameter are `basic`, `iam`, and `icp4d`.
25+
26+
### basic
27+
This indicates Basic Auth is to be used. Users will pass in a `username` and `password` and the SDK will generate a Basic Auth header to send with requests to the service.
28+
29+
### iam
30+
This indicates that IAM token authentication is to be used. Users can pass in an `iam_apikey` or an `iam_access_token`. If an API key is used, the SDK will manage the token for the user. In either case, the SDK will generate a Bearer Auth header to send with requests to the service.
31+
32+
### icp4d
33+
This indicates that the service is an instance of ICP4D, which has its own version of token authentication. Users can pass in a `username` and `password`, or an `icp4d_access_token`. If a username and password is given, the SDK will manage the token for the user.
34+
A `icp4d_url` is **required** for this type. In order to use an SDK-managed token with ICP4D authentication, this option **must** be passed in.
35+
2336
## Issues
2437

2538
If you encounter an issue with this project, you are welcome to submit a [bug report](https://github.com/IBM/python-sdk-core/issues).

ibm_cloud_sdk_core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@
1616
from .base_service import BaseService
1717
from .detailed_response import DetailedResponse
1818
from .iam_token_manager import IAMTokenManager
19+
from .jwt_token_manager import JWTTokenManager
20+
from .icp4d_token_manager import ICP4DTokenManager
1921
from .api_exception import ApiException
2022
from .utils import datetime_to_string, string_to_datetime

ibm_cloud_sdk_core/base_service.py

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .version import __version__
2525
from .utils import has_bad_first_or_last_char, remove_null_values, cleanup_values
2626
from .iam_token_manager import IAMTokenManager
27+
from .icp4d_token_manager import ICP4DTokenManager
2728
from .detailed_response import DetailedResponse
2829
from .api_exception import ApiException
2930

@@ -45,19 +46,21 @@ class BaseService(object):
4546
ICP_PREFIX = 'icp-'
4647
APIKEY = 'apikey'
4748
IAM_ACCESS_TOKEN = 'iam_access_token'
49+
ICP4D_ACCESS_TOKEN = 'icp4d_access_token'
4850
URL = 'url'
4951
USERNAME = 'username'
5052
PASSWORD = 'password'
5153
IAM_APIKEY = 'iam_apikey'
5254
IAM_URL = 'iam_url'
55+
ICP4D_URL = 'icp4d_url'
5356
APIKEY_DEPRECATION_MESSAGE = 'Authenticating with apikey is deprecated. Move to using Identity and Access Management (IAM) authentication.'
5457
DEFAULT_CREDENTIALS_FILE_NAME = 'ibm-credentials.env'
5558
SDK_NAME = 'ibm-python-sdk-core'
5659

5760
def __init__(self, vcap_services_name, url, username=None, password=None,
58-
use_vcap_services=True, api_key=None,
59-
iam_apikey=None, iam_access_token=None, iam_url=None, iam_client_id=None, iam_client_secret=None,
60-
display_name=None):
61+
use_vcap_services=True, api_key=None, iam_apikey=None, iam_url=None,
62+
iam_access_token=None, iam_client_id=None, iam_client_secret=None,
63+
display_name=None, icp4d_access_token=None, icp4d_url=None, authentication_type=None):
6164
"""
6265
It loads credentials with the following preference:
6366
1) Credentials explicitly set in the request
@@ -66,41 +69,56 @@ def __init__(self, vcap_services_name, url, username=None, password=None,
6669
"""
6770
self.url = url
6871
self.http_config = {}
72+
self.authentication_type = authentication_type.lower() if authentication_type else None
6973
self.jar = None
70-
self.api_key = None
71-
self.username = None
72-
self.password = None
73-
self.default_headers = None
74-
self.iam_apikey = None
75-
self.iam_access_token = None
76-
self.iam_url = None
77-
self.iam_client_id = None
78-
self.iam_client_secret = None
74+
self.api_key = api_key
75+
self.username = username
76+
self.password = password
77+
self.iam_apikey = iam_apikey
78+
self.iam_access_token = iam_access_token
79+
self.iam_url = iam_url
80+
self.iam_client_id = iam_client_id
81+
self.iam_client_secret = iam_client_secret
82+
self.icp4d_access_token = icp4d_access_token
83+
self.icp4d_url = icp4d_url
7984
self.token_manager = None
85+
self.default_headers = None
8086
self.verify = None # Indicates whether to ignore verifying the SSL certification
8187

82-
if has_bad_first_or_last_char(self.url):
83-
raise ValueError('The URL shouldn\'t start or end with curly brackets or quotes. '
84-
'Be sure to remove any {} and \" characters surrounding your URL')
88+
self._check_credentials()
8589

8690
self.set_user_agent_header(self.build_user_agent())
8791

8892
# 1. Credentials are passed in constructor
89-
if api_key is not None:
90-
if api_key.startswith(self.ICP_PREFIX):
91-
self.set_username_and_password(self.APIKEY, api_key)
92-
else:
93-
self.set_token_manager(api_key, iam_access_token, iam_url, iam_client_id, iam_client_secret)
94-
elif username is not None and password is not None:
95-
if username is self.APIKEY and not password.startswith(self.ICP_PREFIX):
96-
self.set_token_manager(password, iam_access_token, iam_url, iam_client_id, iam_client_secret)
97-
else:
98-
self.set_username_and_password(username, password)
99-
elif iam_access_token is not None or iam_apikey is not None:
100-
if iam_apikey and iam_apikey.startswith(self.ICP_PREFIX):
101-
self.set_username_and_password(self.APIKEY, iam_apikey)
102-
else:
103-
self.set_token_manager(iam_apikey, iam_access_token, iam_url, iam_client_id, iam_client_secret)
93+
if self.authentication_type == 'iam' or self._has_iam_credentials(self.iam_apikey, self.iam_access_token) or self._has_iam_credentials(self.api_key, self.iam_access_token):
94+
self.token_manager = IAMTokenManager(self.iam_apikey or self.api_key or self.password,
95+
self.iam_access_token,
96+
self.iam_url,
97+
self.iam_client_id,
98+
self.iam_client_secret)
99+
self.iam_apikey = self.iam_apikey or self.api_key or self.password
100+
elif self._uses_basic_for_iam(self.username, self.password):
101+
self.token_manager = IAMTokenManager(self.password,
102+
self.iam_access_token,
103+
self.iam_url,
104+
self.iam_client_id,
105+
self.iam_client_secret)
106+
self.iam_apikey = self.password
107+
self.username = None
108+
self.password = None
109+
elif self._is_for_icp4d(self.authentication_type, self.icp4d_access_token):
110+
if self.icp4d_access_token is None and self.icp4d_url is None:
111+
raise Exception('The icp4d_url is mandatory for ICP4D.')
112+
self.token_manager = ICP4DTokenManager(self.icp4d_url,
113+
self.username,
114+
self.password,
115+
self.icp4d_access_token)
116+
elif self._is_for_icp(self.api_key) or self._is_for_icp(self.iam_apikey):
117+
self.username = self.APIKEY
118+
self.password = self.api_key or self.iam_apikey
119+
elif self.token_manager is None and self._has_basic_credentials(username, password):
120+
self.username = username
121+
self.password = password
104122

105123
# 2. Credentials from credential file
106124
if display_name and not self.username and not self.token_manager:
@@ -124,6 +142,10 @@ def __init__(self, vcap_services_name, url, username=None, password=None,
124142
self.set_iam_apikey(self.vcap_service_credentials.get(self.IAM_APIKEY))
125143
if self.IAM_ACCESS_TOKEN in self.vcap_service_credentials:
126144
self.set_iam_access_token(self.vcap_service_credentials.get(self.IAM_ACCESS_TOKEN))
145+
if self.ICP4D_URL in self.vcap_service_credentials:
146+
self.icp4d_url = self.vcap_service_credentials.get(self.ICP4D_URL)
147+
if self.ICP4D_ACCESS_TOKEN in self.vcap_service_credentials:
148+
self.set_icp4d_access_token(self.vcap_service_credentials.get(self.ICP4D_ACCESS_TOKEN))
127149

128150
if (self.username is None or self.password is None) and self.token_manager is None:
129151
raise ValueError(
@@ -183,8 +205,45 @@ def _load_from_vcap_services(self, service_name):
183205
else:
184206
return None
185207

208+
def _is_for_icp(self, credential=None):
209+
return credential and credential.startswith(self.ICP_PREFIX)
210+
211+
def _is_for_icp4d(self, authentication_type, icp4d_access_token=None):
212+
return authentication_type == 'icp4d' or icp4d_access_token
213+
214+
def _has_basic_credentials(self, username, password):
215+
return username and password and not self._uses_basic_for_iam(username, password)
216+
217+
def _has_iam_credentials(self, iam_apikey, iam_access_token):
218+
return (iam_apikey or iam_access_token) and not self._is_for_icp(iam_apikey)
219+
220+
def _uses_basic_for_iam(self, username, password):
221+
"""
222+
Returns true if the user provides basic auth creds with the intention
223+
of using IAM auth
224+
"""
225+
return username and password and username == self.APIKEY and not self._is_for_icp(password)
226+
227+
def _has_bad_first_or_last_char(self, str):
228+
return str is not None and (str.startswith('{') or str.startswith('"') or str.endswith('}') or str.endswith('"'))
229+
230+
def _check_credentials(self):
231+
credentials_to_check = {
232+
'URL': self.url,
233+
'username': self.username,
234+
'password': self.password,
235+
'credentials': self.iam_apikey
236+
}
237+
238+
for key in credentials_to_check:
239+
if self._has_bad_first_or_last_char(credentials_to_check.get(key)):
240+
raise ValueError('The ' + key + ' shouldn\'t start or end with curly brackets or quotes. '
241+
'Be sure to remove any {} and \" characters surrounding your ' + key)
242+
186243
def disable_SSL_verification(self):
187244
self.verify = False
245+
if self.token_manager is not None:
246+
self.token_manager.disable_SSL_verification(True)
188247

189248
def set_username_and_password(self, username, password):
190249
if has_bad_first_or_last_char(username):
@@ -198,20 +257,6 @@ def set_username_and_password(self, username, password):
198257
self.password = password
199258
self.jar = CookieJar()
200259

201-
def set_token_manager(self, iam_apikey=None, iam_access_token=None, iam_url=None,
202-
iam_client_id=None, iam_client_secret=None):
203-
if has_bad_first_or_last_char(iam_apikey):
204-
raise ValueError('The credentials shouldn\'t start or end with curly brackets or quotes. '
205-
'Be sure to remove any {} and \" characters surrounding your credentials')
206-
207-
self.token_manager = IAMTokenManager(iam_apikey, iam_access_token, iam_url, iam_client_id, iam_client_secret)
208-
self.iam_apikey = iam_apikey
209-
self.iam_access_token = iam_access_token
210-
self.iam_url = iam_url
211-
self.iam_client_id = iam_client_id
212-
self.iam_client_secret = iam_client_secret
213-
self.jar = CookieJar()
214-
215260
def set_iam_access_token(self, iam_access_token):
216261
if self.token_manager:
217262
self.token_manager.set_access_token(iam_access_token)
@@ -220,6 +265,16 @@ def set_iam_access_token(self, iam_access_token):
220265
self.iam_access_token = iam_access_token
221266
self.jar = CookieJar()
222267

268+
def set_icp4d_access_token(self, icp4d_access_token):
269+
if self.token_manager:
270+
self.token_manager.set_access_token(icp4d_access_token)
271+
else:
272+
if self.icp4d_url is None:
273+
raise Exception('The icp4d_url is mandatory for ICP4D.')
274+
self.token_manager = ICP4DTokenManager(self.icp4d_url, access_token=icp4d_access_token)
275+
self.icp4d_access_token = icp4d_access_token
276+
self.jar = CookieJar()
277+
223278
def set_iam_url(self, iam_url):
224279
if self.token_manager:
225280
self.token_manager.set_iam_url(iam_url)
@@ -313,7 +368,7 @@ def request(self, method, url, accept_json=False, headers=None,
313368
if self.token_manager:
314369
access_token = self.token_manager.get_token()
315370
headers['Authorization'] = '{0} {1}'.format(self.BEARER, access_token)
316-
if self.username and self.password:
371+
elif self.username and self.password:
317372
auth = (self.username, self.password)
318373

319374
# Use a one minute timeout when our caller doesn't give a timeout.

0 commit comments

Comments
 (0)