1 package emissary.client;
2
3 import emissary.client.EmissaryResponse.EmissaryResponseHandler;
4 import emissary.config.ConfigUtil;
5 import emissary.config.Configurator;
6
7 import com.google.common.annotations.VisibleForTesting;
8 import jakarta.annotation.Nullable;
9 import jakarta.ws.rs.core.MediaType;
10 import org.apache.hc.client5.http.auth.AuthCache;
11 import org.apache.hc.client5.http.auth.AuthScope;
12 import org.apache.hc.client5.http.auth.Credentials;
13 import org.apache.hc.client5.http.auth.StandardAuthScheme;
14 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
15 import org.apache.hc.client5.http.classic.methods.HttpPost;
16 import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
17 import org.apache.hc.client5.http.config.ConnectionConfig;
18 import org.apache.hc.client5.http.config.RequestConfig;
19 import org.apache.hc.client5.http.cookie.Cookie;
20 import org.apache.hc.client5.http.entity.EntityBuilder;
21 import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
22 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
23 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
24 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
25 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
26 import org.apache.hc.client5.http.protocol.HttpClientContext;
27 import org.apache.hc.core5.http.HttpStatus;
28 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
29 import org.apache.hc.core5.util.Timeout;
30 import org.eclipse.jetty.util.security.Password;
31 import org.glassfish.jersey.server.filter.CsrfProtectionFilter;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import java.io.IOException;
36 import java.net.URISyntaxException;
37 import java.util.Collections;
38 import java.util.Properties;
39 import java.util.concurrent.TimeUnit;
40
41
42
43
44 @SuppressWarnings("NonFinalStaticField")
45 public class EmissaryClient {
46
47 public static final String DEFAULT_CONTEXT = "emissary";
48 public static final String JETTY_USER_FILE_PROPERTY_NAME = "emissary.jetty.users.file";
49 public static final int DEFAULT_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(100L);
50 public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(2L);
51 public static final int DEFAULT_SOCKET_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(1L);
52 public static final int DEFAULT_RETRIES = 3;
53 public static final String DEFAULT_USERNAME = "emissary";
54 public static final String DEFAULT_PASSWORD = "password";
55 public static final String CSRF_HEADER_PARAM = CsrfProtectionFilter.HEADER_NAME;
56
57 private static final Logger LOGGER = LoggerFactory.getLogger(EmissaryClient.class);
58
59 private static final PoolingHttpClientConnectionManager CONNECTION_MANAGER = HTTPConnectionFactory.getFactory().getDefaultConnectionManager();
60
61 private static final BasicCredentialsProvider CRED_PROV = new BasicCredentialsProvider();
62 protected static final int ANY_PORT = -1;
63 @Nullable
64 protected static final String ANY_HOST = null;
65
66 @Nullable
67 private static CloseableHttpClient staticClient = null;
68 @Nullable
69 private static RequestConfig staticRequestConfig = null;
70 @Nullable
71 private static ConnectionConfig staticConnectionConfig = null;
72
73
74 public static String context = DEFAULT_CONTEXT;
75 protected static int retries = DEFAULT_RETRIES;
76 protected static String username = DEFAULT_USERNAME;
77
78 protected static int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
79
80 protected static int connectionManagerTimeout = DEFAULT_CONNECTION_MANAGER_TIMEOUT;
81
82 protected static int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
83
84 protected static final AuthCache AUTH_CACHE = new BasicAuthCache();
85
86 private final CloseableHttpClient client;
87 private final RequestConfig requestConfig;
88 private ConnectionConfig connectionConfig;
89
90 static {
91 configure();
92 }
93
94 @VisibleForTesting
95 protected static void configure() {
96 LOGGER.debug("Configuring EmissaryClient");
97
98
99 try {
100 final Configurator c = ConfigUtil.getConfigInfo(EmissaryClient.class);
101 retries = c.findIntEntry("retries", DEFAULT_RETRIES);
102 username = c.findStringEntry("username", DEFAULT_USERNAME);
103 connectionTimeout = c.findIntEntry("connectionTimeout", DEFAULT_CONNECTION_TIMEOUT);
104 connectionManagerTimeout = c.findIntEntry("connectionManagerTimeout", DEFAULT_CONNECTION_MANAGER_TIMEOUT);
105 socketTimeout = c.findIntEntry("socketTimeout", DEFAULT_SOCKET_TIMEOUT);
106 context = c.findStringEntry("context", DEFAULT_CONTEXT);
107 } catch (IOException iox) {
108 LOGGER.warn("Cannot read EmissaryClient properties, configuring defaults: {}", iox.getMessage());
109 retries = DEFAULT_RETRIES;
110 username = DEFAULT_USERNAME;
111 connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
112 connectionManagerTimeout = DEFAULT_CONNECTION_MANAGER_TIMEOUT;
113 socketTimeout = DEFAULT_SOCKET_TIMEOUT;
114 context = DEFAULT_CONTEXT;
115 }
116
117
118
119 try {
120 String userPropertiesFile = System.getProperty(JETTY_USER_FILE_PROPERTY_NAME);
121 if (null == userPropertiesFile) {
122 LOGGER.debug("System property '{}' not set, using default jetty-users.properties", JETTY_USER_FILE_PROPERTY_NAME);
123 userPropertiesFile = "jetty-users.properties";
124 }
125 LOGGER.debug("Reading password from {}", userPropertiesFile);
126 final Properties props = ConfigUtil.getPropertyInfo(userPropertiesFile);
127 String pass = DEFAULT_PASSWORD;
128 final String value = props.getProperty(username, pass);
129 if (value != null && value.indexOf(',') != -1) {
130 pass = value.substring(0, value.indexOf(',')).trim();
131 } else if (pass.equals(value)) {
132 LOGGER.error("Error reading password from {}", userPropertiesFile);
133 }
134
135 final String decodedPassword = pass.startsWith("OBF:") ? Password.deobfuscate(pass) : pass;
136 final Credentials cred = new UsernamePasswordCredentials(username, decodedPassword.toCharArray());
137 CRED_PROV.setCredentials(new AuthScope(ANY_HOST, ANY_PORT), cred);
138 } catch (IOException iox) {
139 LOGGER.error("Cannot read {} in EmissaryClient, defaulting credentials", System.getProperty(JETTY_USER_FILE_PROPERTY_NAME));
140 final Credentials cred = new UsernamePasswordCredentials(username, DEFAULT_PASSWORD.toCharArray());
141 final AuthScope authScope = new AuthScope(ANY_HOST, ANY_PORT);
142 CRED_PROV.setCredentials(authScope, cred);
143 }
144
145 staticRequestConfig =
146 RequestConfig.custom()
147 .setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionManagerTimeout))
148 .setTargetPreferredAuthSchemes(Collections.singleton(StandardAuthScheme.DIGEST))
149 .setProxyPreferredAuthSchemes(Collections.singleton(StandardAuthScheme.DIGEST))
150 .build();
151
152 staticConnectionConfig = ConnectionConfig.custom()
153 .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout))
154 .setSocketTimeout(Timeout.ofMilliseconds(socketTimeout)).build();
155
156 CONNECTION_MANAGER.setDefaultConnectionConfig(staticConnectionConfig);
157
158 staticClient =
159 HttpClientBuilder.create().setConnectionManager(CONNECTION_MANAGER).setDefaultCredentialsProvider(CRED_PROV)
160 .setDefaultRequestConfig(staticRequestConfig).build();
161
162 }
163
164 public EmissaryClient() {
165 this(staticClient, staticRequestConfig, staticConnectionConfig);
166 }
167
168 public EmissaryClient(RequestConfig requestConfig, ConnectionConfig connectionConfig) {
169 this(staticClient, requestConfig, connectionConfig);
170 }
171
172 public EmissaryClient(CloseableHttpClient client) {
173 this(client, staticRequestConfig, staticConnectionConfig);
174 }
175
176 public EmissaryClient(CloseableHttpClient client, RequestConfig requestConfig, ConnectionConfig connectionConfig) {
177 this.client = client;
178 this.requestConfig = requestConfig;
179 this.connectionConfig = connectionConfig;
180 }
181
182 public EmissaryResponse send(final HttpUriRequestBase method) {
183 return send(method, null);
184 }
185
186
187
188
189
190
191
192 public EmissaryResponse send(final HttpUriRequestBase method, @Nullable final Cookie cookie) {
193 try {
194 LOGGER.debug("Sending {} to {}", method.getMethod(), method.getUri());
195 } catch (URISyntaxException e) {
196 LOGGER.debug("Sending {} and failed to retrieve URI", method.getMethod());
197 }
198
199 HttpClientContext localContext = HttpClientContext.create();
200 localContext.setAttribute(HttpClientContext.AUTH_CACHE, EmissaryClient.AUTH_CACHE);
201
202 if (cookie != null) {
203 localContext.getCookieStore().addCookie(cookie);
204 }
205
206 try {
207
208
209
210 method.setConfig(requestConfig);
211 CloseableHttpClient thisClient = getHttpClient();
212 return thisClient.execute(method, localContext, new EmissaryResponseHandler());
213 } catch (IOException e) {
214 LOGGER.debug("Problem processing request:", e);
215 BasicClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage());
216 response.setEntity(EntityBuilder.create().setText(e.getClass() + ": " + e.getMessage()).setContentEncoding(MediaType.TEXT_PLAIN).build());
217 return new EmissaryResponse(response);
218 }
219 }
220
221 protected CloseableHttpClient getHttpClient() {
222 return client;
223 }
224
225 protected RequestConfig getRequestConfig() {
226 return requestConfig;
227 }
228
229 protected ConnectionConfig getConnectionConfig() {
230 return connectionConfig;
231 }
232
233
234 public void setConnectionTimeout(int timeout) {
235 if (timeout > 0) {
236 connectionConfig = ConnectionConfig.copy(connectionConfig).setConnectTimeout(Timeout.ofMilliseconds(timeout)).build();
237 } else {
238 LOGGER.warn("Tried to set timeout to {}", timeout);
239 }
240 }
241
242 protected String getCsrfToken() {
243 return DEFAULT_CONTEXT;
244 }
245
246 public HttpPost createHttpPost(String uri, String context, String endpoint) {
247 return createHttpPost(uri + context + endpoint, getCsrfToken());
248 }
249
250 public HttpPost createHttpPost(String uri) {
251 return createHttpPost(uri, getCsrfToken());
252 }
253
254 public HttpPost createHttpPost(String uri, String csrfToken) {
255 HttpPost method = new HttpPost(uri);
256 setCsrfHeader(method, csrfToken);
257 return method;
258 }
259
260 public HttpUriRequestBase setCsrfHeader(HttpUriRequestBase request, String csrfToken) {
261 return setCsrfHeader(request, CSRF_HEADER_PARAM, csrfToken);
262 }
263
264 public HttpUriRequestBase setCsrfHeader(HttpUriRequestBase request, String csrfParam, String csrfToken) {
265 request.addHeader(csrfParam, csrfToken);
266 return request;
267 }
268
269 }