Skip to content

Commit

Permalink
[okhttp-gson] Add support for OAuth access token retry (OpenAPITools#…
Browse files Browse the repository at this point in the history
…1058)

* Add support for access token retry in okhttp-gson lib

* Update expected number of generated files in test

* Update samples

* Update security samples

* Fix default user-agent and update samples
  • Loading branch information
Kiran-Sivakumar authored and jaumard committed Sep 21, 2018
1 parent 8b479de commit 4819e84
Show file tree
Hide file tree
Showing 29 changed files with 1,166 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("ProgressRequestBody.mustache", invokerFolder, "ProgressRequestBody.java"));
supportingFiles.add(new SupportingFile("ProgressResponseBody.mustache", invokerFolder, "ProgressResponseBody.java"));
supportingFiles.add(new SupportingFile("GzipRequestInterceptor.mustache", invokerFolder, "GzipRequestInterceptor.java"));
supportingFiles.add(new SupportingFile("auth/OAuthOkHttpClient.mustache", authFolder, "OAuthOkHttpClient.java"));
supportingFiles.add(new SupportingFile("auth/RetryingOAuth.mustache", authFolder, "RetryingOAuth.java"));
additionalProperties.put("gson", "true");
} else if (usesAnyRetrofitLibrary()) {
supportingFiles.add(new SupportingFile("auth/OAuthOkHttpClient.mustache", authFolder, "OAuthOkHttpClient.java"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;
{{/threetenbp}}

import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import org.apache.oltu.oauth2.common.message.types.GrantType;

import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -50,6 +53,7 @@ import {{invokerPackage}}.auth.Authentication;
import {{invokerPackage}}.auth.HttpBasicAuth;
import {{invokerPackage}}.auth.ApiKeyAuth;
import {{invokerPackage}}.auth.OAuth;
import {{invokerPackage}}.auth.RetryingOAuth;

public class ApiClient {
Expand Down Expand Up @@ -78,6 +82,38 @@ public class ApiClient {
* Constructor for ApiClient
*/
public ApiClient() {
init();
// Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}
authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}}
authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}}
authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
{{#authMethods}}{{#isOAuth}}
/*
* Constructor for ApiClient to support access token retry on 401/403
*/
public ApiClient(
String clientId,
String clientSecret,
Map<String, String> parameters
) {
init();
RetryingOAuth retryingOAuth = new RetryingOAuth("{{tokenUrl}}", clientId, GrantType.valueOf("{{flow}}"), clientSecret, parameters);
authentications.put(
"{{name}}",
retryingOAuth
);
httpClient.interceptors().add(retryingOAuth);
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
{{/isOAuth}}{{/authMethods}}
private void init() {
httpClient = new OkHttpClient();
{{#useGzipFeature}}
Expand All @@ -92,13 +128,7 @@ public class ApiClient {
// Set default User-Agent.
setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}");

// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();{{#authMethods}}{{#isBasic}}
authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}}
authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}}
authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
authentications = new HashMap<String, Authentication>();
}

/**
Expand Down Expand Up @@ -502,6 +532,20 @@ public class ApiClient {
return this;
}

/**
* Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one)
* @return Token request builder
*/
public TokenRequestBuilder getTokenEndPoint() {
for (Authentication apiAuth : authentications.values()) {
if (apiAuth instanceof RetryingOAuth) {
RetryingOAuth retryingOAuth = (RetryingOAuth) apiAuth;
return retryingOAuth.getTokenRequestBuilder();
}
}
return null;
}

/**
* Format the given parameter object into string.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package {{invokerPackage}}.auth;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import org.apache.oltu.oauth2.client.HttpClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthClientResponse;
import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;

public class OAuthOkHttpClient implements HttpClient {
private OkHttpClient client;
public OAuthOkHttpClient() {
this.client = new OkHttpClient();
}

public OAuthOkHttpClient(OkHttpClient client) {
this.client = client;
}

@Override
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
MediaType mediaType = MediaType.parse("application/json");
Request.Builder requestBuilder = new Request.Builder().url(request.getLocationUri());
if(headers != null) {
for (Entry<String, String> entry : headers.entrySet()) {
if (entry.getKey().equalsIgnoreCase("Content-Type")) {
mediaType = MediaType.parse(entry.getValue());
} else {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}
}

RequestBody body = request.getBody() != null ? RequestBody.create(mediaType, request.getBody()) : null;
requestBuilder.method(requestMethod, body);

try {
Response response = client.newCall(requestBuilder.build()).execute();
return OAuthClientResponseFactory.createCustomResponse(
response.body().string(),
response.body().contentType().toString(),
response.code(),
responseClass);
} catch (IOException e) {
throw new OAuthSystemException(e);
}
}

@Override
public void shutdown() {
// Nothing to do here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package {{invokerPackage}}.auth;

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Map;

public class RetryingOAuth extends OAuth implements Interceptor {
private OAuthClient oAuthClient;
private TokenRequestBuilder tokenRequestBuilder;
public RetryingOAuth(OkHttpClient client, TokenRequestBuilder tokenRequestBuilder) {
this.oAuthClient = new OAuthClient(new OAuthOkHttpClient(client));
this.tokenRequestBuilder = tokenRequestBuilder;
}

public RetryingOAuth(TokenRequestBuilder tokenRequestBuilder) {
this(new OkHttpClient(), tokenRequestBuilder);
}

public RetryingOAuth(
String tokenUrl,
String clientId,
GrantType grantType,
String clientSecret,
Map<String, String> parameters
) {
this(OAuthClientRequest.tokenLocation(tokenUrl)
.setClientId(clientId)
.setGrantType(grantType)
.setClientSecret(clientSecret));
if (parameters != null) {
for (String paramName : parameters.keySet()) {
tokenRequestBuilder.setParameter(paramName, parameters.get(paramName));
}
}
}

@Override
public Response intercept(Chain chain) throws IOException {
return retryingIntercept(chain, true);
}

private Response retryingIntercept(Chain chain, boolean updateTokenAndRetryOnAuthorizationFailure) throws IOException {
Request request = chain.request();
// If the request already has an authorization (e.g. Basic auth), proceed with the request as is
if (request.header("Authorization") != null) {
return chain.proceed(request);
}

// Get the token if it has not yet been acquired
if (getAccessToken() == null) {
updateAccessToken(null);
}

OAuthClientRequest oAuthRequest;
if (getAccessToken() != null) {
// Build the request
Request.Builder requestBuilder = request.newBuilder();
String requestAccessToken = getAccessToken();
try {
oAuthRequest =
new OAuthBearerClientRequest(request.urlString()).
setAccessToken(requestAccessToken).
buildHeaderMessage();
} catch (OAuthSystemException e) {
throw new IOException(e);
}

Map<String, String> headers = oAuthRequest.getHeaders();
for (String headerName : headers.keySet()) {
requestBuilder.addHeader(headerName, headers.get(headerName));
}
requestBuilder.url(oAuthRequest.getLocationUri());

// Execute the request
Response response = chain.proceed(requestBuilder.build());

// 401/403 response codes most likely indicate an expired access token, unless it happens two times in a row
if (
response != null &&
( response.code() == HttpURLConnection.HTTP_UNAUTHORIZED ||
response.code() == HttpURLConnection.HTTP_FORBIDDEN ) &&
updateTokenAndRetryOnAuthorizationFailure
) {
try {
if (updateAccessToken(requestAccessToken)) {
response.body().close();
return retryingIntercept(chain, false);
}
} catch (Exception e) {
response.body().close();
throw e;
}
}
return response;
}
else {
return chain.proceed(chain.request());
}
}

/*
* Returns true if the access token has been updated
*/
public synchronized boolean updateAccessToken(String requestAccessToken) throws IOException {
if (getAccessToken() == null || getAccessToken().equals(requestAccessToken)) {
try {
OAuthJSONAccessTokenResponse accessTokenResponse =
oAuthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken());
return !getAccessToken().equals(requestAccessToken);
}
} catch (OAuthSystemException | OAuthProblemException e) {
throw new IOException(e);
}
}

return false;
}

public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}

public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ dependencies {
compile 'com.squareup.okhttp:logging-interceptor:2.7.5'
compile 'com.google.code.gson:gson:2.8.1'
compile 'io.gsonfire:gson-fire:1.8.0'
compile group: 'org.apache.oltu.oauth2', name: 'org.apache.oltu.oauth2.client', version: '1.0.1'
{{#joda}}
compile 'joda-time:joda-time:2.9.9'
{{/joda}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@
<artifactId>gson-fire</artifactId>
<version>${gson-fire-version}</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.client</artifactId>
<version>1.0.1</version>
</dependency>
{{#joda}}
<dependency>
<groupId>joda-time</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public void testGeneratePing() throws Exception {
generator.opts(clientOptInput).generate();

Map<String, String> generatedFiles = generator.getFiles();
Assert.assertEquals(generatedFiles.size(), 35);
Assert.assertEquals(generatedFiles.size(), 37);
ensureContainsFile(generatedFiles, output, ".gitignore");
ensureContainsFile(generatedFiles, output, ".openapi-generator-ignore");
ensureContainsFile(generatedFiles, output, ".openapi-generator/VERSION");
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.3-SNAPSHOT
3.3.0-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ dependencies {
compile 'com.squareup.okhttp:logging-interceptor:2.7.5'
compile 'com.google.code.gson:gson:2.8.1'
compile 'io.gsonfire:gson-fire:1.8.0'
compile group: 'org.apache.oltu.oauth2', name: 'org.apache.oltu.oauth2.client', version: '1.0.1'
compile 'org.threeten:threetenbp:1.3.5'
testCompile 'junit:junit:4.12'
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@
<groupId>io.gsonfire</groupId>
<artifactId>gson-fire</artifactId>
<version>${gson-fire-version}</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.client</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.threeten</groupId>
Expand Down
Loading

0 comments on commit 4819e84

Please sign in to comment.