Skip to content

Commit 2a1f307

Browse files
authored
Merge pull request #929 from watson-developer-cloud/iam-support
Add IAM support
2 parents 55f9868 + f44c5af commit 2a1f307

File tree

22 files changed

+761
-28
lines changed

22 files changed

+761
-28
lines changed

README.md

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ Java client library to use the [Watson APIs][wdc].
1515
* [Gradle](#gradle)
1616
* [Usage](#usage)
1717
* [Running in IBM Cloud](#running-in-ibm-cloud)
18-
* [Getting the Service Credentials](#getting-the-service-credentials)
18+
* [Authentication](#authentication)
19+
* [Username and Password](#username-and-password)
20+
* [API Key](#api-key)
21+
* [Using IAM](#using-iam)
1922
* IBM Watson Services
2023
* [Assistant](assistant)
2124
* [Discovery](discovery)
@@ -123,17 +126,92 @@ credentials; the library will get them for you by looking at the [`VCAP_SERVICES
123126
When running in IBM Cloud (or other platforms based on Cloud Foundry), the library will automatically get the credentials from [`VCAP_SERVICES`][vcap_services].
124127
If you have more than one plan, you can use `CredentialUtils` to get the service credentials for an specific plan.
125128

126-
## Getting the Service Credentials
129+
## Authentication
127130

128-
You will need the `username` and `password` (`api_key` for Visual Recognition) credentials, and the API endpoint for each service. Service credentials are different from your IBM Cloud account username and password.
131+
There are three ways to authenticate with IBM Cloud through the SDK: using a `username` and `password`, using an `api_key`, and with IAM.
129132

130-
To get your service credentials, follow these steps:
133+
Getting the credentials necessary for authentication is the same process for all methods. To get them, follow these steps:
131134

132135
1. Log in to [IBM Cloud](https://console.bluemix.net/catalog?category=watson)
133136
1. In the IBM Cloud **Catalog**, select the service you want to use.
134137
1. Click **Create**.
135138
1. On the left side of the page, click **Service Credentials**, and then **View credentials** to view your service credentials.
136-
1. Copy `url`, `username` and `password`(`api_key` for AlchemyAPI or Visual Recognition).
139+
1. Copy the necessary credentials (`url`, `username`, `password`, `api_key`, `apikey`, etc.).
140+
141+
In your code, you can use these values in the service constructor or with a method call after instantiating your service. Here are some examples:
142+
143+
### Username and Password
144+
145+
```java
146+
// in the constructor
147+
Discovery service = new Discovery("2017-11-07", "<username>", "<password>");
148+
```
149+
150+
```java
151+
// after instantiation
152+
Discovery service = new Discovery("2017-11-07");
153+
service.setUsernameAndPassword("<username>", "<password>");
154+
```
155+
156+
### API Key
157+
158+
_Note: This version of instantiation only works with Visual Recognition, as it's the only service that uses an API key rather than a username and password._
159+
160+
```java
161+
// in the constructor
162+
VisualRecognition service = new VisualRecognition("2016-05-20", "<api_key>");
163+
```
164+
165+
```java
166+
// after instantiation
167+
VisualRecognition service = new VisualRecognition("2016-05-20");
168+
service.setApiKey("<api_key>");
169+
```
170+
171+
### Using IAM
172+
173+
When authenticating with IAM, you have the option of passing in:
174+
- the IAM API key and, optionally, the IAM service URL
175+
- an IAM access token
176+
177+
**Be aware that passing in an access token means that you're assuming responsibility for maintaining that token's lifecycle.** If you instead pass in an IAM API key, the SDK will manage it for you.
178+
179+
```java
180+
// in the constructor, letting the SDK manage the IAM token
181+
IamOptions options = new IamOptions.Builder()
182+
.apiKey("<iam_api_key>")
183+
.url("<iam_url>") // optional - the default value is https://iam.ng.bluemix.net/identity/token
184+
.build();
185+
Discovery service = new Discovery("2017-11-07", options);
186+
```
187+
188+
```java
189+
// after instantiation, letting the SDK manage the IAM token
190+
Discovery service = new Discovery("2017-11-07");
191+
IamOptions options = new IamOptions.Builder()
192+
.apiKey("<iam_api_key>")
193+
.build();
194+
service.setIamCredentials(options);
195+
```
196+
197+
```java
198+
// in the constructor, assuming control of managing IAM token
199+
IamOptions options = new IamOptions.Builder()
200+
.accessToken("<access_token>")
201+
.build();
202+
Discovery service = new Discovery("2017-11-07", options);
203+
```
204+
205+
```java
206+
// after instantiation, assuming control of managing IAM token
207+
Discovery service = new Discovery("2017-11-07");
208+
IamOptions options = new IamOptions.Builder()
209+
.accessToken("<access_token>")
210+
.build();
211+
service.setIamCredentials(options);
212+
```
213+
214+
If at any time you would like to let the SDK take over managing your IAM token, simply override your stored IAM credentials with an IAM API key by calling the `setIamCredentials()` method again.
137215

138216
## Android
139217

assistant/src/main/java/com/ibm/watson/developer_cloud/assistant/v1/Assistant.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import com.ibm.watson.developer_cloud.http.RequestBuilder;
8282
import com.ibm.watson.developer_cloud.http.ServiceCall;
8383
import com.ibm.watson.developer_cloud.service.WatsonService;
84+
import com.ibm.watson.developer_cloud.service.security.IamOptions;
8485
import com.ibm.watson.developer_cloud.util.GsonSingleton;
8586
import com.ibm.watson.developer_cloud.util.ResponseConverterUtils;
8687
import com.ibm.watson.developer_cloud.util.Validator;
@@ -130,8 +131,20 @@ public Assistant(String versionDate, String username, String password) {
130131
}
131132

132133
/**
133-
* Get response to user input.
134+
* Instantiates a new `Assistant` with IAM. Note that if the access token is specified in the iamOptions,
135+
* you accept responsibility for managing the access token yourself. You must set a new access token before this one
136+
* expires. Failing to do so will result in authentication errors after this token expires.
134137
*
138+
* @param versionDate The version date (yyyy-MM-dd) of the REST API to use. Specifying this value will keep your API
139+
* calls from failing when the service introduces breaking changes.
140+
* @param iamOptions the options for authenticating through IAM
141+
*/
142+
public Assistant(String versionDate, IamOptions iamOptions) {
143+
this(versionDate);
144+
setIamCredentials(iamOptions);
145+
}
146+
147+
/**
135148
* Get a response to a user's input. There is no rate limit for this operation.
136149
*
137150
* @param messageOptions the {@link MessageOptions} containing the options for the call

conversation/src/main/java/com/ibm/watson/developer_cloud/conversation/v1/Conversation.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import com.ibm.watson.developer_cloud.http.RequestBuilder;
8282
import com.ibm.watson.developer_cloud.http.ServiceCall;
8383
import com.ibm.watson.developer_cloud.service.WatsonService;
84+
import com.ibm.watson.developer_cloud.service.security.IamOptions;
8485
import com.ibm.watson.developer_cloud.util.GsonSingleton;
8586
import com.ibm.watson.developer_cloud.util.ResponseConverterUtils;
8687
import com.ibm.watson.developer_cloud.util.Validator;
@@ -130,8 +131,20 @@ public Conversation(String versionDate, String username, String password) {
130131
}
131132

132133
/**
133-
* Get response to user input.
134+
* Instantiates a new `Conversation` with IAM. Note that if the access token is specified in the iamOptions,
135+
* you accept responsibility for managing the access token yourself. You must set a new access token before this one
136+
* expires. Failing to do so will result in authentication errors after this token expires.
134137
*
138+
* @param versionDate The version date (yyyy-MM-dd) of the REST API to use. Specifying this value will keep your API
139+
* calls from failing when the service introduces breaking changes.
140+
* @param iamOptions the options for authenticating through IAM
141+
*/
142+
public Conversation(String versionDate, IamOptions iamOptions) {
143+
this(versionDate);
144+
setIamCredentials(iamOptions);
145+
}
146+
147+
/**
135148
* Get a response to a user's input. There is no rate limit for this operation.
136149
*
137150
* @param messageOptions the {@link MessageOptions} containing the options for the call

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import com.ibm.watson.developer_cloud.service.exception.TooManyRequestsException;
3434
import com.ibm.watson.developer_cloud.service.exception.UnauthorizedException;
3535
import com.ibm.watson.developer_cloud.service.exception.UnsupportedException;
36+
import com.ibm.watson.developer_cloud.service.security.IamOptions;
37+
import com.ibm.watson.developer_cloud.service.security.IamTokenManager;
3638
import com.ibm.watson.developer_cloud.util.CredentialUtils;
3739
import com.ibm.watson.developer_cloud.util.RequestUtils;
3840
import com.ibm.watson.developer_cloud.util.ResponseConverterUtils;
@@ -66,12 +68,14 @@ public abstract class WatsonService {
6668
private static final String MESSAGE_ERROR_3 = "message";
6769
private static final String MESSAGE_ERROR_2 = "error_message";
6870
private static final String BASIC = "Basic ";
71+
private static final String BEARER = "Bearer ";
6972
private static final Logger LOG = Logger.getLogger(WatsonService.class.getName());
7073
private String apiKey;
7174
private String username;
7275
private String password;
7376
private String endPoint;
7477
private final String name;
78+
private IamTokenManager tokenManager;
7579

7680
private OkHttpClient client;
7781

@@ -97,6 +101,15 @@ public abstract class WatsonService {
97101
*/
98102
public WatsonService(final String name) {
99103
this.name = name;
104+
String iamApiKey = CredentialUtils.getIAMKey(name);
105+
String iamUrl = CredentialUtils.getIAMUrl(name);
106+
if (iamApiKey != null) {
107+
IamOptions iamOptions = new IamOptions.Builder()
108+
.apiKey(iamApiKey)
109+
.url(iamUrl)
110+
.build();
111+
tokenManager = new IamTokenManager(iamOptions);
112+
}
100113
apiKey = CredentialUtils.getAPIKey(name);
101114
String url = CredentialUtils.getAPIUrl(name);
102115
if ((url != null) && !url.isEmpty()) {
@@ -280,13 +293,17 @@ public void setApiKey(String apiKey) {
280293
* @param builder the new authentication
281294
*/
282295
protected void setAuthentication(final Builder builder) {
283-
if (getApiKey() == null) {
296+
if (tokenManager != null) {
297+
String accessToken = tokenManager.getToken();
298+
builder.addHeader(HttpHeaders.AUTHORIZATION, BEARER + accessToken);
299+
} else if (getApiKey() == null) {
284300
if (skipAuthentication) {
285301
return; // chosen to skip authentication with the service
286302
}
287303
throw new IllegalArgumentException("apiKey or username and password were not specified");
304+
} else {
305+
builder.addHeader(HttpHeaders.AUTHORIZATION, apiKey.startsWith(BASIC) ? apiKey : BASIC + apiKey);
288306
}
289-
builder.addHeader(HttpHeaders.AUTHORIZATION, apiKey.startsWith(BASIC) ? apiKey : BASIC + apiKey);
290307
}
291308

292309
/**
@@ -325,6 +342,19 @@ public void setDefaultHeaders(final Map<String, String> headers) {
325342
}
326343
}
327344

345+
/**
346+
* Sets IAM information.
347+
*
348+
* Be aware that if you pass in an access token using this method, you accept responsibility for managing the access
349+
* token yourself. You must set a new access token before this one expires. Failing to do so will result in
350+
* authentication errors after this token expires.
351+
*
352+
* @param iamOptions object containing values to be used for authenticating with IAM
353+
*/
354+
public void setIamCredentials(IamOptions iamOptions) {
355+
this.tokenManager = new IamTokenManager(iamOptions);
356+
}
357+
328358
/*
329359
* (non-Javadoc)
330360
*
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2018 IBM Corp. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.ibm.watson.developer_cloud.service.security;
14+
15+
/**
16+
* Options for authenticating using IAM.
17+
*/
18+
public class IamOptions {
19+
private String apiKey;
20+
private String accessToken;
21+
private String url;
22+
23+
public String getApiKey() {
24+
return apiKey;
25+
}
26+
27+
public String getAccessToken() {
28+
return accessToken;
29+
}
30+
31+
public String getUrl() {
32+
return url;
33+
}
34+
35+
public static class Builder {
36+
private String apiKey;
37+
private String accessToken;
38+
private String url;
39+
40+
public IamOptions build() {
41+
return new IamOptions(this);
42+
}
43+
44+
public Builder apiKey(String apiKey) {
45+
this.apiKey = apiKey;
46+
return this;
47+
}
48+
49+
public Builder accessToken(String accessToken) {
50+
this.accessToken = accessToken;
51+
return this;
52+
}
53+
54+
public Builder url(String url) {
55+
this.url = url;
56+
return this;
57+
}
58+
}
59+
60+
private IamOptions(Builder builder) {
61+
this.apiKey = builder.apiKey;
62+
this.accessToken = builder.accessToken;
63+
this.url = builder.url;
64+
}
65+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2018 IBM Corp. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.ibm.watson.developer_cloud.service.security;
14+
15+
import com.google.gson.annotations.SerializedName;
16+
import com.ibm.watson.developer_cloud.service.model.ObjectModel;
17+
18+
/**
19+
* Represents response from IAM API.
20+
*/
21+
public class IamToken implements ObjectModel {
22+
@SerializedName("access_token")
23+
private String accessToken;
24+
@SerializedName("refresh_token")
25+
private String refreshToken;
26+
@SerializedName("token_type")
27+
private String tokenType;
28+
@SerializedName("expires_in")
29+
private Long expiresIn;
30+
private Long expiration;
31+
32+
public String getAccessToken() {
33+
return accessToken;
34+
}
35+
36+
public String getRefreshToken() {
37+
return refreshToken;
38+
}
39+
40+
public String getTokenType() {
41+
return tokenType;
42+
}
43+
44+
public Long getExpiresIn() {
45+
return expiresIn;
46+
}
47+
48+
public Long getExpiration() {
49+
return expiration;
50+
}
51+
}

0 commit comments

Comments
 (0)