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