Skip to content

Commit a6f8dc4

Browse files
Fix TestingBot credential resolution and Test Connection
Two correctness fixes surfaced by an end-to-end run against a real account. 1. Credentials never resolved at build time. TestingBotCredentials extended BaseCredentials (implements StandardCredentials). CredentialsProvider.lookupCredentialsInItem(TestingBotCredentials.class, ...) — used by the build wrapper's getCredentials(), availableCredentials() and every credential dropdown — returned an empty list for it, even though the stored objects are instances of the class (a StandardCredentials.class lookup found them; a UsernamePasswordCredentialsImpl, which extends BaseStandardCredentials, is found by concrete class). As a result the freestyle wrapper silently resolved null credentials, so TB_KEY/TB_SECRET/TESTINGBOT_BUILD were never injected and the dropdowns were empty. Make TestingBotCredentials extend BaseStandardCredentials, which owns id and description (and the id-based equals/hashCode), so concrete-type lookups work. Old-format credentials.xml (scope/id/description/key/secret) still deserializes unchanged — field names are identical. 2. Test Connection failed for accounts without an email. doVerifyCredentials required a non-null user email; a valid account can return 200 with a null email (e.g. sub-accounts), which produced a false 'Could not verify these credentials' for working key/secret. Treat any authenticated user as success, preferring the email and falling back to the account first name.
1 parent 6c594a9 commit a6f8dc4

1 file changed

Lines changed: 13 additions & 33 deletions

File tree

src/main/java/testingbot/TestingBotCredentials.java

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import org.kohsuke.stapler.export.Exported;
88
import org.kohsuke.stapler.verb.POST;
99

10-
import com.cloudbees.plugins.credentials.BaseCredentials;
1110
import com.cloudbees.plugins.credentials.Credentials;
1211
import com.cloudbees.plugins.credentials.CredentialsDescriptor;
1312
import com.cloudbees.plugins.credentials.CredentialsMatcher;
@@ -28,7 +27,6 @@
2827
import com.testingbot.testingbotrest.TestingbotREST;
2928
import com.testingbot.testingbotrest.TestingbotUnauthorizedException;
3029
import edu.umd.cs.findbugs.annotations.CheckForNull;
31-
import edu.umd.cs.findbugs.annotations.NonNull;
3230
import hudson.Extension;
3331
import hudson.Util;
3432
import hudson.model.AbstractItem;
@@ -56,19 +54,15 @@
5654
import java.util.logging.Logger;
5755

5856
@NameWith(value = TestingBotCredentials.NameProvider.class)
59-
public class TestingBotCredentials extends BaseCredentials implements StandardCredentials {
57+
public class TestingBotCredentials extends BaseStandardCredentials {
6058
private static final String CREDENTIAL_DISPLAY_NAME = "TestingBot";
6159

62-
private final String id;
63-
private final String description;
6460
private final String key;
6561
private final Secret secret;
6662

6763
@DataBoundConstructor
6864
public TestingBotCredentials(String id, String description, String key, String secret) {
69-
super(CredentialsScope.GLOBAL);
70-
this.id = IdCredentials.Helpers.fixEmptyId(id);
71-
this.description = Util.fixNull(description);
65+
super(CredentialsScope.GLOBAL, IdCredentials.Helpers.fixEmptyId(id), Util.fixNull(description));
7266
this.key = Util.fixNull(key);
7367
this.secret = Secret.fromString(secret);
7468
}
@@ -86,28 +80,6 @@ public String getDecryptedSecret() {
8680
return secret.getPlainText();
8781
}
8882

89-
@NonNull
90-
@Exported
91-
public String getDescription() {
92-
return description;
93-
}
94-
95-
@NonNull
96-
@Exported
97-
public String getId() {
98-
return id;
99-
}
100-
101-
@Override
102-
public final boolean equals(Object o) {
103-
return IdCredentials.Helpers.equals(this, o);
104-
}
105-
106-
@Override
107-
public final int hashCode() {
108-
return IdCredentials.Helpers.hashCode(this);
109-
}
110-
11183
private static List<String> getLegacyCredentials() {
11284
DataInputStream in = null;
11385
BufferedReader br = null;
@@ -330,12 +302,20 @@ public FormValidation doVerifyCredentials(@QueryParameter String key, @QueryPara
330302
String plainSecret = Secret.fromString(secret).getPlainText();
331303
try (TestingbotREST rest = new TestingbotREST(key.trim(), plainSecret)) {
332304
TestingbotUser user = rest.getUserInfo();
333-
if (user == null || Util.fixEmptyAndTrim(user.getEmail()) == null) {
305+
if (user == null) {
334306
return FormValidation.error("Could not verify these credentials with TestingBot.");
335307
}
308+
// A valid TestingBot account may not expose an email (e.g. sub-accounts); the
309+
// authenticated user object itself is proof the key/secret work. Prefer the email,
310+
// fall back to the account's first name, so verification never fails on a null email.
311+
String who = Util.fixEmptyAndTrim(user.getEmail());
312+
if (who == null) {
313+
who = Util.fixEmptyAndTrim(user.getFirstName());
314+
}
336315
String plan = Util.fixEmptyAndTrim(user.getPlan());
337-
return FormValidation.ok("Connection successful — signed in as %s%s",
338-
user.getEmail(), plan != null ? " (" + plan + " plan)" : "");
316+
return FormValidation.ok("Connection successful%s%s",
317+
who != null ? " — signed in as " + who : "",
318+
plan != null ? " (" + plan + " plan)" : "");
339319
} catch (TestingbotUnauthorizedException | TestingbotApiException e) {
340320
// The REST client's typed failures: bad key/secret, and network/parse errors (it wraps
341321
// IOException into TestingbotApiException). Unexpected runtime exceptions propagate.

0 commit comments

Comments
 (0)