rundeck / rundeck-api-java-client

Java client for the Rundeck REST API
http://rundeck.github.com/rundeck-api-java-client
Apache License 2.0
13 stars 17 forks source link

Form based authentication failed on Tomcat 7 with rundeck-api-java-client (http 408) #7

Closed katanafleet closed 10 years ago

katanafleet commented 10 years ago

Input :

jenkins 1.6.2 war deployed on a tomcat 7 server Authentication works fine on GUI (w/ tomcat-users.xml) Using rundeck-api-java-client and form based authentication (jenkins plugin).

Results :

with a wrong login / password you are rejected but if your credentials are OK you get an HTT408 and client exit...

Example : Exception in thread "main" org.rundeck.api.RundeckApiException$RundeckApiLoginException: Invalid HTTP response 'HTTP/1.1 408 Request Timeout' for for http://xxxx/j_security_check at org.rundeck.api.ApiCall.login(ApiCall.java:274) at org.rundeck.api.ApiCall.execute(ApiCall.java:187) at org.rundeck.api.ApiCall.execute(ApiCall.java:169) at org.rundeck.api.ApiCall.get(ApiCall.java:119) at org.rundeck.api.RundeckClient.getSystemInfo(RundeckClient.java:976) at org.rundeck.cli.command.StatusCommand.execute(StatusCommand.java:46) at org.rundeck.cli.RundeckCli.execute(RundeckCli.java:84) at org.rundeck.cli.RundeckCli.run(RundeckCli.java:67) at org.rundeck.cli.RundeckCli.main(RundeckCli.java:93)

Remarks

Looks like it comes from the java client API as we reproduce it with jenkins plugin & rundeck remote cli :

Tomcat seems to reject direct access with POST to j_security_check You need to call another protected URIin order be redirected to the login form and then post your authentication data with JSESSIONID cookie.

I works fine with rundeck.jar & jetty.

gschueler commented 10 years ago

I think the jenkins plugin uses an old version of the rundeck-api-java-client lib, but this issue may still be present in current versions. Did you try it using the latest release of rundeck-api-java-client?

thanks for the bug report

katanafleet commented 10 years ago

I tried with a build from this repository (version 9.1) and a i still reproduce the bug with a groovy script calling rundeck-api-java-client. Looking into ApiCall class, i found out the issue. I ask to a java DEV of my company to update ApiCall login method with a first Http get and tested with groovy client (works with jetty & tomcat). Do you want me to push it ? But i do agree, the jenkins plugin & remote client also need to be upgrade with the last version of rundeck java api. By the way, wouldn't it be better if they used token authentication instead of form based authentication ?

katanafleet commented 10 years ago

Here's an update of ApiCall.java class which works fine with a tomcat container. We just add a first HTTP GET to j_security_check URL


 /**
     * 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). Only use this in case of login-based authentication.
     *
     * @param httpClient pre-instantiated
     * @throws RundeckApiLoginException if the login failed
     */
    private String login(HttpClient httpClient) throws RundeckApiLoginException {
        String sessionID = null;
        // 1. call expected GET request
        String location = client.getUrl();
        try {
            HttpGet getRequest = new HttpGet(location);
            HttpResponse response =  httpClient.execute(getRequest);
            // useless, sessionID is never used...
            Header cookieHeader = response.getFirstHeader("Set-Cookie");
            if(cookieHeader != null){
                String cookieStr = cookieHeader.getValue();
                if(cookieStr != null){
                    int i1 = cookieStr.indexOf("JSESSIONID=");
                    if(i1 >= 0){
                        cookieStr = cookieStr.substring(i1 +  "JSESSIONID=".length());
                        int i2 = cookieStr.indexOf(";");
                        if(i2 >= 0){
                            sessionID = cookieStr.substring(0, i2);
                        }
                    }
                }
            }
            try {
                EntityUtils.consume(response.getEntity());
            } catch (IOException e) {
                throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
            }
        } catch (IOException e) {
            throw new RundeckApiLoginException("Failed to get request on " + location, e);
        }
        // 2. then call POST login request
        location  += "/j_security_check";
        while (true) {
            try {
                HttpPost postLogin = new HttpPost(location);
                List params = new ArrayList();
                params.add(new BasicNameValuePair("j_username", client.getLogin()));
                params.add(new BasicNameValuePair("j_password", client.getPassword()));
                params.add(new BasicNameValuePair("action", "login"));
                postLogin.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                HttpResponse response = httpClient.execute(postLogin);
                if (response.getStatusLine().getStatusCode() / 100 == 3) {
                    // HTTP client refuses to handle redirects (code 3xx) for POST, so we have to do it manually...
                    location = response.getFirstHeader("Location").getValue();
                    try {
                        EntityUtils.consume(response.getEntity());
                    } catch (IOException e) {
                        throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
                    }
                    continue;
                }
                if (response.getStatusLine().getStatusCode() / 100 != 2) {
                    throw new RundeckApiLoginException("Invalid HTTP response '" + response.getStatusLine() + "' for "
                            + location);
                }
                try {
                    String content = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
                    if (StringUtils.contains(content, "j_security_check")) {
                        throw new RundeckApiLoginException("Login failed for user " + client.getLogin());
                    }
                    try {
                        EntityUtils.consume(response.getEntity());
                    } catch (IOException e) {
                        throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
                    }
                    break;
                } catch (IOException io) {
                    throw new RundeckApiLoginException("Failed to read RunDeck result", io);
                } catch (ParseException p) {
                    throw new RundeckApiLoginException("Failed to parse RunDeck response", p);
                }
            } catch (IOException e) {
                throw new RundeckApiLoginException("Failed to post login form on " + location, e);
            }
        }
        return sessionID;
    }
gschueler commented 10 years ago

thanks, I will add this patch. I tested it against tomcat6 and jetty7