Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#592 Ported automatic access token retrieval from openapi-generator #593

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,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 @@ -2,6 +2,8 @@

package {{invokerPackage}};

import {{invokerPackage}}.auth.*;

import com.squareup.okhttp.*;
import com.squareup.okhttp.internal.http.HttpMethod;
import com.squareup.okhttp.logging.HttpLoggingInterceptor;
Expand Down Expand Up @@ -79,7 +81,6 @@ public class ApiClient {
*/
public ApiClient() {
httpClient = new OkHttpClient();

{{#useGzipFeature}}
// Enable gzip request compression
httpClient.interceptors().add(new GzipRequestInterceptor());
Expand All @@ -98,10 +99,21 @@ public class ApiClient {
authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}}
authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}}
authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}}
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}

{{#authMethods}}{{#is this 'oauth'}}
/*
* Constructor for ApiClient
*/
public ApiClient(final String clientId, final String clientSecret, final Map<String, String> parameters) {
this();

RetryingOAuth retryingOAuth = new RetryingOAuth("{{tokenUrl}}", clientId, OAuthFlow.{{flow}}, clientSecret, parameters);
httpClient.interceptors().add(retryingOAuth);
authentications.put("{{name}}", retryingOAuth);
}
{{/is}}{{/authMethods}}

/**
* Get base path
*
Expand Down Expand Up @@ -273,7 +285,7 @@ public class ApiClient {
* @return Map of authentication objects
*/
public Map<String, Authentication> getAuthentications() {
return authentications;
return Collections.unmodifiableMap(authentications);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{{#authMethods}}{{#is this 'oauth'}}
package {{invokerPackage}}.auth;

import com.squareup.okhttp.*;
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
}
}
{{/is}}{{/authMethods}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
{{#authMethods}}{{#is this 'oauth'}}
package {{invokerPackage}}.auth;

import {{invokerPackage}}.Pair;

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;
import java.util.List;

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,
OAuthFlow flow,
String clientSecret,
Map<String, String> parameters
) {
this(OAuthClientRequest.tokenLocation(tokenUrl)
.setClientId(clientId)
.setClientSecret(clientSecret));
setFlow(flow);
if (parameters != null) {
for (String paramName : parameters.keySet()) {
tokenRequestBuilder.setParameter(paramName, parameters.get(paramName));
}
}
}

public void setFlow(OAuthFlow flow) {
switch(flow) {
case accessCode:
tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
break;
case implicit:
tokenRequestBuilder.setGrantType(GrantType.IMPLICIT);
break;
case password:
tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
break;
case application:
tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
break;
default:
break;
}
}

@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.url().toString()).
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;
}

// Applying authorization to parameters is performed in the retryingIntercept method
@Override
public void applyToParams(List<Pair> queryParams, Map<String, String> headerParams) {
// No implementation necessary
}
}
{{/is}}{{/authMethods}}