1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.rundeck.api;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.ProxySelector;
22 import java.security.KeyManagementException;
23 import java.security.KeyStoreException;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.UnrecoverableKeyException;
26 import java.security.cert.CertificateException;
27 import java.security.cert.X509Certificate;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map.Entry;
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.http.HttpResponse;
33 import org.apache.http.NameValuePair;
34 import org.apache.http.ParseException;
35 import org.apache.http.client.HttpClient;
36 import org.apache.http.client.entity.UrlEncodedFormEntity;
37 import org.apache.http.client.methods.HttpDelete;
38 import org.apache.http.client.methods.HttpGet;
39 import org.apache.http.client.methods.HttpPost;
40 import org.apache.http.client.methods.HttpRequestBase;
41 import org.apache.http.conn.scheme.Scheme;
42 import org.apache.http.conn.ssl.SSLSocketFactory;
43 import org.apache.http.conn.ssl.TrustStrategy;
44 import org.apache.http.entity.mime.HttpMultipartMode;
45 import org.apache.http.entity.mime.MultipartEntity;
46 import org.apache.http.entity.mime.content.InputStreamBody;
47 import org.apache.http.impl.client.DefaultHttpClient;
48 import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
49 import org.apache.http.message.BasicNameValuePair;
50 import org.apache.http.protocol.HTTP;
51 import org.apache.http.util.EntityUtils;
52 import org.dom4j.Document;
53 import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
54 import org.rundeck.api.parser.ParserHelper;
55 import org.rundeck.api.parser.XmlNodeParser;
56 import org.rundeck.api.util.AssertUtil;
57
58
59
60
61
62
63 class ApiCall {
64
65 private final RundeckClient client;
66
67
68
69
70
71
72
73 public ApiCall(RundeckClient client) throws IllegalArgumentException {
74 super();
75 this.client = client;
76 AssertUtil.notNull(client, "The RunDeck Client must not be null !");
77 }
78
79
80
81
82
83
84 public void ping() throws RundeckApiException {
85 HttpClient httpClient = instantiateHttpClient();
86 try {
87 HttpResponse response = httpClient.execute(new HttpGet(client.getUrl()));
88 if (response.getStatusLine().getStatusCode() / 100 != 2) {
89 throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' when pinging "
90 + client.getUrl());
91 }
92 } catch (IOException e) {
93 throw new RundeckApiException("Failed to ping RunDeck instance at " + client.getUrl(), e);
94 } finally {
95 httpClient.getConnectionManager().shutdown();
96 }
97 }
98
99
100
101
102
103
104 public void testCredentials() throws RundeckApiLoginException {
105 HttpClient httpClient = instantiateHttpClient();
106 try {
107 login(httpClient);
108 } finally {
109 httpClient.getConnectionManager().shutdown();
110 }
111 }
112
113
114
115
116
117
118
119
120
121
122
123 public <T> T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
124 RundeckApiLoginException {
125 return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
126 }
127
128
129
130
131
132
133
134
135
136
137 public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException {
138 ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath));
139
140
141 ParserHelper.loadDocument(response);
142 response.reset();
143
144 return response;
145 }
146
147
148
149
150
151
152
153
154
155
156
157 public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
158 RundeckApiLoginException {
159 HttpPost httpPost = new HttpPost(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath);
160
161
162 MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
163 for (Entry<String, InputStream> attachment : apiPath.getAttachments().entrySet()) {
164 entity.addPart(attachment.getKey(), new InputStreamBody(attachment.getValue(), attachment.getKey()));
165 }
166 httpPost.setEntity(entity);
167
168 return execute(httpPost, parser);
169 }
170
171
172
173
174
175
176
177
178
179
180
181 public <T> T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
182 RundeckApiLoginException {
183 return execute(new HttpDelete(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
184 }
185
186
187
188
189
190
191
192
193
194
195
196 private <T> T execute(HttpRequestBase request, XmlNodeParser<T> parser) throws RundeckApiException,
197 RundeckApiLoginException {
198
199 InputStream response = execute(request);
200
201
202 Document xmlDocument = ParserHelper.loadDocument(response);
203 return parser.parseXmlNode(xmlDocument);
204 }
205
206
207
208
209
210
211
212
213
214 private ByteArrayInputStream execute(HttpRequestBase request) throws RundeckApiException, RundeckApiLoginException {
215 HttpClient httpClient = instantiateHttpClient();
216 try {
217 login(httpClient);
218
219
220 HttpResponse response = null;
221 try {
222 response = httpClient.execute(request);
223 } catch (IOException e) {
224 throw new RundeckApiException("Failed to execute an HTTP " + request.getMethod() + " on url : "
225 + request.getURI(), e);
226 }
227
228
229
230 if (response.getStatusLine().getStatusCode() / 100 == 3
231 && HttpDelete.METHOD_NAME.equals(request.getMethod())) {
232 String newLocation = response.getFirstHeader("Location").getValue();
233 try {
234 EntityUtils.consume(response.getEntity());
235 } catch (IOException e) {
236 throw new RundeckApiException("Failed to consume entity (release connection)", e);
237 }
238 request = new HttpDelete(newLocation);
239 try {
240 response = httpClient.execute(request);
241 } catch (IOException e) {
242 throw new RundeckApiException("Failed to execute an HTTP " + request.getMethod() + " on url : "
243 + request.getURI(), e);
244 }
245 }
246
247
248
249 if (response.getStatusLine().getStatusCode() / 100 == 3) {
250 String newLocation = response.getFirstHeader("Location").getValue();
251 try {
252 EntityUtils.consume(response.getEntity());
253 } catch (IOException e) {
254 throw new RundeckApiException("Failed to consume entity (release connection)", e);
255 }
256 request = new HttpGet(newLocation);
257 try {
258 response = httpClient.execute(request);
259 } catch (IOException e) {
260 throw new RundeckApiException("Failed to execute an HTTP GET on url : " + request.getURI(), e);
261 }
262 }
263
264
265 if (response.getStatusLine().getStatusCode() / 100 != 2) {
266 throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for "
267 + request.getURI());
268 }
269 if (response.getEntity() == null) {
270 throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : "
271 + response.getStatusLine());
272 }
273
274
275 try {
276 return new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
277 } catch (IOException e) {
278 throw new RundeckApiException("Failed to consume entity and convert the inputStream", e);
279 }
280 } finally {
281 httpClient.getConnectionManager().shutdown();
282 }
283 }
284
285
286
287
288
289
290
291
292 private void login(HttpClient httpClient) throws RundeckApiLoginException {
293 String location = client.getUrl() + "/j_security_check";
294
295 while (true) {
296 HttpPost postLogin = new HttpPost(location);
297 List<NameValuePair> params = new ArrayList<NameValuePair>();
298 params.add(new BasicNameValuePair("j_username", client.getLogin()));
299 params.add(new BasicNameValuePair("j_password", client.getPassword()));
300 params.add(new BasicNameValuePair("action", "login"));
301
302 HttpResponse response = null;
303 try {
304 postLogin.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
305 response = httpClient.execute(postLogin);
306 } catch (IOException e) {
307 throw new RundeckApiLoginException("Failed to post login form on " + location, e);
308 }
309
310 if (response.getStatusLine().getStatusCode() / 100 == 3) {
311
312 location = response.getFirstHeader("Location").getValue();
313 try {
314 EntityUtils.consume(response.getEntity());
315 } catch (IOException e) {
316 throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
317 }
318 continue;
319 }
320 if (response.getStatusLine().getStatusCode() / 100 != 2) {
321 throw new RundeckApiLoginException("Invalid HTTP response '" + response.getStatusLine() + "' for "
322 + location);
323 }
324 try {
325 String content = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
326 if (StringUtils.contains(content, "j_security_check")) {
327 throw new RundeckApiLoginException("Login failed for user " + client.getLogin());
328 }
329 try {
330 EntityUtils.consume(response.getEntity());
331 } catch (IOException e) {
332 throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
333 }
334 } catch (IOException io) {
335 throw new RundeckApiLoginException("Failed to read RunDeck result", io);
336 } catch (ParseException p) {
337 throw new RundeckApiLoginException("Failed to parse RunDeck response", p);
338 }
339 break;
340 }
341 }
342
343
344
345
346
347
348 private HttpClient instantiateHttpClient() {
349 DefaultHttpClient httpClient = new DefaultHttpClient();
350
351
352 SSLSocketFactory socketFactory = null;
353 try {
354 socketFactory = new SSLSocketFactory(new TrustStrategy() {
355
356 @Override
357 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
358 return true;
359 }
360 }, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
361 } catch (KeyManagementException e) {
362 throw new RuntimeException(e);
363 } catch (UnrecoverableKeyException e) {
364 throw new RuntimeException(e);
365 } catch (NoSuchAlgorithmException e) {
366 throw new RuntimeException(e);
367 } catch (KeyStoreException e) {
368 throw new RuntimeException(e);
369 }
370 httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
371
372
373 System.setProperty("java.net.useSystemProxies", "true");
374 httpClient.setRoutePlanner(new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(),
375 ProxySelector.getDefault()));
376
377 return httpClient;
378 }
379 }