Skip to content

Commit 6ceb0d3

Browse files
Merge pull request #1027 from watson-developer-cloud/support-credentials-file
Support credential file
2 parents 5d1bee8 + 19ed0d6 commit 6ceb0d3

File tree

5 files changed

+245
-63
lines changed

5 files changed

+245
-63
lines changed

README.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,53 @@ Watson services are migrating to token-based Identity and Access Management (IAM
141141
**Note:** Previously, it was possible to authenticate using a token in a header called `X-Watson-Authorization-Token`. This method is deprecated. The token continues to work with Cloud Foundry services, but is not supported for services that use Identity and Access Management (IAM) authentication. See [here](#iam) for details.
142142

143143
### Getting credentials
144+
144145
To find out which authentication to use, view the service credentials. You find the service credentials for authentication the same way for all Watson services:
145146

146-
1. Go to the IBM Cloud [Dashboard](https://console.bluemix.net/dashboard/apps?category=ai) page.
147-
1. Either click an existing Watson service instance or click [**Create resource > AI**](https://console.bluemix.net/catalog/?category=ai) and create a service instance.
148-
1. Copy the credentials you need for authentication. Click **Show** if the credentials are masked.
147+
1. Go to the IBM Cloud [Dashboard](https://cloud.ibm.com/) page.
148+
1. Either click an existing Watson service instance in your [resource list](https://cloud.ibm.com/resources) or click [**Create resource > AI**](https://cloud.ibm.com/catalog?category=ai) and create a service instance.
149+
1. Click on the **Manage** item in the left nav bar of your service instance.
150+
151+
On this page, you should be able to see your credentials for accessing your service instance.
149152

150153
In your code, you can use these values in the service constructor or with a method call after instantiating your service.
151154

152-
### IAM
155+
### Supplying credentials
156+
157+
There are two ways to supply the credentials you found above to the SDK for authentication.
158+
159+
#### Credential file (easier!)
160+
161+
With a credential file, you just need to put the file in the right place and the SDK will do the work of parsing it and authenticating. You can get this file by clicking the **Download** button for the credentials in the **Manage** tab of your service instance.
162+
163+
The file downloaded will be called `ibm-credentials.env`. This is the name the SDK will search for and **must** be preserved unless you want to configure the file path (more on that later). The SDK will look for your `ibm-credentials.env` file in the following places (in order):
164+
165+
- Your system's home directory
166+
- The top-level directory of the project you're using the SDK in
167+
168+
As long as you set that up correctly, you don't have to worry about setting any authentication options in your code. So, for example, if you created and downloaded the credential file for your Discovery instance, you just need to do the following:
169+
170+
```java
171+
Discovery service = new Discovery("2017-11-07");
172+
```
173+
174+
And that's it!
175+
176+
If you're using more than one service at a time in your code and get two different `ibm-credentials.env` files, just put the contents together in one `ibm-credentials.env` file and the SDK will handle assigning credentials to their appropriate services.
177+
178+
If you would like to configure the location/name of your credential file, you can set an environment variable called `IBM_CREDENTIALS_FILE`. **This will take precedence over the locations specified above.** Here's how you can do that:
179+
180+
```bash
181+
export IBM_CREDENTIALS_FILE="<path>"
182+
```
183+
184+
where `<path>` is something like `/home/user/Downloads/<file_name>.env`.
185+
186+
#### Manually
187+
188+
If you'd prefer to set authentication values manually in your code, the SDK supports that as well. The way you'll do this depends on what type of credentials your service instance gives you.
189+
190+
##### IAM
153191

154192
Some services use token-based Identity and Access Management (IAM) authentication. IAM authentication uses a service API key to get an access token that is passed with the call. Access tokens are valid for approximately one hour and must be regenerated.
155193

@@ -159,7 +197,8 @@ You supply either an IAM service **API key** or an **access token**:
159197
- Use the access token if you want to manage the lifecycle yourself. For details, see [Authenticating with IAM tokens](https://console.bluemix.net/docs/services/watson/getting-started-iam.html). If you want to switch to API key, override your stored IAM credentials with an IAM API key. Then call the `setIamCredentials()` method again.
160198

161199

162-
#### Supplying the IAM API key
200+
Supplying the IAM API key:
201+
163202
```java
164203
// in the constructor, letting the SDK manage the IAM token
165204
IamOptions options = new IamOptions.Builder()
@@ -178,7 +217,8 @@ IamOptions options = new IamOptions.Builder()
178217
service.setIamCredentials(options);
179218
```
180219

181-
#### Supplying the access token
220+
Supplying the access token:
221+
182222
```java
183223
// in the constructor, assuming control of managing IAM token
184224
IamOptions options = new IamOptions.Builder()
@@ -196,7 +236,7 @@ IamOptions options = new IamOptions.Builder()
196236
service.setIamCredentials(options);
197237
```
198238

199-
### Username and password
239+
#### Username and password
200240

201241
```java
202242
// in the constructor

core/ibm-credentials.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
NATURAL_LANGUAGE_CLASSIFIER_APIKEY=123456789
2+
NATURAL_LANGUAGE_CLASSIFIER_URL=https://gateway.watsonplatform.net/natural-language-classifier/api

core/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,15 @@ public abstract class WatsonService {
117117
*/
118118
public WatsonService(final String name) {
119119
this.name = name;
120-
setCredentialFields(CredentialUtils.getCredentialsFromVcap(name));
120+
121+
// file credentials take precedence
122+
CredentialUtils.ServiceCredentials fileCredentials = CredentialUtils.getFileCredentials(name);
123+
if (!fileCredentials.isEmpty()) {
124+
setCredentialFields(fileCredentials);
125+
} else {
126+
setCredentialFields(CredentialUtils.getCredentialsFromVcap(name));
127+
}
128+
121129
client = configureHttpClient();
122130
}
123131

core/src/main/java/com/ibm/watson/developer_cloud/util/CredentialUtils.java

Lines changed: 162 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@
1212
*/
1313
package com.ibm.watson.developer_cloud.util;
1414

15-
import java.util.Hashtable;
16-
import java.util.Map.Entry;
17-
import java.util.logging.Level;
18-
import java.util.logging.Logger;
19-
20-
import javax.naming.Context;
21-
import javax.naming.InitialContext;
22-
2315
import com.google.gson.JsonArray;
2416
import com.google.gson.JsonElement;
2517
import com.google.gson.JsonObject;
2618
import com.google.gson.JsonParser;
2719
import com.google.gson.JsonSyntaxException;
20+
import org.apache.commons.io.IOUtils;
21+
22+
import javax.naming.Context;
23+
import javax.naming.InitialContext;
24+
import java.io.File;
25+
import java.io.FileInputStream;
26+
import java.io.IOException;
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.ArrayList;
29+
import java.util.Hashtable;
30+
import java.util.List;
31+
import java.util.Map.Entry;
32+
import java.util.logging.Level;
33+
import java.util.logging.Logger;
2834

2935
/**
3036
* CredentialUtils retrieves service credentials from the environment.
@@ -37,14 +43,16 @@ public final class CredentialUtils {
3743
*/
3844
public static class ServiceCredentials {
3945
private String username;
46+
private String password;
4047
private String oldApiKey;
4148
private String url;
4249
private String iamApiKey;
4350
private String iamUrl;
44-
private String password;
4551

46-
private ServiceCredentials(String username, String oldApiKey, String url, String iamApiKey, String iamUrl,
47-
String password) {
52+
private ServiceCredentials() { }
53+
54+
private ServiceCredentials(String username, String password, String oldApiKey, String url, String iamApiKey,
55+
String iamUrl) {
4856
this.username = username;
4957
this.password = password;
5058
this.oldApiKey = oldApiKey;
@@ -106,59 +114,43 @@ public String getIamApiKey() {
106114
public String getIamUrl() {
107115
return iamUrl;
108116
}
109-
}
110-
111-
/** The Constant VCAP_SERVICES. */
112-
private static final String VCAP_SERVICES = "VCAP_SERVICES";
113117

114-
/** The Constant APIKEY. */
115-
private static final String APIKEY = "apikey";
118+
/**
119+
* Returns true if no fields are set on the object.
120+
*
121+
* @return whether the object has any set fields
122+
*/
123+
public boolean isEmpty() {
124+
return (username == null
125+
&& password == null
126+
&& oldApiKey == null
127+
&& url == null
128+
&& iamApiKey == null
129+
&& iamUrl == null);
130+
}
131+
}
116132

117-
/** The Constant CREDENTIALS. */
118-
private static final String CREDENTIALS = "credentials";
133+
static final String PLAN_STANDARD = "standard";
119134

120-
/** The Constant log. */
135+
private static String services;
136+
private static Context context;
121137
private static final Logger log = Logger.getLogger(CredentialUtils.class.getName());
122138

123-
/** The Constant PASSWORD. */
124-
private static final String PASSWORD = "password";
125-
126-
/** The Constant PLAN. */
127-
private static final String PLAN = "plan";
139+
private static final String DEFAULT_CREDENTIAL_FILE_NAME = "ibm-credentials.env";
128140

129-
/** The services. */
130-
private static String services;
131-
132-
/** The context. */
133-
private static Context context;
141+
private static final String VCAP_SERVICES = "VCAP_SERVICES";
142+
private static final String LOOKUP_NAME_EXTENSION_API_KEY = "/credentials";
143+
private static final String LOOKUP_NAME_EXTENSION_URL = "/url";
134144

135-
/** The Constant USERNAME. */
145+
private static final String CREDENTIALS = "credentials";
146+
private static final String PLAN = "plan";
136147
private static final String USERNAME = "username";
137-
138-
/** The Constant URL. */
148+
private static final String PASSWORD = "password";
149+
private static final String OLD_APIKEY = "api_key";
139150
private static final String URL = "url";
140-
141-
/** The Constant IAM_URL. */
151+
private static final String IAM_APIKEY = "apikey";
142152
private static final String IAM_URL = "iam_url";
143153

144-
/** The Constant PLAN_EXPERIMENTAL. */
145-
public static final String PLAN_EXPERIMENTAL = "experimental";
146-
147-
/** The Constant PLAN_FREE. */
148-
public static final String PLAN_FREE = "free";
149-
150-
/** The Constant PLAN_STANDARD. */
151-
public static final String PLAN_STANDARD = "standard";
152-
153-
/** The Constant API_KEY. */
154-
private static final String API_KEY = "api_key";
155-
156-
/** The Constant LOOKUP_NAME_EXTENSION_API_KEY. */
157-
private static final String LOOKUP_NAME_EXTENSION_API_KEY = "/credentials";
158-
159-
/** The Constant LOOKUP_NAME_EXTENSION_URL. */
160-
private static final String LOOKUP_NAME_EXTENSION_URL = "/url";
161-
162154
private CredentialUtils() {
163155
// This is a utility class - no instantiation allowed.
164156
}
@@ -190,7 +182,7 @@ public static boolean hasBadStartOrEndChar(String credentialValue) {
190182
public static ServiceCredentials getCredentialsFromVcap(String serviceName) {
191183
String username = getVcapValue(serviceName, USERNAME);
192184
String password = getVcapValue(serviceName, PASSWORD);
193-
String oldApiKey = getVcapValue(serviceName, API_KEY);
185+
String oldApiKey = getVcapValue(serviceName, OLD_APIKEY);
194186
if (username == null && password == null && oldApiKey == null) {
195187
oldApiKey = getJdniValue(serviceName, LOOKUP_NAME_EXTENSION_API_KEY);
196188
}
@@ -200,7 +192,7 @@ public static ServiceCredentials getCredentialsFromVcap(String serviceName) {
200192
url = getJdniValue(serviceName, LOOKUP_NAME_EXTENSION_URL);
201193
}
202194

203-
String iamApiKey = getVcapValue(serviceName, APIKEY);
195+
String iamApiKey = getVcapValue(serviceName, IAM_APIKEY);
204196
String iamUrl = getVcapValue(serviceName, IAM_URL);
205197

206198
return new ServiceCredentials(username, password, oldApiKey, url, iamApiKey, iamUrl);
@@ -352,4 +344,119 @@ public static void setContext(Hashtable<String, String> env) {
352344
log.fine("Error setting up JDNI context: " + e.getMessage());
353345
}
354346
}
347+
348+
// Credential file-related methods
349+
350+
/**
351+
* Calls methods to find and parse a credential file in various locations.
352+
*
353+
* @param serviceName the service name
354+
* @return ServiceCredentials object containing parsed values
355+
*/
356+
public static ServiceCredentials getFileCredentials(String serviceName) {
357+
List<File> files = getFilesToCheck();
358+
List<String> credentialFileContents = getFirstExistingFileContents(files);
359+
return setCredentialFields(serviceName, credentialFileContents);
360+
}
361+
362+
/**
363+
* Creates a list of files to check for credentials. The file locations are:
364+
* * Location provided by user-specified IBM_CREDENTIALS_FILE environment variable
365+
* * System home directory (Unix)
366+
* * System home directory (Windows)
367+
* * Top-level directory of the project this code is being called in
368+
*
369+
* @return list of credential files to check
370+
*/
371+
private static List<File> getFilesToCheck() {
372+
List<File> files = new ArrayList<>();
373+
374+
String userSpecifiedPath = System.getenv("IBM_CREDENTIALS_FILE");
375+
String unixHomeDirectory = System.getenv("HOME");
376+
String windowsFirstHomeDirectory = System.getenv("HOMEDRIVE") + System.getenv("HOMEPATH");
377+
String windowsSecondHomeDirectory = System.getenv("USERPROFILE");
378+
String projectDirectory = System.getProperty("user.dir");
379+
380+
if (userSpecifiedPath != null) {
381+
files.add(new File(userSpecifiedPath));
382+
}
383+
files.add(new File(String.format("%s/%s", unixHomeDirectory, DEFAULT_CREDENTIAL_FILE_NAME)));
384+
files.add(new File(String.format("%s/%s", windowsFirstHomeDirectory, DEFAULT_CREDENTIAL_FILE_NAME)));
385+
files.add(new File(String.format("%s/%s", windowsSecondHomeDirectory, DEFAULT_CREDENTIAL_FILE_NAME)));
386+
files.add(new File(String.format("%s/%s", projectDirectory, DEFAULT_CREDENTIAL_FILE_NAME)));
387+
388+
return files;
389+
}
390+
391+
/**
392+
* Looks through the provided list of files to search for credentials, stopping at the first existing file.
393+
*
394+
* @return list of lines in the credential file, or null if no file is found
395+
*/
396+
private static List<String> getFirstExistingFileContents(List<File> files) {
397+
List<String> credentialFileContents = null;
398+
399+
try {
400+
for (File file : files) {
401+
if (file.isFile()) {
402+
credentialFileContents = IOUtils.readLines(new FileInputStream(file), StandardCharsets.UTF_8);
403+
break;
404+
}
405+
}
406+
} catch (IOException e) {
407+
log.severe("There was a problem trying to read the credential file: " + e);
408+
}
409+
410+
return credentialFileContents;
411+
}
412+
413+
/**
414+
* Parses provided list of strings to create and set values for a ServiceCredentials instance.
415+
*
416+
* @param serviceName the service name
417+
* @param credentialFileContents list of lines in the user's credential file
418+
* @return ServiceCredentials object containing the parsed values
419+
*/
420+
private static ServiceCredentials setCredentialFields(String serviceName, List<String> credentialFileContents) {
421+
ServiceCredentials serviceCredentials = new ServiceCredentials();
422+
423+
if (credentialFileContents == null) {
424+
return serviceCredentials;
425+
}
426+
427+
for (String line : credentialFileContents) {
428+
String[] keyAndVal = line.split("=");
429+
String lowercaseKey = keyAndVal[0].toLowerCase();
430+
if (lowercaseKey.contains(serviceName)) {
431+
String credentialType = lowercaseKey.substring(serviceName.length() + 1);
432+
String credentialValue = keyAndVal[1];
433+
434+
switch (credentialType) {
435+
case USERNAME:
436+
serviceCredentials.username = credentialValue;
437+
break;
438+
case PASSWORD:
439+
serviceCredentials.password = credentialValue;
440+
break;
441+
case OLD_APIKEY:
442+
serviceCredentials.oldApiKey = credentialValue;
443+
break;
444+
case URL:
445+
serviceCredentials.url = credentialValue;
446+
break;
447+
case IAM_APIKEY:
448+
serviceCredentials.iamApiKey = credentialValue;
449+
break;
450+
case IAM_URL:
451+
serviceCredentials.iamUrl = credentialValue;
452+
break;
453+
default:
454+
log.warning("Unknown credential key found in credential file: " + credentialType);
455+
break;
456+
}
457+
}
458+
}
459+
460+
return serviceCredentials;
461+
}
355462
}

0 commit comments

Comments
 (0)