Bump ancient OkHTTP & Retrofit in Hooks (FINERACT-1252) (#1486)

This commit is contained in:
Michael Vorburger ⛑️
2020-11-03 14:16:01 +05:30
committed by GitHub
parent 26cd903301
commit 7b34e83843
11 changed files with 111 additions and 129 deletions
+1 -4
View File
@@ -21,7 +21,7 @@ buildscript {
ext {
jacocoVersion = '0.8.6'
retrofitVersion = '2.9.0'
okhttpVersion = '4.7.2'
okhttpVersion = '4.9.0'
oltuVersion = '1.0.1'
fineractJavaProjects = subprojects.findAll{
[
@@ -107,9 +107,6 @@ allprojects {
dependency 'org.springframework.security.oauth:spring-security-oauth2:2.5.0.RELEASE'
dependency 'org.apache.openjpa:openjpa:3.1.2' // when upgrading, also change OpenJPA version repeated above in buildscript!
dependency 'com.squareup.retrofit:retrofit:1.9.0'
dependency 'com.squareup.okhttp:okhttp:2.7.5'
dependency 'com.squareup.okhttp:okhttp-urlconnection:2.7.5'
dependency 'com.google.guava:guava:30.0-jre'
dependency 'com.google.code.gson:gson:2.8.6'
dependency 'org.apache.commons:commons-email:1.5'
@@ -453,7 +453,7 @@ public final class FineractClient {
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
SSLContext sslContext = SSLContext.getInstance("SSL"); // TODO "SSL" or "TLS" as in hooks.processor.ProcessorHelper?
sslContext.init(null, new TrustManager[] { insecureX509TrustManager }, new SecureRandom());
SSLSocketFactory insecureSslSocketFactory = sslContext.getSocketFactory();
+4 -3
View File
@@ -45,12 +45,13 @@ dependencies {
'com.google.guava:guava',
'com.google.code.gson:gson',
'com.sun.jersey:jersey-core',
'com.squareup.retrofit:retrofit',
'jakarta.jms:jakarta.jms-api',
'jakarta.management.j2ee:jakarta.management.j2ee-api',
'jakarta.validation:jakarta.validation-api',
'com.squareup.okhttp:okhttp',
'com.squareup.okhttp:okhttp-urlconnection',
'com.squareup.retrofit2:retrofit',
'com.squareup.okhttp3:okhttp',
'com.squareup.okhttp3:okhttp-urlconnection',
'org.apache.commons:commons-email',
'org.apache.commons:commons-lang3',
@@ -29,12 +29,16 @@ import org.apache.fineract.infrastructure.hooks.processor.HookProcessorProvider;
import org.apache.fineract.infrastructure.hooks.service.HookReadPlatformService;
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class FineractHookListener implements HookListener {
private static final Logger LOG = LoggerFactory.getLogger(FineractHookListener.class);
private final HookProcessorProvider hookProcessorProvider;
private final HookReadPlatformService hookReadPlatformService;
private final TenantDetailsService tenantDetailsService;
@@ -49,7 +53,6 @@ public class FineractHookListener implements HookListener {
@Override
public void onApplicationEvent(final HookEvent event) {
final String tenantIdentifier = event.getTenantIdentifier();
final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
ThreadLocalContextUtil.setTenant(tenant);
@@ -67,8 +70,12 @@ public class FineractHookListener implements HookListener {
for (final Hook hook : hooks) {
final HookProcessor processor = this.hookProcessorProvider.getProcessor(hook);
processor.process(hook, appUser, payload, entityName, actionName, tenantIdentifier, authToken);
try {
processor.process(hook, appUser, payload, entityName, actionName, tenantIdentifier, authToken);
} catch (Throwable e) {
LOG.error("Hook {} failed in HookProcessor {} for tenantIdentifier/user {}/{}, entityName: {}, actionName: {}, payload {} ",
hook.getId(), processor.getClass().getSimpleName(), tenantIdentifier, appUser, entityName, actionName, payload, e);
}
}
}
}
@@ -24,6 +24,6 @@ import org.apache.fineract.useradministration.domain.AppUser;
public interface HookProcessor {
void process(Hook hook, AppUser appUser, String payload, String entityName, String actionName, String tenantIdentifier,
String authToken);
String authToken) throws Exception;
}
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.infrastructure.hooks.processor;
import com.squareup.okhttp.OkHttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -26,23 +25,39 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import retrofit.Callback;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.OkClient;
import retrofit.client.Response;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
@Service
public final class ProcessorHelper {
// Nota bene: Similar code to insecure HTTPS is also in Fineract Client's
// org.apache.fineract.client.util.FineractClient.Builder.insecure()
private static final Logger LOG = LoggerFactory.getLogger(ProcessorHelper.class);
@SuppressWarnings("unused")
private static final X509TrustManager insecureX509TrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
};
/**
* Configure HTTP client to be "insecure", as in skipping host SSL certificate verification. While this can be
* useful during development e.g. when using self-signed certificates, it should never be enabled in production (due
@@ -60,43 +75,23 @@ public final class ProcessorHelper {
}
private OkHttpClient createClient() {
final OkHttpClient client = new OkHttpClient();
var okBuilder = new OkHttpClient.Builder();
if (insecureHttpClient) {
configureInsecureClient(client);
configureInsecureClient(okBuilder);
}
return client;
return okBuilder.build();
}
private void configureInsecureClient(final OkHttpClient client) {
client.setSslSocketFactory(insecureSSLContext.getSocketFactory());
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(final String hostname, final SSLSession session) {
return true;
}
};
client.setHostnameVerifier(hostnameVerifier);
private void configureInsecureClient(final OkHttpClient.Builder okBuilder) {
okBuilder.sslSocketFactory(insecureSSLContext.getSocketFactory(), insecureX509TrustManager);
HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true;
okBuilder.hostnameVerifier(insecureHostnameVerifier);
}
private SSLContext createInsecureSSLContext() throws NoSuchAlgorithmException, KeyManagementException {
final TrustManager[] certs = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
} };
SSLContext insecureSSLContext = SSLContext.getInstance("TLS");
insecureSSLContext.init(null, certs, new SecureRandom());
SSLContext insecureSSLContext = SSLContext.getInstance("TLS"); // TODO "TLS" or "SSL" as in
// FineractClient.Builder?
insecureSSLContext.init(null, new TrustManager[] { insecureX509TrustManager }, new SecureRandom());
return insecureSSLContext;
}
@@ -105,20 +100,23 @@ public final class ProcessorHelper {
return new Callback() {
@Override
public void success(final Object o, final Response response) {
LOG.info("URL: {} - Status: {}", url, response.getStatus());
public void onResponse(@SuppressWarnings("unused") Call call, retrofit2.Response response) {
LOG.info("URL: {} - Status: {}", url, response.code());
}
@Override
public void failure(final RetrofitError retrofitError) {
LOG.error("URL: {} - RetrofitError occured", url, retrofitError);
public void onFailure(@SuppressWarnings("unused") Call call, Throwable t) {
LOG.error("URL: {} - Retrofit failure occured", url, t);
}
};
}
public WebHookService createWebHookService(final String url) {
final OkHttpClient client = createClient();
final RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(url).setClient(new OkClient(client)).build();
return restAdapter.create(WebHookService.class);
final Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
retrofitBuilder.baseUrl(url);
retrofitBuilder.client(client);
final Retrofit retrofit = retrofitBuilder.build();
return retrofit.create(WebHookService.class);
}
}
@@ -34,18 +34,16 @@ import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.template.service.TemplateMergeService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import retrofit.Callback;
import retrofit2.Callback;
@Service
public class TwilioHookProcessor implements HookProcessor {
private static final Logger LOG = LoggerFactory.getLogger(TwilioHookProcessor.class);
private final HookConfigurationRepository hookConfigurationRepository;
private final TemplateMergeService templateMergeService;
private final ClientRepositoryWrapper clientRepositoryWrapper;
@@ -63,7 +61,7 @@ public class TwilioHookProcessor implements HookProcessor {
@Override
public void process(final Hook hook, @SuppressWarnings("unused") final AppUser appUser, final String payload, final String entityName,
final String actionName, final String tenantIdentifier, final String authToken) {
final String actionName, final String tenantIdentifier, final String authToken) throws IOException {
final SmsProviderData smsProviderData = new SmsProviderData(hook.getHookConfig());
@@ -72,7 +70,7 @@ public class TwilioHookProcessor implements HookProcessor {
@SuppressWarnings("unchecked")
private void sendRequest(final SmsProviderData smsProviderData, final String payload, String entityName, String actionName,
final String tenantIdentifier, final String authToken, final Hook hook) {
final String tenantIdentifier, final String authToken, final Hook hook) throws IOException {
final WebHookService service = processorHelper.createWebHookService(smsProviderData.getUrl());
@@ -85,7 +83,7 @@ public class TwilioHookProcessor implements HookProcessor {
smsProviderData.setEndpoint(System.getProperty("baseUrl"));
smsProviderData.setTenantId(tenantIdentifier);
smsProviderData.setMifosToken(authToken);
apiKey = service.sendSmsBridgeConfigRequest(smsProviderData);
apiKey = service.sendSmsBridgeConfigRequest(smsProviderData).execute().body();
final HookConfiguration apiKeyEntry = HookConfiguration.createNew(hook, "string", apiKeyName, apiKey);
this.hookConfigurationRepository.save(apiKeyEntry);
}
@@ -102,34 +100,30 @@ public class TwilioHookProcessor implements HookProcessor {
} else {
json = JsonParser.parseString(payload).getAsJsonObject();
}
service.sendSmsBridgeRequest(entityName, actionName, tenantIdentifier, apiKey, json, callback);
service.sendSmsBridgeRequest(entityName, actionName, tenantIdentifier, apiKey, json).enqueue(callback);
}
}
private JsonObject processUgdTemplate(final String payload, final Hook hook, final String authToken) {
private JsonObject processUgdTemplate(final String payload, final Hook hook, final String authToken)
throws JsonParseException, JsonMappingException, IOException {
JsonObject json = null;
try {
@SuppressWarnings("unchecked")
final HashMap<String, Object> map = new ObjectMapper().readValue(payload, HashMap.class);
map.put("BASE_URI", System.getProperty("baseUrl"));
if (map.containsKey("clientId")) {
final Long clientId = Long.valueOf(Integer.toString((int) map.get("clientId")));
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
final String mobileNo = client.mobileNo();
if (mobileNo != null && !mobileNo.isEmpty()) {
this.templateMergeService.setAuthToken(authToken);
final String compiledMessage = this.templateMergeService.compile(hook.getUgdTemplate(), map).replace("<p>", "")
.replace("</p>", "");
final Map<String, String> jsonMap = new HashMap<>();
jsonMap.put("mobileNo", mobileNo);
jsonMap.put("message", compiledMessage);
final String jsonString = new Gson().toJson(jsonMap);
json = JsonParser.parseString(jsonString).getAsJsonObject();
}
@SuppressWarnings("unchecked")
final HashMap<String, Object> map = new ObjectMapper().readValue(payload, HashMap.class);
map.put("BASE_URI", System.getProperty("baseUrl"));
if (map.containsKey("clientId")) {
final Long clientId = Long.valueOf(Integer.toString((int) map.get("clientId")));
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
final String mobileNo = client.mobileNo();
if (mobileNo != null && !mobileNo.isEmpty()) {
this.templateMergeService.setAuthToken(authToken);
final String compiledMessage = this.templateMergeService.compile(hook.getUgdTemplate(), map).replace("<p>", "")
.replace("</p>", "");
final Map<String, String> jsonMap = new HashMap<>();
jsonMap.put("mobileNo", mobileNo);
jsonMap.put("message", compiledMessage);
final String jsonString = new Gson().toJson(jsonMap);
json = JsonParser.parseString(jsonString).getAsJsonObject();
}
} catch (IOException e) {
LOG.error("Problem occurred in processUgdTemplate function", e);
}
return json;
}
@@ -32,7 +32,7 @@ import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import retrofit.Callback;
import retrofit2.Callback;
@Service
public class WebHookProcessor implements HookProcessor {
@@ -78,11 +78,11 @@ public class WebHookProcessor implements HookProcessor {
if (contentType.equalsIgnoreCase("json") || contentType.contains("json")) {
final JsonObject json = JsonParser.parseString(payload).getAsJsonObject();
service.sendJsonRequest(entityName, actionName, tenantIdentifier, fineractEndpointUrl, json, callback);
service.sendJsonRequest(entityName, actionName, tenantIdentifier, fineractEndpointUrl, json).enqueue(callback);
} else {
Map<String, String> map = new HashMap<>();
map = new Gson().fromJson(payload, map.getClass());
service.sendFormRequest(entityName, actionName, tenantIdentifier, fineractEndpointUrl, map, callback);
service.sendFormRequest(entityName, actionName, tenantIdentifier, fineractEndpointUrl, map).enqueue(callback);
}
}
}
@@ -21,14 +21,13 @@ package org.apache.fineract.infrastructure.hooks.processor;
import com.google.gson.JsonObject;
import java.util.Map;
import org.apache.fineract.infrastructure.hooks.processor.data.SmsProviderData;
import retrofit.Callback;
import retrofit.client.Response;
import retrofit.http.Body;
import retrofit.http.FieldMap;
import retrofit.http.FormUrlEncoded;
import retrofit.http.GET;
import retrofit.http.Header;
import retrofit.http.POST;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
public interface WebHookService {
@@ -40,27 +39,25 @@ public interface WebHookService {
// Ping
@GET("/")
Response sendEmptyRequest();
Call<Void> sendEmptyRequest();
// Template - Web
@POST("/")
void sendJsonRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
@Header(TENANT_HEADER) String tenantHeader, @Header(ENDPOINT_HEADER) String endpointHeader, @Body JsonObject result,
Callback<Response> callBack);
Call<Void> sendJsonRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
@Header(TENANT_HEADER) String tenantHeader, @Header(ENDPOINT_HEADER) String endpointHeader, @Body JsonObject result);
@FormUrlEncoded
@POST("/")
void sendFormRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
Call<Void> sendFormRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
@Header(TENANT_HEADER) String tenantHeader, @Header(ENDPOINT_HEADER) String endpointHeader,
@FieldMap Map<String, String> params, Callback<Response> callBack);
@FieldMap Map<String, String> params);
// Template - SMS Bridge
@POST("/")
void sendSmsBridgeRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
@Header(TENANT_HEADER) String tenantHeader, @Header(API_KEY_HEADER) String apiKeyHeader, @Body JsonObject result,
Callback<Response> callBack);
Call<Void> sendSmsBridgeRequest(@Header(ENTITY_HEADER) String entityHeader, @Header(ACTION_HEADER) String actionHeader,
@Header(TENANT_HEADER) String tenantHeader, @Header(API_KEY_HEADER) String apiKeyHeader, @Body JsonObject result);
@POST("/configuration")
String sendSmsBridgeConfigRequest(@Body SmsProviderData config);
Call<String> sendSmsBridgeConfigRequest(@Body SmsProviderData config);
}
@@ -30,6 +30,7 @@ import static org.apache.fineract.infrastructure.hooks.api.HookApiConstants.webT
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -67,7 +68,6 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import retrofit.RetrofitError;
@Service
public class HookWritePlatformServiceJpaRepositoryImpl implements HookWritePlatformService {
@@ -282,15 +282,10 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements HookWritePlatf
if (conf.getFieldName().equals(payloadURLName)) {
try {
final WebHookService service = processorHelper.createWebHookService(fieldValue);
service.sendEmptyRequest();
} catch (RetrofitError re) {
// Swallow error if it's because of method not supported or
// if url throws 404 - required for integration test,
// url generated on 1st POST request
if (re.getResponse() == null) {
String errorMessage = "url.invalid";
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
}
service.sendEmptyRequest().execute();
} catch (IOException re) {
String errorMessage = "url.invalid";
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
}
}
}
@@ -61,21 +61,16 @@ public class HookIntegrationTest {
@Test
public void shouldSendOfficeCreationNotification() {
// Subject to https://echo-webhook.herokuapp.com being up
// See
// http://www.jamesward.com/2014/06/11/testing-webhooks-was-a-pain-so-i-fixed-the-glitch
// See http://www.jamesward.com/2014/06/11/testing-webhooks-was-a-pain-so-i-fixed-the-glitch
final String uniqueId = UUID.randomUUID().toString();
final String payloadURL = "http://echo-webhook.herokuapp.com:80/" + uniqueId + "?";
final String payloadURL = "http://echo-webhook.herokuapp.com:80/" + uniqueId + "/";
final Integer hookId = this.hookHelper.createHook(payloadURL);
Assertions.assertNotNull(hookId);
final Integer createdOfficeID = this.officeHelper.createOffice("01 January 2012");
Assertions.assertNotNull(createdOfficeID);
try {
/**
* sleep for a three seconds after each failure to increase the likelihood of the previous request for
* creating office completing
**/
// sleep for a three seconds after each failure to increase the likelihood of the previous request for
// creating office completing
for (int i = 0; i < 6; i++) {
try {
final String json = RestAssured.get(payloadURL.replace("?", "")).asString();
@@ -98,12 +93,11 @@ public class HookIntegrationTest {
} finally {
this.hookHelper.deleteHook(hookId.longValue());
}
}
@Test
public void createUpdateAndDeleteHook() {
final String payloadURL = "http://echo-webhook.herokuapp.com:80/Z7RXoCBdLSFMDrpn?";
final String payloadURL = "http://echo-webhook.herokuapp.com:80/Z7RXoCBdLSFMDrpn/";
final String updateURL = "http://localhost";
Long hookId = this.hookHelper.createHook(payloadURL).longValue();
@@ -116,6 +110,5 @@ public class HookIntegrationTest {
this.hookHelper.deleteHook(hookId);
this.hookHelper.verifyDeleteHook(hookId);
LOG.info("---------------------SUCCESSFULLY DELETED AND VERIFIED HOOK------------------------- {}", hookId);
}
}