From 3688808ea7508a4c82a64bce82f23d2152f30b65 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Wed, 11 Aug 2021 11:44:51 +0900 Subject: [PATCH 1/2] Improve the error caused in the #792 scenario --- .../impl/SocketModeClientJavaWSImpl.java | 14 +++++++++++++- .../java/com/slack/api/util/http/ProxyUrlUtil.java | 4 ++-- .../api/socket_mode/SocketModeClientTest.java | 9 +++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/slack-api-client/src/main/java/com/slack/api/socket_mode/impl/SocketModeClientJavaWSImpl.java b/slack-api-client/src/main/java/com/slack/api/socket_mode/impl/SocketModeClientJavaWSImpl.java index 22a7fe02d..2d6b3acac 100644 --- a/slack-api-client/src/main/java/com/slack/api/socket_mode/impl/SocketModeClientJavaWSImpl.java +++ b/slack-api-client/src/main/java/com/slack/api/socket_mode/impl/SocketModeClientJavaWSImpl.java @@ -219,17 +219,29 @@ public UnderlyingWebSocketSession(URI serverUri, Map httpHeaders // FIXME: the proxy settings here may not work SlackConfig slackConfig = smc.getSlack().getHttpClient().getConfig(); Map proxyHeaders = slackConfig.getProxyHeaders(); + if (proxyHeaders == null) { + proxyHeaders = new HashMap<>(); + } + String proxyUrl = slackConfig.getProxyUrl(); if (proxyUrl != null) { if (smc.getLogger().isDebugEnabled()) { smc.getLogger().debug("The SocketMode client's going to use an HTTP proxy: {}", proxyUrl); } ProxyUrlUtil.ProxyUrl parsedProxy = ProxyUrlUtil.parse(proxyUrl); + if (parsedProxy.getUsername() != null && parsedProxy.getPassword() != null) { + // see also: https://github.com/slackapi/java-slack-sdk/issues/792#issuecomment-895961176 + String message = "Unfortunately, " + + "having username:password with the Java-WebSocket library is not yet supported. " + + "Consider using other implementations such SocketModeClient.Backend.Tyrus."; + throw new UnsupportedOperationException(message); + } + InetSocketAddress proxyAddress = new InetSocketAddress(parsedProxy.getHost(), parsedProxy.getPort()); this.setProxy(new Proxy(Proxy.Type.HTTP, proxyAddress)); ProxyUrlUtil.setProxyAuthorizationHeader(proxyHeaders, parsedProxy); } - if (slackConfig.getProxyHeaders() != null) { + if (proxyHeaders != null && !proxyHeaders.isEmpty()) { for (Map.Entry each : proxyHeaders.entrySet()) { this.addHeader(each.getKey(), each.getValue()); } diff --git a/slack-api-client/src/main/java/com/slack/api/util/http/ProxyUrlUtil.java b/slack-api-client/src/main/java/com/slack/api/util/http/ProxyUrlUtil.java index 01ba84ed4..c5be5343a 100644 --- a/slack-api-client/src/main/java/com/slack/api/util/http/ProxyUrlUtil.java +++ b/slack-api-client/src/main/java/com/slack/api/util/http/ProxyUrlUtil.java @@ -48,14 +48,14 @@ public static ProxyUrl parse(String proxyUrl) { .username(userAndPassword[0]) .password(userAndPassword[1]) .host(hostAndPort[0]) - .port(hostAndPort.length == 2 ? Integer.parseInt(hostAndPort[1]) : 80) + .port(hostAndPort.length == 2 ? Integer.parseInt(hostAndPort[1].replace("/", "")) : 80) .build(); } else { String[] hostAndPort = proxyUrl.split("://")[1].split(":"); return ProxyUrl.builder() .schema(schema) .host(hostAndPort[0]) - .port(hostAndPort.length == 2 ? Integer.parseInt(hostAndPort[1]) : 80) + .port(hostAndPort.length == 2 ? Integer.parseInt(hostAndPort[1].replace("/", "")) : 80) .build(); } } diff --git a/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java index 3f83fa1ff..d8a73ebb4 100644 --- a/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java +++ b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java @@ -146,6 +146,15 @@ public void messageReceiver() throws Exception { // JavaWebSocket implementation // ------------------------------------------------- + @Test(expected = UnsupportedOperationException.class) + public void proxyAuth() throws Exception { + SlackConfig config = new SlackConfig(); + config.setMethodsEndpointUrlPrefix(webApiServer.getMethodsEndpointPrefix()); + config.setProxyUrl("http://user:pass@localhost:9000/"); + Slack slack = Slack.getInstance(config); + slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket); + } + @Test public void attributes_JavaWebSocket() throws Exception { try (SocketModeClient client = slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket)) { From fc09e63071f31fa0d74f597ac05f1d4cb1f723dd Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Wed, 11 Aug 2021 11:58:38 +0900 Subject: [PATCH 2/2] Fix the test --- .../src/main/java/com/slack/api/Slack.java | 2 +- .../api/socket_mode/SocketModeClientTest.java | 39 ++++--- .../SocketModeClient_Proxies_Test.java | 108 ++++++++++++++++++ 3 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClient_Proxies_Test.java diff --git a/slack-api-client/src/main/java/com/slack/api/Slack.java b/slack-api-client/src/main/java/com/slack/api/Slack.java index 76a661441..8cab6d3aa 100644 --- a/slack-api-client/src/main/java/com/slack/api/Slack.java +++ b/slack-api-client/src/main/java/com/slack/api/Slack.java @@ -173,7 +173,7 @@ public String issueSocketModeUrl(String appToken) throws IOException { } catch (SlackApiException e) { String message = "Failed to connect to the Socket Mode API endpoint. (" + "status: " + e.getResponse().code() + ", " + - "error: " + e.getError().getError() + + "error: " + (e.getError() != null ? e.getError().getError() : "") + ")"; throw new IOException(message, e); } diff --git a/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java index d8a73ebb4..34f78da84 100644 --- a/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java +++ b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClientTest.java @@ -68,11 +68,16 @@ public void connect() throws Exception { try (SocketModeClient client = slack.socketMode(VALID_APP_TOKEN)) { AtomicBoolean received = new AtomicBoolean(false); client.addWebSocketMessageListener(helloListener(received)); - client.addWebSocketErrorListener(error -> {}); - client.addWebSocketCloseListener((code, reason) -> {}); - client.addEventsApiEnvelopeListener(envelope -> {}); - client.addInteractiveEnvelopeListener(envelope -> {}); - client.addSlashCommandsEnvelopeListener(envelope -> {}); + client.addWebSocketErrorListener(error -> { + }); + client.addWebSocketCloseListener((code, reason) -> { + }); + client.addEventsApiEnvelopeListener(envelope -> { + }); + client.addInteractiveEnvelopeListener(envelope -> { + }); + client.addSlashCommandsEnvelopeListener(envelope -> { + }); client.connect(); Thread.sleep(500L); @@ -146,15 +151,6 @@ public void messageReceiver() throws Exception { // JavaWebSocket implementation // ------------------------------------------------- - @Test(expected = UnsupportedOperationException.class) - public void proxyAuth() throws Exception { - SlackConfig config = new SlackConfig(); - config.setMethodsEndpointUrlPrefix(webApiServer.getMethodsEndpointPrefix()); - config.setProxyUrl("http://user:pass@localhost:9000/"); - Slack slack = Slack.getInstance(config); - slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket); - } - @Test public void attributes_JavaWebSocket() throws Exception { try (SocketModeClient client = slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket)) { @@ -172,11 +168,16 @@ public void connect_JavaWebSocket() throws Exception { try (SocketModeClient client = slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket)) { AtomicBoolean received = new AtomicBoolean(false); client.addWebSocketMessageListener(helloListener(received)); - client.addWebSocketErrorListener(error -> {}); - client.addWebSocketCloseListener((code, reason) -> {}); - client.addEventsApiEnvelopeListener(envelope -> {}); - client.addInteractiveEnvelopeListener(envelope -> {}); - client.addSlashCommandsEnvelopeListener(envelope -> {}); + client.addWebSocketErrorListener(error -> { + }); + client.addWebSocketCloseListener((code, reason) -> { + }); + client.addEventsApiEnvelopeListener(envelope -> { + }); + client.addInteractiveEnvelopeListener(envelope -> { + }); + client.addSlashCommandsEnvelopeListener(envelope -> { + }); client.connect(); Thread.sleep(500L); diff --git a/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClient_Proxies_Test.java b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClient_Proxies_Test.java new file mode 100644 index 000000000..91e6f0fcb --- /dev/null +++ b/slack-api-client/src/test/java/test_locally/api/socket_mode/SocketModeClient_Proxies_Test.java @@ -0,0 +1,108 @@ +package test_locally.api.socket_mode; + +import com.slack.api.Slack; +import com.slack.api.SlackConfig; +import com.slack.api.socket_mode.SocketModeClient; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Credentials; +import org.eclipse.jetty.proxy.ConnectHandler; +import org.eclipse.jetty.proxy.ProxyServlet; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import test_with_remote_apis.AuthProxyHeadersTest; +import util.PortProvider; +import util.socket_mode.MockWebApiServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class SocketModeClient_Proxies_Test { + + static final String VALID_APP_TOKEN = "xapp-valid-123123123123123123123123123123123123"; + + MockWebApiServer webApiServer = new MockWebApiServer(); + SlackConfig config = new SlackConfig(); + + static Server proxyServer = new Server(); + static ServerConnector proxyServerConnector = new ServerConnector(proxyServer); + static Integer proxyServerPort; + + AtomicInteger proxyCallCount = new AtomicInteger(0); + + @Before + public void setUp() throws Exception { + webApiServer.start(); + config.setMethodsEndpointUrlPrefix(webApiServer.getMethodsEndpointPrefix()); + + // https://github.com/eclipse/jetty.project/blob/jetty-9.2.30.v20200428/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java + proxyServerPort = PortProvider.getPort(AuthProxyHeadersTest.class.getName()); + proxyServerConnector.setPort(proxyServerPort); + proxyServer.addConnector(proxyServerConnector); + ConnectHandler proxy = new ConnectHandler() { + @Override + public void handle(String target, Request br, HttpServletRequest request, HttpServletResponse res) + throws ServletException, IOException { + log.info("Proxy server handles a new connection (target: {})", target); + super.handle(target, br, request, res); + if (res.getStatus() != 407) { + proxyCallCount.incrementAndGet(); + } + } + + @Override + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) { + for (String name : Collections.list(request.getHeaderNames())) { + log.info("{}: {}", name, request.getHeader(name)); + if (name.toLowerCase(Locale.ENGLISH).equals("proxy-authorization")) { + return request.getHeader(name).equals("Basic bXktdXNlcm5hbWU6bXktcGFzc3dvcmQ="); + } + } + return false; + } + }; + proxyServer.setHandler(proxy); + ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS); + ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class); + context.addServlet(proxyServlet, "/*"); + proxyServer.start(); + + config.setProxyUrl("http://127.0.0.1:" + proxyServerPort); + + Map proxyHeaders = new HashMap<>(); + String username = "my-username"; + String password = "my-password"; + proxyHeaders.put("Proxy-Authorization", Credentials.basic(username, password)); + config.setProxyHeaders(proxyHeaders); + } + + @After + public void tearDown() throws Exception { + webApiServer.stop(); + + proxyServer.removeConnector(proxyServerConnector); + proxyServer.stop(); + } + + @Test(expected = UnsupportedOperationException.class) + public void proxyAuth() throws Exception { + SlackConfig config = new SlackConfig(); + config.setMethodsEndpointUrlPrefix(webApiServer.getMethodsEndpointPrefix()); + config.setProxyUrl("http://my-username:my-password@localhost:" + proxyServerPort + "/"); + Slack slack = Slack.getInstance(config); + slack.socketMode(VALID_APP_TOKEN, SocketModeClient.Backend.JavaWebSocket); + } +}