Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/main/java/testingbot/TestingBotCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
import com.testingbot.models.TestingbotUser;
import com.testingbot.testingbotrest.TestingbotApiException;
import com.testingbot.testingbotrest.TestingbotREST;
import com.testingbot.testingbotrest.TestingbotUnauthorizedException;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
Expand Down Expand Up @@ -305,6 +309,39 @@ public final FormValidation doCheckId(@QueryParameter String value, @AncestorInP
}
return FormValidation.ok();
}

/**
* Verifies a key/secret against the TestingBot API and reports whether they authenticate.
* Gated so only users who can configure the surrounding item (or admins) can probe.
*/
@POST
public FormValidation doVerifyCredentials(@QueryParameter String key, @QueryParameter String secret,
@AncestorInPath ModelObject context) {
boolean allowed = Jenkins.get().hasPermission(Jenkins.ADMINISTER)
|| (context instanceof Item && ((Item) context).hasPermission(Item.CONFIGURE));
if (!allowed) {
return FormValidation.error("You do not have permission to verify credentials.");
}
if (Util.fixEmptyAndTrim(key) == null || Util.fixEmptyAndTrim(secret) == null) {
return FormValidation.error("Enter both a key and a secret first.");
}
// The password field may submit the encrypted Secret when editing an existing credential;
// fromString transparently yields the plaintext either way.
String plainSecret = Secret.fromString(secret).getPlainText();
try (TestingbotREST rest = new TestingbotREST(key.trim(), plainSecret)) {
TestingbotUser user = rest.getUserInfo();
if (user == null || Util.fixEmptyAndTrim(user.getEmail()) == null) {
return FormValidation.error("Could not verify these credentials with TestingBot.");
}
String plan = Util.fixEmptyAndTrim(user.getPlan());
return FormValidation.ok("Connection successful — signed in as %s%s",
user.getEmail(), plan != null ? " (" + plan + " plan)" : "");
} catch (TestingbotUnauthorizedException | TestingbotApiException e) {
// The REST client's typed failures: bad key/secret, and network/parse errors (it wraps
// IOException into TestingbotApiException). Unexpected runtime exceptions propagate.
return FormValidation.error("TestingBot authentication failed: " + e.getMessage());
Comment on lines +339 to +342

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Local file outline =="
ast-grep outline src/main/java/testingbot/TestingBotCredentials.java --view expanded || true

echo
echo "== Relevant local lines =="
sed -n '320,350p' src/main/java/testingbot/TestingBotCredentials.java

echo
echo "== Local references to TestingbotApiException/Unauthorized =="
rg -n "Testingbot(ApiException|UnauthorizedException)" src/main/java -S || true

Repository: testingbot/TestingBot-Jenkins-Plugin

Length of output: 3692


🌐 Web query:

TestingbotREST.java TestingbotApiException non-401 HTTP parse IOException raw GitHub

💡 Result:

In the testingbot-java client, the TestingbotApiException is typically raised when the TestingBot API returns a non-2xx HTTP status code that is not handled by specific exception types like TestingbotUnauthorizedException [1]. When you encounter an IOException during the parsing of a non-401 HTTP response, it often indicates that the client received a response body that could not be parsed as expected (e.g., malformed JSON) or that a network-level issue occurred during the request-response cycle [2][1]. To diagnose and resolve this: 1. Verify Credentials: Ensure your TESTINGBOT_KEY and TESTINGBOT_SECRET are correctly set [3][4]. A 401 Unauthorized error specifically points to authentication failures, while other status codes or parsing errors will trigger the general TestingbotApiException [3][1]. 2. Inspect the Raw Response: If the library throws an IOException or TestingbotApiException due to a parsing failure, the underlying cause is often an unexpected response format [2]. Ensure you are not accidentally hitting an HTML error page or a proxy message instead of the expected JSON API response. 3. Check Network/Environment: Network-level issues (such as timeouts or connection resets) can sometimes be misreported if the client attempts to parse an empty or interrupted response stream [5][2]. 4. Library Updates: If you are using an older version of the Java client, consider checking the repository for updates, as newer versions of TestingBot's client libraries have moved toward more robust, typed error hierarchies to better distinguish between transport failures, authentication errors, and API-level errors [5][6]. For debugging purposes, you can often capture the raw response or status code by wrapping the API call in a try-catch block and inspecting the exception object provided by the testingbot-java library [1].

Citations:


🌐 Web query:

site:github.com/testingbot/testingbot-java TestingbotApiException TestingbotUnauthorizedException TestingbotREST.java getUserInfo

💡 Result:

In the TestingBot Java REST client library, the getUserInfo method is used to retrieve details about the current user account [1]. This method is part of the TestingbotREST class [1]. When interacting with the TestingBot API via this library, methods like getUserInfo can throw specific exceptions to handle API-related errors [1]: 1. TestingbotApiException: This exception is thrown when an API error occurs, and it typically contains the JSON response from the server as a string, which can be useful for debugging [1]. 2. TestingbotUnauthorizedException: This exception is thrown when the API request fails due to invalid authentication credentials (API key or secret) [1]. To use the client, it is recommended to manage resources properly, for instance by using a try-with-resources block since the TestingbotREST class implements Closeable [1]. An example usage of the getUserInfo method is provided below: try (TestingbotREST restApi = new TestingbotREST("key", "secret")) { TestingbotUser user = restApi.getUserInfo; // Use user object... } catch (TestingbotUnauthorizedException e) { // Handle authentication error } catch (TestingbotApiException e) { // Handle API error }

Citations:


🌐 Web query:

"TestingbotApiException" "TestingbotUnauthorizedException" "TestingbotREST.java" GitHub

💡 Result:

The classes TestingbotApiException and TestingbotUnauthorizedException are custom exception types used within the official TestingBot Java client library [1]. This library, available on GitHub as testingbot-java, provides the TestingbotREST class to facilitate interaction with the TestingBot REST API [1][2][3]. According to the library's documentation, all API methods provided by the TestingbotREST class are designed to throw these exceptions when errors occur [1]. Specifically: - TestingbotApiException(String json): Thrown when the API returns an error response [1]. - TestingbotUnauthorizedException: Thrown when an authentication error occurs, typically associated with an invalid API key or secret [1][2]. To integrate this in a Java project, you can add the following dependency (using the version 1.0.10 as referenced in official documentation) [4][5][6]: Maven: com.testingbot testingbotrest 1.0.10 Gradle: implementation 'com.testingbot:testingbotrest:1.0.10' The TestingbotREST class is used to authenticate requests using your TestingBot API key and secret, allowing you to perform actions such as updating test metadata, retrieving test information, or managing browser lists [1][2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmpdir="$(mktemp -d)"
cd "$tmpdir"

for url in \
  "https://raw.githubusercontent.com/testingbot/testingbot-java/master/src/main/java/com/testingbot/testingbotrest/TestingbotREST.java" \
  "https://raw.githubusercontent.com/testingbot/testingbot-java/master/src/main/java/com/testingbot/testingbotrest/TestingbotApiException.java" \
  "https://raw.githubusercontent.com/testingbot/testingbot-java/master/src/main/java/com/testingbot/testingbotrest/TestingbotUnauthorizedException.java"
do
  echo "== $url =="
  curl -fsSL "$url" | sed -n '1,240p'
  echo
done

Repository: testingbot/TestingBot-Jenkins-Plugin

Length of output: 10836


Separate 401 auth failures from other TestingBot errors. TestingbotApiException covers non-401 API errors and transport/parse failures, so this catch block currently labels rate limits, server errors, and malformed responses as “authentication failed.” Catch TestingbotUnauthorizedException separately, and return a generic “TestingBot request failed” message for TestingbotApiException without echoing the raw response body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/testingbot/TestingBotCredentials.java` around lines 339 - 342,
Update the exception handling in the credentials validation flow around
TestingbotUnauthorizedException and TestingbotApiException: keep unauthorized
failures returning the authentication-failed message, but catch
TestingbotApiException separately and return a generic “TestingBot request
failed” validation message without including e.getMessage() or raw response
details.

Source: MCP tools

}
}
}

public static class NameProvider extends CredentialsNameProvider<TestingBotCredentials> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<f:password/>
</f:entry>

<f:validateButton title="${%Test Connection}" progress="${%Testing…}"
method="verifyCredentials" with="key,secret"/>

<f:entry title="${%Description}" field="description">
<f:textbox/>
</f:entry>
Expand Down