start implementation of the token-based authentication

This commit is contained in:
Vincent Behar 2011-07-31 23:05:05 +02:00
parent e5cb20a03c
commit 05e3b54977
5 changed files with 451 additions and 223 deletions

View file

@ -23,6 +23,7 @@
</properties>
<body>
<release version="2.0" date="Not Yet Released" description="API 2">
<action dev="vbehar" type="add">Token-based authentication</action>
</release>
<release version="1.2" date="2011-07-31" description="Ad-hoc scripts + history events">
<action dev="vbehar" type="add">Run ad-hoc scripts</action>

View file

@ -29,6 +29,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
@ -48,9 +51,11 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.RundeckApiException.RundeckApiTokenException;
import org.rundeck.api.parser.ParserHelper;
import org.rundeck.api.parser.XmlNodeParser;
import org.rundeck.api.util.AssertUtil;
@ -62,6 +67,10 @@ import org.rundeck.api.util.AssertUtil;
*/
class ApiCall {
/** RunDeck HTTP header for the auth-token (in case of token-based authentication) */
private static final transient String AUTH_TOKEN_HEADER = "X-RunDeck-Auth-Token";
/** {@link RundeckClient} instance holding the RunDeck url and the credentials */
private final RundeckClient client;
/**
@ -118,10 +127,11 @@ class ApiCall {
* @param parser used to parse the response
* @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
public <T> T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException {
RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
}
@ -132,9 +142,11 @@ class ApiCall {
* @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder}
* @return a new {@link InputStream} instance, not linked with network resources
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException {
public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException {
ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath));
// try to load the document, to throw an exception in case of error
@ -152,10 +164,11 @@ class ApiCall {
* @param parser used to parse the response
* @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException {
RundeckApiLoginException, RundeckApiTokenException {
HttpPost httpPost = new HttpPost(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath);
// POST a multi-part request, with all attachments
@ -176,10 +189,11 @@ class ApiCall {
* @param parser used to parse the response
* @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
public <T> T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException {
RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpDelete(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
}
@ -191,10 +205,11 @@ class ApiCall {
* @param parser used to parse the response
* @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
private <T> T execute(HttpRequestBase request, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException {
RundeckApiLoginException, RundeckApiTokenException {
// execute the request
InputStream response = execute(request);
@ -209,12 +224,18 @@ class ApiCall {
* @param request to execute. see {@link HttpGet}, {@link HttpDelete}, and so on...
* @return a new {@link InputStream} instance, not linked with network resources
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
private ByteArrayInputStream execute(HttpRequestBase request) throws RundeckApiException, RundeckApiLoginException {
private ByteArrayInputStream execute(HttpRequestBase request) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException {
HttpClient httpClient = instantiateHttpClient();
try {
login(httpClient);
// we only need to manually login in case of login-based authentication
// note that in case of token-based auth, the auth (via an HTTP header) is managed by an interceptor.
if (client.getToken() == null) {
login(httpClient);
}
// execute the HTTP request
HttpResponse response = null;
@ -244,8 +265,13 @@ class ApiCall {
// check the response code (should be 2xx, even in case of error : error message is in the XML result)
if (response.getStatusLine().getStatusCode() / 100 != 2) {
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for "
+ request.getURI());
if (response.getStatusLine().getStatusCode() == 403 && client.getToken() != null) {
throw new RundeckApiTokenException("Invalid Token ! Got HTTP response '" + response.getStatusLine()
+ "' for " + request.getURI());
} else {
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for "
+ request.getURI());
}
}
if (response.getEntity() == null) {
throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : "
@ -265,7 +291,7 @@ class ApiCall {
/**
* Do the actual work of login, using the given {@link HttpClient} instance. You'll need to re-use this instance
* when making API calls (such as running a job).
* when making API calls (such as running a job). Only use this in case of login-based authentication.
*
* @param httpClient pre-instantiated
* @throws RundeckApiLoginException if the login failed
@ -355,6 +381,17 @@ class ApiCall {
httpClient.setRoutePlanner(new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(),
ProxySelector.getDefault()));
// in case of token-based authentication, add the correct HTTP header to all requests via an interceptor
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
if (client.getToken() != null) {
request.addHeader(AUTH_TOKEN_HEADER, client.getToken());
}
}
});
return httpClient;
}
}

View file

@ -33,9 +33,28 @@ public class RundeckApiException extends RuntimeException {
}
/**
* Specific login-related error
* Specific authentication-related error (either login or token).
*
* @see RundeckApiLoginException
* @see RundeckApiTokenException
*/
public static class RundeckApiLoginException extends RundeckApiException {
public static class RundeckApiAuthException extends RundeckApiException {
private static final long serialVersionUID = 1L;
public RundeckApiAuthException(String message) {
super(message);
}
public RundeckApiAuthException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Specific authentication-related error (in case of login-based authentication)
*/
public static class RundeckApiLoginException extends RundeckApiAuthException {
private static final long serialVersionUID = 1L;
@ -48,4 +67,20 @@ public class RundeckApiException extends RuntimeException {
}
}
/**
* Specific authentication-related error (in case of token-based authentication)
*/
public static class RundeckApiTokenException extends RundeckApiAuthException {
private static final long serialVersionUID = 1L;
public RundeckApiTokenException(String message) {
super(message);
}
public RundeckApiTokenException(String message, Throwable cause) {
super(message, cause);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ h2. RunDeck API version 2
[Documentation of the RunDeck API version 2|http://rundeck.org/1.3/api/index.html]
* Token-based authentication - *TODO*
* Token-based authentication - OK
* Listing Jobs for a Project - *TODO*
* Updating and Listing Resources for a Project - *TODO*
* Refreshing Resources for a Project - *TODO*