package org.gitnex.tea4j.v2; import com.google.gson.Gson; import com.google.gson.JsonParseException; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.text.DateFormat; import java.util.LinkedHashMap; import java.util.Map; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.ResponseBody; import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; import org.gitnex.tea4j.v2.auth.ApiKeyAuth; import org.gitnex.tea4j.v2.auth.HttpBasicAuth; import org.gitnex.tea4j.v2.auth.OAuth; import org.gitnex.tea4j.v2.auth.OAuth.AccessTokenListener; import retrofit2.Converter; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.scalars.ScalarsConverterFactory; public class ApiClient { private Map apiAuthorizations; private OkHttpClient.Builder okBuilder; private Retrofit.Builder adapterBuilder; private JSON json; public ApiClient() { apiAuthorizations = new LinkedHashMap(); createDefaultAdapter(); } public ApiClient(String[] authNames) { this(); for (String authName : authNames) { Interceptor auth = null; if ("AccessToken".equals(authName)) { auth = new ApiKeyAuth("query", "access_token"); } else if ("AuthorizationHeaderToken".equals(authName)) { auth = new ApiKeyAuth("header", "Authorization"); } else if ("BasicAuth".equals(authName)) { auth = new HttpBasicAuth(); } else if ("SudoHeader".equals(authName)) { auth = new ApiKeyAuth("header", "Sudo"); } else if ("SudoParam".equals(authName)) { auth = new ApiKeyAuth("query", "sudo"); } else if ("TOTPHeader".equals(authName)) { auth = new ApiKeyAuth("header", "X-GITEA-OTP"); } else if ("Token".equals(authName)) { auth = new ApiKeyAuth("query", "token"); } else { throw new RuntimeException( "auth name \"" + authName + "\" not found in available auth names"); } addAuthorization(authName, auth); } } /** * Basic constructor for single auth name * * @param authName Authentication name */ public ApiClient(String authName) { this(new String[] {authName}); } /** * Helper constructor for single api key * * @param authName Authentication name * @param apiKey API key */ public ApiClient(String authName, String apiKey) { this(authName); this.setApiKey(apiKey); } /** * Helper constructor for single basic auth or password oauth2 * * @param authName Authentication name * @param username Username * @param password Password */ public ApiClient(String authName, String username, String password) { this(authName); this.setCredentials(username, password); } /** * Helper constructor for single password oauth2 * * @param authName Authentication name * @param clientId Client ID * @param secret Client Secret * @param username Username * @param password Password */ public ApiClient( String authName, String clientId, String secret, String username, String password) { this(authName); this.getTokenEndPoint() .setClientId(clientId) .setClientSecret(secret) .setUsername(username) .setPassword(password); } public void createDefaultAdapter() { json = new JSON(); okBuilder = new OkHttpClient.Builder(); String baseUrl = "http://{{AppSubUrl | JSEscape | Safe}}/api/v1"; if (!baseUrl.endsWith("/")) baseUrl = baseUrl + "/"; adapterBuilder = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonCustomConverterFactory.create(json.getGson())); } public S createService(Class serviceClass) { return adapterBuilder.client(okBuilder.build()).build().create(serviceClass); } public ApiClient setDateFormat(DateFormat dateFormat) { this.json.setDateFormat(dateFormat); return this; } public ApiClient setSqlDateFormat(DateFormat dateFormat) { this.json.setSqlDateFormat(dateFormat); return this; } /** * Helper method to configure the first api key found * * @param apiKey API key * @return ApiClient */ public ApiClient setApiKey(String apiKey) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof ApiKeyAuth) { ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization; keyAuth.setApiKey(apiKey); return this; } } return this; } /** * Helper method to configure the username/password for basic auth or password oauth * * @param username Username * @param password Password * @return ApiClient */ public ApiClient setCredentials(String username, String password) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof HttpBasicAuth) { HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization; basicAuth.setCredentials(username, password); return this; } if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; oauth.getTokenRequestBuilder().setUsername(username).setPassword(password); return this; } } 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 (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; return oauth.getTokenRequestBuilder(); } } return null; } /** * Helper method to configure authorization endpoint of the first oauth found in the * apiAuthorizations (there should be only one) * * @return Authentication request builder */ public AuthenticationRequestBuilder getAuthorizationEndPoint() { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; return oauth.getAuthenticationRequestBuilder(); } } return null; } /** * Helper method to pre-set the oauth access token of the first oauth found in the * apiAuthorizations (there should be only one) * * @param accessToken Access token * @return ApiClient */ public ApiClient setAccessToken(String accessToken) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; oauth.setAccessToken(accessToken); return this; } } return this; } /** * Helper method to configure the oauth accessCode/implicit flow parameters * * @param clientId Client ID * @param clientSecret Client secret * @param redirectURI Redirect URI * @return ApiClient */ public ApiClient configureAuthorizationFlow( String clientId, String clientSecret, String redirectURI) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; oauth .getTokenRequestBuilder() .setClientId(clientId) .setClientSecret(clientSecret) .setRedirectURI(redirectURI); oauth.getAuthenticationRequestBuilder().setClientId(clientId).setRedirectURI(redirectURI); return this; } } return this; } /** * Configures a listener which is notified when a new access token is received. * * @param accessTokenListener Access token listener * @return ApiClient */ public ApiClient registerAccessTokenListener(AccessTokenListener accessTokenListener) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { if (apiAuthorization instanceof OAuth) { OAuth oauth = (OAuth) apiAuthorization; oauth.registerAccessTokenListener(accessTokenListener); return this; } } return this; } /** * Adds an authorization to be used by the client * * @param authName Authentication name * @param authorization Authorization interceptor * @return ApiClient */ public ApiClient addAuthorization(String authName, Interceptor authorization) { if (apiAuthorizations.containsKey(authName)) { throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations"); } apiAuthorizations.put(authName, authorization); okBuilder.addInterceptor(authorization); return this; } public Map getApiAuthorizations() { return apiAuthorizations; } public ApiClient setApiAuthorizations(Map apiAuthorizations) { this.apiAuthorizations = apiAuthorizations; return this; } public Retrofit.Builder getAdapterBuilder() { return adapterBuilder; } public ApiClient setAdapterBuilder(Retrofit.Builder adapterBuilder) { this.adapterBuilder = adapterBuilder; return this; } public OkHttpClient.Builder getOkBuilder() { return okBuilder; } public void addAuthsToOkBuilder(OkHttpClient.Builder okBuilder) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { okBuilder.addInterceptor(apiAuthorization); } } /** * Clones the okBuilder given in parameter, adds the auth interceptors and uses it to configure * the Retrofit * * @param okClient An instance of OK HTTP client */ public void configureFromOkclient(OkHttpClient okClient) { this.okBuilder = okClient.newBuilder(); addAuthsToOkBuilder(this.okBuilder); } } /** * This wrapper is to take care of this case: when the deserialization fails due to * JsonParseException and the expected type is String, then just return the body string. */ class GsonResponseBodyConverterToString implements Converter { private final Gson gson; private final Type type; GsonResponseBodyConverterToString(Gson gson, Type type) { this.gson = gson; this.type = type; } @Override public T convert(ResponseBody value) throws IOException { String returned = value.string(); try { return gson.fromJson(returned, type); } catch (JsonParseException e) { return (T) returned; } } } class GsonCustomConverterFactory extends Converter.Factory { private final Gson gson; private final GsonConverterFactory gsonConverterFactory; public static GsonCustomConverterFactory create(Gson gson) { return new GsonCustomConverterFactory(gson); } private GsonCustomConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; this.gsonConverterFactory = GsonConverterFactory.create(gson); } @Override public Converter responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) { if (type.equals(String.class)) return new GsonResponseBodyConverterToString(gson, type); else return gsonConverterFactory.responseBodyConverter(type, annotations, retrofit); } @Override public Converter requestBodyConverter( Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return gsonConverterFactory.requestBodyConverter( type, parameterAnnotations, methodAnnotations, retrofit); } }