Skip to content

Commit 830c28f

Browse files
committed
feat: support api key use case for CP4D authenticator
1 parent 1fe1864 commit 830c28f

11 files changed

+194
-65
lines changed

.secrets.baseline

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "package-lock.json|^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2021-03-18T14:18:04Z",
6+
"generated_at": "2021-05-04T15:09:09Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -173,9 +173,17 @@
173173
"hashed_secret": "da2f27d2c57a0e1ed2dc3a34b4ef02faf2f7a4c2",
174174
"is_secret": false,
175175
"is_verified": false,
176-
"line_number": 112,
176+
"line_number": 113,
177177
"type": "Hex High Entropy String",
178178
"verified_result": null
179+
},
180+
{
181+
"hashed_secret": "5eb942810a75ebc850972a89285d570d484c89c4",
182+
"is_secret": false,
183+
"is_verified": false,
184+
"line_number": 307,
185+
"type": "Secret Keyword",
186+
"verified_result": null
179187
}
180188
],
181189
"test/test_cp4d_authenticator.py": [
@@ -186,6 +194,14 @@
186194
"line_number": 88,
187195
"type": "Hex High Entropy String",
188196
"verified_result": null
197+
},
198+
{
199+
"hashed_secret": "5eb942810a75ebc850972a89285d570d484c89c4",
200+
"is_secret": false,
201+
"is_verified": false,
202+
"line_number": 99,
203+
"type": "Secret Keyword",
204+
"verified_result": null
189205
}
190206
],
191207
"test/test_cp4d_token_manager.py": [
@@ -287,7 +303,7 @@
287303
}
288304
]
289305
},
290-
"version": "0.13.1+ibm.29.dss",
306+
"version": "0.13.1+ibm.34.dss",
291307
"word_list": {
292308
"file": null,
293309
"hash": null

Authentication.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ service = ExampleService(authenticator=authenticator)
140140
```
141141

142142
## Cloud Pak for Data
143-
The `CloudPakForDataAuthenticator` will accept user-supplied username and password values, and will
143+
The `CloudPakForDataAuthenticator` will accept a user-supplied username and either a password or an apikey value, and will
144144
perform the necessary interactions with the Cloud Pak for Data token service to obtain a suitable
145145
bearer token. The authenticator will also obtain a new bearer token when the current token expires.
146146
The bearer token is then added to each outbound request in the `Authorization` header in the
@@ -150,8 +150,9 @@ form:
150150
```
151151
### Properties
152152
- username: (required) the username used to obtain a bearer token.
153-
- password: (required) the password used to obtain a bearer token.
153+
- password: (required if apikey is not specified) the password used to obtain a bearer token.
154154
- url: (required) The URL representing the Cloud Pak for Data token service endpoint.
155+
- apikey: (required if password is not specified) the apikey used to obtain a bearer token.
155156
- disableSSLVerification: (optional) A flag that indicates whether verificaton of the server's SSL
156157
certificate should be disabled or not. The default value is `false`.
157158
- headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
@@ -160,22 +161,38 @@ made to the IAM token service.
160161
```python
161162
from ibm_cloud_sdk_core.authenticators import CloudPakForDataAuthenticator
162163

164+
# Username / password authentication
163165
authenticator = CloudPakForDataAuthenticator(
164-
'my_username',
165-
'my_password',
166-
'https://my-cp4d-url',
166+
username='my_username',
167+
password='my_password',
168+
url='https://my-cp4d-url',
167169
disable_ssl_verification=True)
170+
171+
# Username / apikey authentication
172+
authenticator = CloudPakForDataAuthenticator(
173+
username='my_username',
174+
apikey='my_apikey',
175+
url='https://my-cp4d-url',
176+
disable_ssl_verification=True)
177+
168178
service = ExampleService(authenticator=authenticator)
169179

170180
service.get_authenticator().set_headers({'dummy': 'headers'})
171181
```
172182
### Configuration example
173183
External configuration:
174184
```
185+
# Username / password authentication
175186
export EXAMPLE_SERVICE_AUTH_TYPE=cp4d
176187
export EXAMPLE_SERVICE_USERNAME=myuser
177188
export EXAMPLE_SERVICE_PASSWORD=mypassword
178189
export EXAMPLE_SERVICE_URL=https://mycp4dhost.com/
190+
191+
# Username / apikey authentication
192+
export EXAMPLE_SERVICE_AUTH_TYPE=cp4d
193+
export EXAMPLE_SERVICE_USERNAME=myuser
194+
export EXAMPLE_SERVICE_APIKEY=myapikey
195+
export EXAMPLE_SERVICE_URL=https://mycp4dhost.com/
179196
```
180197
Application code:
181198
```python

ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ class CloudPakForDataAuthenticator(Authenticator):
3131
3232
Authorization: Bearer <bearer-token>
3333
34-
Args:
35-
username: The username used to obtain a bearer token.
36-
password: The password used to obtain a bearer token.
37-
url: The URL representing the Cloud Pak for Data token service endpoint.
38-
3934
Keyword Args:
35+
username: The username used to obtain a bearer token [required].
36+
password: The password used to obtain a bearer token [required if apikey not specified].
37+
url: The URL representing the Cloud Pak for Data token service endpoint [required].
38+
apikey: The API key used to obtain a bearer token [required if password not specified].
4039
disable_ssl_verification: A flag that indicates whether verification of the server's SSL
4140
certificate should be disabled or not. Defaults to False.
4241
headers: Default headers to be sent with every CP4D token request. Defaults to None.
@@ -48,21 +47,22 @@ class CloudPakForDataAuthenticator(Authenticator):
4847
token_manager (CP4DTokenManager): Retrives and manages CP4D tokens from the endpoint specified by the url.
4948
5049
Raises:
51-
ValueError: The username, password, and/or url are not valid for CP4D token requests.
50+
ValueError: The username, password/apikey, and/or url are not valid for CP4D token requests.
5251
"""
5352
authenticationdict = 'cp4d'
5453

5554
def __init__(self,
56-
username: str,
57-
password: str,
58-
url: str,
55+
username: str = None,
56+
password: str = None,
57+
url: str = None,
5958
*,
59+
apikey: str = None,
6060
disable_ssl_verification: bool = False,
6161
headers: Optional[Dict[str, str]] = None,
6262
proxies: Optional[Dict[str, str]] = None) -> None:
6363
self.token_manager = CP4DTokenManager(
64-
username, password, url, disable_ssl_verification=disable_ssl_verification,
65-
headers=headers, proxies=proxies)
64+
username=username, password=password, apikey=apikey, url=url,
65+
disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies)
6666
self.validate()
6767

6868
def validate(self) -> None:
@@ -74,8 +74,12 @@ def validate(self) -> None:
7474
Raises:
7575
ValueError: The username, password, and/or url are not valid for token requests.
7676
"""
77-
if self.token_manager.username is None or self.token_manager.password is None:
78-
raise ValueError('The username and password shouldn\'t be None.')
77+
if self.token_manager.username is None:
78+
raise ValueError('The username shouldn\'t be None.')
79+
80+
if ((self.token_manager.password is None and self.token_manager.apikey is None)
81+
or (self.token_manager.password is not None and self.token_manager.apikey is not None)):
82+
raise ValueError('Exactly one of `apikey` or `password` must be specified.')
7983

8084
if self.token_manager.url is None:
8185
raise ValueError('The url shouldn\'t be None.')

ibm_cloud_sdk_core/cp4d_token_manager.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17+
import json
1718
from typing import Dict, Optional
1819
from .jwt_token_manager import JWTTokenManager
1920

@@ -24,12 +25,11 @@ class CP4DTokenManager(JWTTokenManager):
2425
The Token Manager performs basic auth with a username and password
2526
to acquire JWT tokens.
2627
27-
Args:
28-
username: The username for authentication.
29-
password: The password for authentication.
30-
url: The endpoint for JWT token requests.
31-
3228
Keyword Arguments:
29+
username: The username for authentication [required].
30+
password: The password for authentication [required if apikey not specified].
31+
url: The endpoint for JWT token requests [required].
32+
apikey: The apikey for authentication [required if password not specified].
3333
disable_ssl_verification: Disable ssl verification. Defaults to False.
3434
headers: Headers to be sent with every service token request. Defaults to None.
3535
proxies: Proxies to use for making request. Defaults to None.
@@ -45,36 +45,43 @@ class CP4DTokenManager(JWTTokenManager):
4545
proxies.http (str): The proxy endpoint to use for HTTP requests.
4646
proxies.https (str): The proxy endpoint to use for HTTPS requests.
4747
"""
48-
TOKEN_NAME = 'accessToken'
49-
VALIDATE_AUTH_PATH = '/v1/preauth/validateAuth'
48+
TOKEN_NAME = 'token'
49+
VALIDATE_AUTH_PATH = '/v1/authorize'
5050

5151
def __init__(self,
52-
username: str,
53-
password: str,
54-
url: str,
52+
username: str = None,
53+
password: str = None,
54+
url: str = None,
5555
*,
56+
apikey: str = None,
5657
disable_ssl_verification: bool = False,
5758
headers: Optional[Dict[str, str]] = None,
5859
proxies: Optional[Dict[str, str]] = None) -> None:
5960
self.username = username
6061
self.password = password
6162
if url and not self.VALIDATE_AUTH_PATH in url:
62-
url = url + '/v1/preauth/validateAuth'
63+
url = url + '/v1/authorize'
64+
self.apikey = apikey
6365
self.headers = headers
66+
if self.headers is None:
67+
self.headers = {}
68+
self.headers['Content-Type'] = 'application/json'
6469
self.proxies = proxies
6570
super().__init__(url, disable_ssl_verification=disable_ssl_verification,
6671
token_name=self.TOKEN_NAME)
6772

6873
def request_token(self) -> dict:
6974
"""Makes a request for a token.
7075
"""
71-
auth_tuple = (self.username, self.password)
72-
7376
response = self._request(
74-
method='GET',
77+
method='POST',
7578
headers=self.headers,
7679
url=self.url,
77-
auth_tuple=auth_tuple,
80+
data=json.dumps({
81+
"username": self.username,
82+
"password": self.password,
83+
"api_key": self.apikey
84+
}),
7885
proxies=self.proxies)
7986
return response
8087

ibm_cloud_sdk_core/get_authenticator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __construct_authenticator(config: dict) -> Authenticator:
5454
username=config.get('USERNAME'),
5555
password=config.get('PASSWORD'),
5656
url=config.get('AUTH_URL'),
57+
apikey=config.get('APIKEY'),
5758
disable_ssl_verification=config.get('AUTH_DISABLE_SSL'))
5859
elif auth_type == 'iam' and config.get('APIKEY'):
5960
authenticator = IAMAuthenticator(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CP4D_PASSWORD_TEST_AUTH_URL=<url> e.g. https://cpd350-cpd-cpd350.apps.wml-kf-cluster.os.fyre.ibm.com/icp4d-api
2+
CP4D_PASSWORD_TEST_AUTH_TYPE=cp4d
3+
CP4D_PASSWORD_TEST_USERNAME=<username>
4+
CP4D_PASSWORD_TEST_PASSWORD=<password>
5+
CP4D_PASSWORD_TEST_AUTH_DISABLE_SSL=true
6+
7+
CP4D_APIKEY_TEST_AUTH_URL=<url> e.g. https://cpd350-cpd-cpd350.apps.wml-kf-cluster.os.fyre.ibm.com/icp4d-api
8+
CP4D_APIKEY_TEST_AUTH_TYPE=cp4d
9+
CP4D_APIKEY_TEST_USERNAME=<username>
10+
CP4D_APIKEY_TEST_APIKEY=<apikey>
11+
CP4D_APIKEY_TEST_AUTH_DISABLE_SSL=true

test/test_base_service.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def test_for_cp4d():
293293
assert service.authenticator.token_manager is not None
294294
assert service.authenticator.token_manager.username == 'my_username'
295295
assert service.authenticator.token_manager.password == 'my_password'
296-
assert service.authenticator.token_manager.url == 'my_url/v1/preauth/validateAuth'
296+
assert service.authenticator.token_manager.url == 'my_url/v1/authorize'
297297
assert isinstance(service.authenticator.token_manager, CP4DTokenManager)
298298

299299

@@ -648,10 +648,10 @@ def test_files_dict():
648648
service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
649649

650650
form_data = {}
651-
file = open(
651+
with open(
652652
os.path.join(
653-
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r')
654-
form_data['file1'] = (None, file, 'application/octet-stream')
653+
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r') as file:
654+
form_data['file1'] = (None, file, 'application/octet-stream')
655655
form_data['string1'] = (None, 'hello', 'text/plain')
656656
request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data)
657657
files = request['files']
@@ -667,10 +667,10 @@ def test_files_list():
667667
service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
668668

669669
form_data = []
670-
file = open(
670+
with open(
671671
os.path.join(
672-
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r')
673-
form_data.append(('file1', (None, file, 'application/octet-stream')))
672+
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r') as file:
673+
form_data.append(('file1', (None, file, 'application/octet-stream')))
674674
form_data.append(('string1', (None, 'hello', 'text/plain')))
675675
request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data)
676676
files = request['files']
@@ -686,18 +686,18 @@ def test_files_duplicate_parts():
686686
service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
687687

688688
form_data = []
689-
file = open(
689+
with open(
690690
os.path.join(
691-
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r')
692-
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
693-
file = open(
691+
os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r') as file:
692+
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
693+
with open(
694694
os.path.join(
695-
os.path.dirname(__file__), '../resources/ibm-credentials-basic.env'), 'r')
696-
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
697-
file = open(
695+
os.path.dirname(__file__), '../resources/ibm-credentials-basic.env'), 'r') as file:
696+
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
697+
with open(
698698
os.path.join(
699-
os.path.dirname(__file__), '../resources/ibm-credentials-bearer.env'), 'r')
700-
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
699+
os.path.dirname(__file__), '../resources/ibm-credentials-bearer.env'), 'r') as file:
700+
form_data.append(('creds_file', (None, file, 'application/octet-stream')))
701701
request = service.prepare_request('GET', url='', headers={'X-opt-out': True}, files=form_data)
702702
files = request['files']
703703
assert isinstance(files, list)

0 commit comments

Comments
 (0)