Skip to content

Commit 45c4576

Browse files
committed
feat(icp4d): Add support for icp4d url
1 parent e19a28f commit 45c4576

File tree

9 files changed

+71
-46
lines changed

9 files changed

+71
-46
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
from .detailed_response import DetailedResponse
1818
from .iam_token_manager import IAMTokenManager
1919
from .jwt_token_manager import JWTTokenManager
20-
from .icp_token_manager import ICPTokenManager
20+
from .icp4d_token_manager import ICP4DTokenManager
2121
from .api_exception import ApiException
2222
from .utils import datetime_to_string, string_to_datetime

ibm_cloud_sdk_core/base_service.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +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 .icp_token_manager import ICPTokenManager
27+
from .icp4d_token_manager import ICP4DTokenManager
2828
from .detailed_response import DetailedResponse
2929
from .api_exception import ApiException
3030

@@ -46,20 +46,21 @@ class BaseService(object):
4646
ICP_PREFIX = 'icp-'
4747
APIKEY = 'apikey'
4848
IAM_ACCESS_TOKEN = 'iam_access_token'
49-
ICP_ACCESS_TOKEN = 'icp_access_token'
49+
ICP4D_ACCESS_TOKEN = 'icp4d_access_token'
5050
URL = 'url'
5151
USERNAME = 'username'
5252
PASSWORD = 'password'
5353
IAM_APIKEY = 'iam_apikey'
5454
IAM_URL = 'iam_url'
55+
ICP4D_URL = 'icp4d_url'
5556
APIKEY_DEPRECATION_MESSAGE = 'Authenticating with apikey is deprecated. Move to using Identity and Access Management (IAM) authentication.'
5657
DEFAULT_CREDENTIALS_FILE_NAME = 'ibm-credentials.env'
5758
SDK_NAME = 'ibm-python-sdk-core'
5859

5960
def __init__(self, vcap_services_name, url, username=None, password=None,
6061
use_vcap_services=True, api_key=None, iam_apikey=None, iam_url=None,
6162
iam_access_token=None, iam_client_id=None, iam_client_secret=None,
62-
display_name=None, icp_access_token=None, authentication_type=None):
63+
display_name=None, icp4d_access_token=None, icp4d_url=None, authentication_type=None):
6364
"""
6465
It loads credentials with the following preference:
6566
1) Credentials explicitly set in the request
@@ -78,7 +79,8 @@ def __init__(self, vcap_services_name, url, username=None, password=None,
7879
self.iam_url = iam_url
7980
self.iam_client_id = iam_client_id
8081
self.iam_client_secret = iam_client_secret
81-
self.icp_access_token = icp_access_token
82+
self.icp4d_access_token = icp4d_access_token
83+
self.icp4d_url = icp4d_url
8284
self.token_manager = None
8385
self.default_headers = None
8486
self.verify = None # Indicates whether to ignore verifying the SSL certification
@@ -104,11 +106,13 @@ def __init__(self, vcap_services_name, url, username=None, password=None,
104106
self.iam_apikey = self.password
105107
self.username = None
106108
self.password = None
107-
elif self._is_for_icp4d(self.authentication_type, self.icp_access_token):
108-
self.token_manager = ICPTokenManager(self.url,
109-
self.username,
110-
self.password,
111-
self.icp_access_token)
109+
elif self._is_for_icp4d(self.authentication_type, self.icp4d_access_token):
110+
if 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)
112116
elif self._is_for_icp(self.api_key) or self._is_for_icp(self.iam_apikey):
113117
self.username = self.APIKEY
114118
self.password = self.api_key or self.iam_apikey
@@ -138,8 +142,10 @@ def __init__(self, vcap_services_name, url, username=None, password=None,
138142
self.set_iam_apikey(self.vcap_service_credentials.get(self.IAM_APIKEY))
139143
if self.IAM_ACCESS_TOKEN in self.vcap_service_credentials:
140144
self.set_iam_access_token(self.vcap_service_credentials.get(self.IAM_ACCESS_TOKEN))
141-
if self.ICP_ACCESS_TOKEN in self.vcap_service_credentials:
142-
self.set_icp_access_token(self.vcap_service_credentials.get(self.ICP_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))
143149

144150
if (self.username is None or self.password is None) and self.token_manager is None:
145151
raise ValueError(
@@ -202,8 +208,8 @@ def _load_from_vcap_services(self, service_name):
202208
def _is_for_icp(self, credential=None):
203209
return credential and credential.startswith(self.ICP_PREFIX)
204210

205-
def _is_for_icp4d(self, authentication_type, icp_access_token=None):
206-
return authentication_type == 'icp4d' or icp_access_token
211+
def _is_for_icp4d(self, authentication_type, icp4d_access_token=None):
212+
return authentication_type == 'icp4d' or icp4d_access_token
207213

208214
def _has_basic_credentials(self, username, password):
209215
return username and password and not self._uses_basic_for_iam(username, password)
@@ -259,12 +265,14 @@ def set_iam_access_token(self, iam_access_token):
259265
self.iam_access_token = iam_access_token
260266
self.jar = CookieJar()
261267

262-
def set_icp_access_token(self, icp_access_token):
268+
def set_icp4d_access_token(self, icp4d_access_token):
263269
if self.token_manager:
264-
self.token_manager.set_access_token(icp_access_token)
270+
self.token_manager.set_access_token(icp4d_access_token)
265271
else:
266-
self.token_manager = ICPTokenManager(self.url, access_token=icp_access_token)
267-
self.icp_access_token = icp_access_token
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
268276
self.jar = CookieJar()
269277

270278
def set_iam_url(self, iam_url):

ibm_cloud_sdk_core/iam_token_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ class IAMTokenManager(JWTTokenManager):
2121
CONTENT_TYPE = 'application/x-www-form-urlencoded'
2222
REQUEST_TOKEN_GRANT_TYPE = 'urn:ibm:params:oauth:grant-type:apikey'
2323
REQUEST_TOKEN_RESPONSE_TYPE = 'cloud_iam'
24+
TOKEN_NAME = 'access_token'
2425

2526
def __init__(self, iam_apikey=None, iam_access_token=None, iam_url=None,
2627
iam_client_id=None, iam_client_secret=None):
2728
self.iam_apikey = iam_apikey
2829
self.iam_url = iam_url if iam_url else self.DEFAULT_IAM_URL
2930
self.iam_client_id = iam_client_id
3031
self.iam_client_secret = iam_client_secret
31-
super(IAMTokenManager, self).__init__(self.iam_url, iam_access_token)
32+
super(IAMTokenManager, self).__init__(self.iam_url, iam_access_token, self.TOKEN_NAME)
3233

3334
def request_token(self):
3435
"""

ibm_cloud_sdk_core/icp_token_manager.py renamed to ibm_cloud_sdk_core/icp4d_token_manager.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
from .jwt_token_manager import JWTTokenManager
1818

19-
class ICPTokenManager(JWTTokenManager):
20-
def __init__(self, url, username=None, password=None, access_token=None):
21-
url = url + '/v1/preauth/validateAuth'
19+
class ICP4DTokenManager(JWTTokenManager):
20+
TOKEN_NAME = 'accessToken'
21+
def __init__(self, icp4d_url, username=None, password=None, access_token=None):
22+
url = icp4d_url + '/v1/preauth/validateAuth'
2223
self.username = username
2324
self.password = password
24-
super(ICPTokenManager, self).__init__(url, access_token)
25+
super(ICP4DTokenManager, self).__init__(url, access_token, self.TOKEN_NAME)
2526

2627
def request_token(self):
2728
auth_tuple = (self.username, self.password)

ibm_cloud_sdk_core/jwt_token_manager.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
from .api_exception import ApiException
2121

2222
class JWTTokenManager(object):
23-
TOKEN_NAME = 'access_token'
24-
25-
def __init__(self, url, access_token=None):
23+
def __init__(self, url, access_token=None, token_name=None):
2624
"""
2725
Parameters
2826
----------
@@ -37,6 +35,7 @@ def __init__(self, url, access_token=None):
3735
self.time_to_live = None
3836
self.expire_time = None
3937
self.verify = None # to enable/ disable SSL verification
38+
self.token_name = token_name
4039

4140
def get_token(self):
4241
"""
@@ -57,7 +56,7 @@ def get_token(self):
5756
token_response = self.request_token()
5857
self._save_token_info(token_response)
5958

60-
return self.token_info.get(self.TOKEN_NAME)
59+
return self.token_info.get(self.token_name)
6160

6261
def set_access_token(self, access_token):
6362
"""
@@ -117,7 +116,7 @@ def _save_token_info(self, token_response):
117116
token_response : str
118117
Response from token service
119118
"""
120-
access_token = token_response.get(self.TOKEN_NAME)
119+
access_token = token_response.get(self.token_name)
121120

122121
# The time of expiration is found by decoding the JWT access token
123122
decoded_response = jwt.decode(access_token, verify=False)

test/test_base_service.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import jwt
88
from ibm_cloud_sdk_core import BaseService
99
from ibm_cloud_sdk_core import ApiException
10-
from ibm_cloud_sdk_core import ICPTokenManager
10+
from ibm_cloud_sdk_core import ICP4DTokenManager
1111

1212
class AnyServiceV1(BaseService):
1313
default_url = 'https://gateway.watsonplatform.net/test/api'
@@ -17,7 +17,8 @@ def __init__(self, version, url=default_url, username=None, password=None,
1717
iam_apikey=None,
1818
iam_access_token=None,
1919
iam_url=None,
20-
icp_access_token=None,
20+
icp4d_access_token=None,
21+
icp4d_url=None,
2122
authentication_type=None
2223
):
2324
BaseService.__init__(
@@ -32,7 +33,8 @@ def __init__(self, version, url=default_url, username=None, password=None,
3233
iam_access_token=iam_access_token,
3334
iam_url=iam_url,
3435
display_name='Watson',
35-
icp_access_token=icp_access_token,
36+
icp4d_access_token=icp4d_access_token,
37+
icp4d_url=icp4d_url,
3638
authentication_type=authentication_type)
3739
self.version = version
3840

@@ -223,29 +225,29 @@ def test_for_icp():
223225
assert service6.password is not None
224226

225227
def test_for_icp4d():
226-
service1 = AnyServiceV1('2017-07-07', username='hello', password='world', url='service_url', authentication_type='icp4d')
228+
service1 = AnyServiceV1('2017-07-07', username='hello', password='world', icp4d_url='service_url', authentication_type='icp4d')
227229
assert service1.token_manager is not None
228230
assert service1.iam_apikey is None
229231
assert service1.username is not None
230232
assert service1.password is not None
231-
assert service1.url == 'service_url'
232-
assert isinstance(service1.token_manager, ICPTokenManager)
233+
assert service1.icp4d_url == 'service_url'
234+
assert isinstance(service1.token_manager, ICP4DTokenManager)
233235

234-
service2 = AnyServiceV1('2017-07-07', icp_access_token='icp_access_token')
236+
service2 = AnyServiceV1('2017-07-07', icp4d_access_token='icp4d_access_token', icp4d_url='service_url')
235237
assert service2.token_manager is not None
236238
assert service2.iam_apikey is None
237239
assert service2.username is None
238240
assert service2.password is None
239-
assert isinstance(service2.token_manager, ICPTokenManager)
241+
assert isinstance(service2.token_manager, ICP4DTokenManager)
240242

241-
service3 = AnyServiceV1('2019-06-03', username='hello', password='world')
243+
service3 = AnyServiceV1('2019-06-03', username='hello', password='world', icp4d_url='icp4d_url')
242244
assert service3.username is not None
243245
assert service3.password is not None
244246
assert service3.token_manager is None
245247

246-
service3.set_icp_access_token('icp_access_token')
248+
service3.set_icp4d_access_token('icp4d_access_token')
247249
assert service3.token_manager is not None
248-
assert isinstance(service3.token_manager, ICPTokenManager)
250+
assert isinstance(service3.token_manager, ICP4DTokenManager)
249251

250252
def test_disable_SSL_verification():
251253
service1 = AnyServiceV1('2017-07-07', username='apikey', password='icp-xxxx', url='service_url')
@@ -254,7 +256,7 @@ def test_disable_SSL_verification():
254256
service1.disable_SSL_verification()
255257
assert service1.verify is False
256258

257-
service2 = AnyServiceV1('2017-07-07', username='hello', password='world', authentication_type='icp4d')
259+
service2 = AnyServiceV1('2017-07-07', username='hello', password='world', authentication_type='icp4d', icp4d_url='icp4d_url')
258260
assert service2.verify is None
259261
service2.disable_SSL_verification()
260262
assert service2.token_manager.verify is not None
@@ -354,11 +356,12 @@ def test_vcap_credentials():
354356

355357
vcap_services = '{"test":[{"credentials":{ \
356358
"url":"https://gateway.watsonplatform.net/compare-comply/api",\
357-
"icp_access_token":"bogus icp_access_token"}}]}'
359+
"icp4d_url":"https://test/",\
360+
"icp4d_access_token":"bogus icp4d_access_token"}}]}'
358361
os.environ['VCAP_SERVICES'] = vcap_services
359362
service = AnyServiceV1('2018-11-20')
360363
assert service.token_manager is not None
361-
assert service.token_manager.user_access_token == 'bogus icp_access_token'
364+
assert service.token_manager.user_access_token == 'bogus icp4d_access_token'
362365
del os.environ['VCAP_SERVICES']
363366

364367
@responses.activate

test/test_icp_token_manager.py renamed to test/test_icp4d_token_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import responses
22
import jwt
33
import json
4-
from ibm_cloud_sdk_core import ICPTokenManager
4+
from ibm_cloud_sdk_core import ICP4DTokenManager
55

66
@responses.activate
77
def test_request_token():
@@ -25,15 +25,15 @@ def test_request_token():
2525
'secret', algorithm='HS256',
2626
headers={'kid': '230498151c214b788dd97f22b85410a5'}).decode('utf-8')
2727
response = {
28-
"access_token": access_token,
28+
"accessToken": access_token,
2929
"token_type": "Bearer",
3030
"expires_in": 3600,
3131
"expiration": 1524167011,
3232
"refresh_token": "jy4gl91BQ"
3333
}
3434
responses.add(responses.GET, url + '/v1/preauth/validateAuth', body=json.dumps(response), status=200)
3535

36-
token_manager = ICPTokenManager(url, "username", "password")
36+
token_manager = ICP4DTokenManager(url, "username", "password")
3737
token_manager.disable_SSL_verification(True)
3838
token = token_manager.get_token()
3939

test/test_jwt_token_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def __init__(self, url=None, access_token=None):
88
self.url = url
99
self.access_token = access_token
1010
self.request_count = 0 # just for tests to see how many times request was called
11-
super(JWTTokenManagerMockImpl, self).__init__(url, access_token)
11+
super(JWTTokenManagerMockImpl, self).__init__(url, access_token, 'access_token')
1212

1313
def request_token(self):
1414
self.request_count += 1

0 commit comments

Comments
 (0)