diff --git a/src/main/java/com/botdarr/api/DownloadsStrategy.java b/src/main/java/com/botdarr/api/DownloadsStrategy.java index 097c4cb..c49586f 100644 --- a/src/main/java/com/botdarr/api/DownloadsStrategy.java +++ b/src/main/java/com/botdarr/api/DownloadsStrategy.java @@ -7,6 +7,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.Collections; @@ -38,6 +41,14 @@ public List downloads() { public List getContentDownloads() { return ConnectionHelper.makeGetRequest(this.api, this.url, new ConnectionHelper.SimpleMessageEmbedResponseHandler(chatClientResponseBuilder) { + + @Override + public List onConnectException(HttpHostConnectException e) { + String message = "Error trying to connect to " + DownloadsStrategy.this.api.getApiUrl(DownloadsStrategy.this.url); + LOGGER.error(message); + return Collections.emptyList(); + } + @Override public List onSuccess(String response) { return parseContent(response); @@ -69,4 +80,5 @@ public List parseContent(String response) { private final ChatClientResponseBuilder chatClientResponseBuilder; private final int MAX_DOWNLOADS_TO_SHOW = new ApiRequests().getMaxDownloadsToShow(); private final ContentType contentType; + private static Logger LOGGER = LogManager.getLogger(); } diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java b/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java index 124b312..8f1d385 100644 --- a/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java +++ b/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java @@ -12,6 +12,7 @@ import com.botdarr.connections.ConnectionHelper; import com.google.gson.Gson; import org.apache.http.client.methods.*; +import org.apache.http.conn.HttpHostConnectException; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.logging.log4j.LogManager; @@ -291,6 +292,11 @@ public T onException(Exception e) { LOGGER.error("Error trying to make matrix request", e); return null; } + + @Override + public T onConnectException(HttpHostConnectException e) { + return onException(e); + } } protected static class Constants { diff --git a/src/main/java/com/botdarr/connections/ConnectionHelper.java b/src/main/java/com/botdarr/connections/ConnectionHelper.java index 8560c3b..d557d92 100644 --- a/src/main/java/com/botdarr/connections/ConnectionHelper.java +++ b/src/main/java/com/botdarr/connections/ConnectionHelper.java @@ -6,6 +6,7 @@ import com.botdarr.clients.ChatClientResponseBuilder; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.*; +import org.apache.http.conn.HttpHostConnectException; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; @@ -59,17 +60,25 @@ public static T makeRequest(RequestHandler requestHandler, ResponseHandler> { + @Override + public List onConnectException(HttpHostConnectException e) { + return onException(e); + } + public SimpleMessageEmbedResponseHandler(ChatClientResponseBuilder chatClientResponseBuilder) { this.chatClientResponseBuilder = chatClientResponseBuilder; } @@ -97,6 +106,11 @@ public T onFailure(int statusCode, String reason) { public T onException(Exception e) { return null; } + + @Override + public T onConnectException(HttpHostConnectException e) { + return null; + } } public interface RequestHandler { @@ -112,6 +126,8 @@ public interface ResponseHandler { T onFailure(int statusCode, String reason); T onException(Exception e); + + T onConnectException(HttpHostConnectException e); } private static final Logger LOGGER = LogManager.getLogger(); diff --git a/src/main/java/com/botdarr/utilities/Log4jSensitiveDataPattern.java b/src/main/java/com/botdarr/utilities/Log4jSensitiveDataPattern.java new file mode 100644 index 0000000..567101c --- /dev/null +++ b/src/main/java/com/botdarr/utilities/Log4jSensitiveDataPattern.java @@ -0,0 +1,42 @@ +package com.botdarr.utilities; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Plugin(name="LogMaskingConverter", category = "Converter") +@ConverterKeys({"scrubbedMsg"}) +public class Log4jSensitiveDataPattern extends LogEventPatternConverter { + private static final Pattern API_KEY_PATTERN = Pattern.compile("apikey=([0-9a-zA-z]+)"); + + protected Log4jSensitiveDataPattern(String name, String style) { + super(name, style); + } + public static Log4jSensitiveDataPattern newInstance(String[] options) { + return new Log4jSensitiveDataPattern("scrubbedMsg", Thread.currentThread().getName()); + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + String message = event.getMessage().getFormattedMessage(); + String maskedMessage = message; + try { + StringBuffer modifiedBuffer = new StringBuffer(); + Matcher matcher = API_KEY_PATTERN.matcher(message); + while(matcher.find()) { + matcher.appendReplacement(modifiedBuffer, "apikey=****"); + } + if (modifiedBuffer.length() > 0) { + maskedMessage = modifiedBuffer.toString(); + } + } catch (Exception e) { + System.out.println("Error trying to mask message, message = " + e.getMessage()); + maskedMessage = message; + } + toAppendTo.append(maskedMessage); + } +} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 8691a33..7f62795 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -3,13 +3,13 @@ - + logs/bot.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -21,7 +21,7 @@ logs/audit.log logs/%d{yyyy-MM-dd-hh-mm}.audit.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -33,7 +33,7 @@ logs/radarr.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -45,7 +45,7 @@ logs/sonarr.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -57,7 +57,7 @@ logs/network.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -69,7 +69,7 @@ logs/slack.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -81,7 +81,7 @@ logs/discord.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -93,7 +93,7 @@ logs/matrix.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n @@ -105,7 +105,7 @@ logs/telegram.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip - %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %scrubbedMsg%n diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt index 63f23b1..40dc926 100644 --- a/src/main/resources/version.txt +++ b/src/main/resources/version.txt @@ -1 +1 @@ -5.1.14 \ No newline at end of file +5.1.15 \ No newline at end of file diff --git a/src/test/java/com/botdarr/api/DownloadsStrategyTests.java b/src/test/java/com/botdarr/api/DownloadsStrategyTests.java index dbb26e5..aa95f7a 100644 --- a/src/test/java/com/botdarr/api/DownloadsStrategyTests.java +++ b/src/test/java/com/botdarr/api/DownloadsStrategyTests.java @@ -3,11 +3,18 @@ import com.botdarr.TestResponse; import com.botdarr.clients.ChatClientResponse; import com.botdarr.clients.ChatClientResponseBuilder; +import com.google.gson.Gson; import com.google.gson.JsonElement; import mockit.*; +import org.apache.logging.log4j.Logger; import org.junit.Assert; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.mockserver.junit.MockServerRule; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; +import org.mockserver.model.MediaType; import java.util.ArrayList; import java.util.List; @@ -61,12 +68,37 @@ public void parseContent_tooManyDownloads_infoMessageIncluded() { Assert.assertEquals(expectedInfoResponse, responses.get(0)); } + @Test + public void getContentDownloads_endpointUnavailable_emptyResponse() { + DownloadsStrategy downloadsStrategy = new DownloadsStrategy(api, "", chatClientResponseBuilder, ContentType.MOVIE) { + @Override + public ChatClientResponse getResponse(JsonElement rawElement) { + return null; + } + }; + Deencapsulation.setField(downloadsStrategy, "LOGGER", logger); + new Expectations(downloadsStrategy) {{ + api.getApiUrl(""); result = "http://localhost"; times = 2; + api.getApiToken(); result = "token1"; times = 1; + logger.error("Error trying to connect to http://localhost"); times = 1; + }}; + + //confirm even tho we failed to connect we don't return error notifications that could flood chat clients + Assert.assertTrue(downloadsStrategy.getContentDownloads().isEmpty()); + } + private DownloadsStrategy getMockDownloadsStrategy(int maxDownloadsToShow) { DownloadsStrategy mockDownloadsStrategy = new MockDownloadsStrategy(api, "", chatClientResponseBuilder, ContentType.MOVIE); Deencapsulation.setField(mockDownloadsStrategy, "MAX_DOWNLOADS_TO_SHOW", maxDownloadsToShow); return mockDownloadsStrategy; } + @Rule + public MockServerRule mockServerRule = new MockServerRule(this); + + @Mocked + private Logger logger; + @Injectable private Api api; diff --git a/src/test/java/com/botdarr/api/radarr/RadarrApiTests.java b/src/test/java/com/botdarr/api/radarr/RadarrApiTests.java index 9f7f77e..bd5eb4d 100644 --- a/src/test/java/com/botdarr/api/radarr/RadarrApiTests.java +++ b/src/test/java/com/botdarr/api/radarr/RadarrApiTests.java @@ -3,7 +3,6 @@ import com.botdarr.Config; import com.botdarr.TestResponse; import com.botdarr.TestResponseBuilder; -import com.botdarr.api.radarr.*; import com.botdarr.commands.CommandResponse; import com.google.gson.Gson; import mockit.Deencapsulation; diff --git a/src/test/java/com/botdarr/utilities/Log4jSensitiveDataPatternTests.java b/src/test/java/com/botdarr/utilities/Log4jSensitiveDataPatternTests.java new file mode 100644 index 0000000..5c5c530 --- /dev/null +++ b/src/test/java/com/botdarr/utilities/Log4jSensitiveDataPatternTests.java @@ -0,0 +1,39 @@ +package com.botdarr.utilities; + +import mockit.Expectations; +import mockit.Mocked; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.junit.Assert; +import org.junit.Test; + +public class Log4jSensitiveDataPatternTests { + @Test + public void format_noApiKeyInString_noMaskedText() { + String input = "http://localhost"; + LogEventPatternConverter patternConverter = new Log4jSensitiveDataPattern("test", "style"); + StringBuilder output = new StringBuilder(); + new Expectations() {{ + mockedEvent.getMessage().getFormattedMessage(); result = input; + }}; + patternConverter.format(mockedEvent, output); + //input should remain unchanged + Assert.assertEquals(input, output.toString()); + } + + @Test + public void format_apiKeyInString_noMaskedText() { + String input = "http://localhost?apikey=fdskjkjfd"; + LogEventPatternConverter patternConverter = new Log4jSensitiveDataPattern("test", "style"); + StringBuilder output = new StringBuilder(); + new Expectations() {{ + mockedEvent.getMessage().getFormattedMessage(); result = input; + }}; + patternConverter.format(mockedEvent, output); + //input should remain unchanged + Assert.assertEquals("http://localhost?apikey=****", output.toString()); + } + + @Mocked + private Log4jLogEvent mockedEvent; +}