From 632611fb2c5f9361a1dcb719b84f151dd67b759f Mon Sep 17 00:00:00 2001 From: Priya Date: Fri, 6 Dec 2024 20:47:01 +0530 Subject: [PATCH 1/6] [JENKINS-74964] Fix: Display error message when adding invalid certificate credentials --- .../credentials/CredentialsSelectHelper.java | 11 ++- .../impl/CertificateCredentialsImpl.java | 9 ++- .../impl/UsernamePasswordCredentialsImpl.java | 2 - .../credentials/impl/Messages.properties | 1 + .../lib/credentials/select/select.js | 1 + .../CredentialsSelectHelperTest.java | 67 +++++++++++++++++ .../CertificateCredentialsImplFIPSTest.java | 75 +++++++++++++++++++ .../impl/CertificateCredentialsImplTest.java | 3 +- .../plugins/credentials/impl/FIPSCerts.pem | 20 +++++ .../plugins/credentials/impl/FIPSKey.pem | 28 +++++++ 10 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java create mode 100644 src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem create mode 100644 src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java index e07df340..ddbeb8d0 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java @@ -57,6 +57,7 @@ import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.Localizable; +import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.StaplerResponse2; @@ -608,8 +609,14 @@ public JSONObject doAddCredentials(StaplerRequest2 req, StaplerResponse2 rsp) th .element("notificationType", "ERROR"); } store.checkPermission(CredentialsStoreAction.CREATE); - Credentials credentials = Descriptor.bindJSON(req, Credentials.class, data.getJSONObject("credentials")); - boolean credentialsWereAdded = store.addCredentials(wrapper.getDomain(), credentials); + boolean credentialsWereAdded; + try { + Credentials credentials = Descriptor.bindJSON(req, Credentials.class, + data.getJSONObject("credentials")); + credentialsWereAdded = store.addCredentials(wrapper.getDomain(), credentials); + } catch (HttpResponses.HttpResponseException e) { + return new JSONObject().element("message", e.getMessage()).element("notificationType", "ERROR"); + } if (credentialsWereAdded) { return new JSONObject() .element("message", "Credentials created") diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 34e7ccb0..b3b8c29b 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -129,9 +129,12 @@ public class CertificateCredentialsImpl extends BaseStandardCredentials implemen public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description, @CheckForNull String password, - @NonNull KeyStoreSource keyStoreSource) { + @NonNull KeyStoreSource keyStoreSource) throws Descriptor.FormException { super(scope, id, description); Objects.requireNonNull(keyStoreSource); + if (FIPS140.useCompliantAlgorithms() && StringUtils.isNotBlank(password) && password.length() < 14) { + throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_ShortPasswordFIPS(), "password"); + } this.password = Secret.fromString(password); this.keyStoreSource = keyStoreSource; // ensure the keySore is valid @@ -139,7 +142,9 @@ public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, try { keyStoreSource.toKeyStore(toCharArray(this.password)); } catch (GeneralSecurityException | IOException e) { - throw new IllegalArgumentException("KeyStore is not valid.", e); + LOGGER.log(Level.WARNING, Messages.CertificateCredentialsImpl_InvalidKeystore(), e); + throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_InvalidKeystore(), e, + "keyStoreSource"); } } diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/UsernamePasswordCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/UsernamePasswordCredentialsImpl.java index 34321f8f..f652cdca 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/UsernamePasswordCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/UsernamePasswordCredentialsImpl.java @@ -42,8 +42,6 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -import java.util.Objects; - /** * Concrete implementation of {@link StandardUsernamePasswordCredentials}. * diff --git a/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties b/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties index 90ab3277..92dc7953 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties @@ -24,6 +24,7 @@ UsernamePasswordCredentialsImpl.DisplayName=Username with password CertificateCredentialsImpl.DisplayName=Certificate CertificateCredentialsImpl.EmptyKeystore=Empty keystore +CertificateCredentialsImpl.InvalidKeystore=KeyStore is not valid CertificateCredentialsImpl.LoadKeyFailed=Could retrieve key "{0}" CertificateCredentialsImpl.LoadKeyFailedQueryEmptyPassword=Could retrieve key "{0}". You may need to provide a password CertificateCredentialsImpl.LoadKeystoreFailed=Could not load keystore diff --git a/src/main/resources/lib/credentials/select/select.js b/src/main/resources/lib/credentials/select/select.js index 1114fb97..932f19e7 100644 --- a/src/main/resources/lib/credentials/select/select.js +++ b/src/main/resources/lib/credentials/select/select.js @@ -123,6 +123,7 @@ window.credentials.addSubmit = function (_) { .catch((e) => { // notificationBar.show(...) with logging ID could be handy here? console.error("Could not add credentials:", e); + window.notificationBar.show("Credentials creation failed.", window.notificationBar["ERROR"]); }) } }; diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java index 5703712c..a4483302 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java @@ -1,18 +1,32 @@ package com.cloudbees.plugins.credentials; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertTrue; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; +import com.cloudbees.plugins.credentials.impl.Messages; import hudson.model.UnprotectedRootAction; +import java.io.IOException; import java.util.List; +import net.sf.json.JSONObject; import org.hamcrest.Matchers; +import org.htmlunit.Page; +import org.htmlunit.html.DomNode; +import org.htmlunit.html.DomNodeList; import org.htmlunit.html.HtmlButton; +import org.htmlunit.html.HtmlElementUtil; import org.htmlunit.html.HtmlForm; +import org.htmlunit.html.HtmlFormUtil; import org.htmlunit.html.HtmlInput; +import org.htmlunit.html.HtmlOption; import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlRadioButtonInput; import org.junit.Rule; import org.junit.Test; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; @@ -56,6 +70,59 @@ public void doAddCredentialsFromPopupWorksAsExpected() throws Exception { } } + @Test + @Issue("JENKINS-74964") + public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore() throws Exception { + + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage htmlPage = wc.goTo("credentials-selection"); + + HtmlButton addCredentialsButton = htmlPage.querySelector(".credentials-add-menu"); + addCredentialsButton.fireEvent("mouseenter"); + addCredentialsButton.click(); + + HtmlButton jenkinsCredentialsOption = htmlPage.querySelector(".jenkins-dropdown__item"); + jenkinsCredentialsOption.click(); + + wc.waitForBackgroundJavaScript(4000); + HtmlForm form = htmlPage.querySelector("#credentials-dialog-form"); + String certificateDisplayName = j.jenkins.getDescriptor(CertificateCredentialsImpl.class).getDisplayName(); + String KeyStoreSourceDisplayName = j.jenkins.getDescriptor( + CertificateCredentialsImpl.PEMEntryKeyStoreSource.class).getDisplayName(); + DomNodeList allOptions = htmlPage.getDocumentElement().querySelectorAll( + "select.dropdownList option"); + boolean optionFound = selectOption(allOptions, certificateDisplayName); + assertTrue("The Certificate option was not found in the credentials type select", optionFound); + List inputs = htmlPage.getDocumentElement().getByXPath( + "//input[contains(@name, 'keyStoreSource') and following-sibling::label[contains(.,'" + + KeyStoreSourceDisplayName + "')]]"); + assertThat("query should return only a singular input", inputs, hasSize(1)); + HtmlElementUtil.click(inputs.get(0)); + wc.waitForBackgroundJavaScript(4000); + Page submit = HtmlFormUtil.submit(form); + JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); + assertThat(responseJson.getString("message"), is(Messages.CertificateCredentialsImpl_InvalidKeystore())); + assertThat(responseJson.getString("notificationType"), is("ERROR")); + } + } + + private static boolean selectOption(DomNodeList allOptions, String optionName) { + return allOptions.stream().anyMatch(domNode -> { + if (domNode instanceof HtmlOption option) { + if (option.getVisibleText().equals(optionName)) { + try { + HtmlElementUtil.click(option); + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + } + + return false; + }); + } + @TestExtension public static class CredentialsSelectionAction implements UnprotectedRootAction { @Override diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java new file mode 100644 index 00000000..6cb9f0c4 --- /dev/null +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java @@ -0,0 +1,75 @@ +package com.cloudbees.plugins.credentials.impl; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.RealJenkinsRule; + +import hudson.ExtensionList; +import hudson.model.Descriptor; +import hudson.util.FormValidation; + +import com.cloudbees.plugins.credentials.CredentialsScope; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThrows; + +public class CertificateCredentialsImplFIPSTest { + + @Rule + public RealJenkinsRule rule = new RealJenkinsRule().javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true"); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + String pemCert; + String pemKey; + + @Before + public void setup() throws IOException { + pemCert = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("FIPSCerts.pem"), + StandardCharsets.UTF_8); + pemKey = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("FIPSKey.pem"), + StandardCharsets.UTF_8); + } + + @Test + public void doCheckPasswordTest() throws Throwable { + rule.then(r -> { + CertificateCredentialsImpl.DescriptorImpl descriptor = ExtensionList.lookupSingleton( + CertificateCredentialsImpl.DescriptorImpl.class); + FormValidation result = descriptor.doCheckPassword("passwordFipsCheck"); + assertThat(result.kind, is(FormValidation.Kind.OK)); + result = descriptor.doCheckPassword("foo"); + assertThat(result.kind, is(FormValidation.Kind.ERROR)); + assertThat(result.getMessage(), + is(StringEscapeUtils.escapeHtml4(Messages.CertificateCredentialsImpl_ShortPasswordFIPS()))); + }); + } + + @Test + public void invalidPEMKeyStoreTest() throws Throwable { + CertificateCredentialsImpl.PEMEntryKeyStoreSource storeSource = new CertificateCredentialsImpl.PEMEntryKeyStoreSource( + pemCert, pemKey); + rule.then(r -> { + new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", + "Validate the certificate credentials", "passwordFipsCheck", storeSource); + + assertThrows(Descriptor.FormException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", + "Validate the certificate password", "foo", storeSource)); + assertThrows(Descriptor.FormException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", + "Validate the certificate keyStore", "passwordFipsCheck", + new CertificateCredentialsImpl.PEMEntryKeyStoreSource( + null, null))); + }); + } +} diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 96ff209a..182d3697 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -47,6 +47,7 @@ import org.htmlunit.html.HtmlRadioButtonInput; import hudson.Util; +import hudson.model.Descriptor; import hudson.model.ItemGroup; import hudson.security.ACL; import hudson.util.Secret; @@ -106,7 +107,7 @@ public void setup() throws IOException { } @Test - public void displayName() throws IOException { + public void displayName() throws IOException, Descriptor.FormException { SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore); assertEquals(EXPECTED_DISPLAY_NAME, CredentialsNameProvider.name(new CertificateCredentialsImpl(null, "abc123", null, "password", storeSource))); diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem new file mode 100644 index 00000000..7c15c314 --- /dev/null +++ b/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIUWcaB9PB40lCu4um/CD8Ni6yNxkwwDQYJKoZIhvcNAQEL +BQAwUDELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlROMREwDwYDVQQHDAhUaXJ1cHB1 +cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI0MTEyODE2 +MzI1OFoXDTI1MTEyODE2MzI1OFowUDELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlRO +MREwDwYDVQQHDAhUaXJ1cHB1cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8dgK5e2+wy3 +2uRxF8hiaSFnUjfKOVDYrj+RbZ+ss9Kzxm2T3nfGcjGKHVHTvkaoduVdF95V8RtQ +I2lbwPnb/RKY6+Rlx+74e6Aw+KvCAEFa0lt0dST7IpErEetDppkkR5N/RbVXvon8 +sbJSHRZQOazFD2fG6MJJYiHqLC9xqfKOrZ07JQ9/J/mXN05mTG3gWXIlaJxf6hv3 ++n2NfkaTioor9dHGwr03S9D4CJWbJvk2006WCh9TEKCrG+oOAU/Fm+mlWndeResv +EWHiH13BMOsqWj/q9QlMYnmRl4MN00SSKKREQAUAAd6WbXg6+iavxGpN8eJw4O72 +wti2bS6I4wIDAQABoyEwHzAdBgNVHQ4EFgQUsACQZeota/vgn2vOY0EVIMoKM7Iw +DQYJKoZIhvcNAQELBQADggEBAEZ7qOLFPL4QJYcD3DYohFyOR2QeQQ5sceLlCg+/ +vtET3TPqBGswHER4fkOUgehud9i/h6ff33KCgTGMpGCWkYISaAbCQgGeD8AJQIOX +aWQ12Ux/E98a4Bk9nid0moiXKTXzMBule44JolGWroxayrJnYDCo39IraJO6XxlK +SJnI1dA1uRuJl6XKHr1N/knH3QBE4wI3CjqHc9qjTXVprhRosmTykvIeLaNL/ZxW +/QnKD5VRWRmHFom03COkmPSyGWNp225dLgN+rv9e2Pvn0AP8CeUJAoWSXwxGpv9Y +AgIsGfIhwQIt3AsmmujlaLvjRQpxBh00ba77kteFR4S3WMk= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem new file mode 100644 index 00000000..94b70bdd --- /dev/null +++ b/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChhYxialRHFZdE +Y1d6A0zTCi6pMnQlk/DU2xt2qV6xrI/Jkz0xIuDjCA/vD4EyEEh7aPEY5Ooxz6zz +tmrhF8nG2Nis4UBAxtTHqV2D5eLPfxtrRMccrIYh6121K0B45vqcHpBq/nFMNGyK +q1WJBmb4JTZ4MsZXcpe7rGBhApwgUdP/ey6jwf7oG2YCBOywZ7hu0Vs6yeSbF84R +uTuVBhVdNm2BJHMy7P2BxyDpm5RSbHQGYox68pn3kfVEdv/iC/8qwq7sbyzbKsgv +YJrpqAunTEkgLBkG/NtsSFlTzssEAtSXErfI+6b8Zd4UK6E9CoclKWyzEqMT9mVc +hox4+XL/AgMBAAECggEANYYAqBgeB1QzRRk6QpdXXNOR9MVgUZd9hbt5lU+4rl3F +ZAGjlGW/adwhE5HquQFGU4bJ5frtVEZCRJxdPGvalEcFPfyCgzSgC+2mrG+AQkwX +dOtco7bT1+ebrM5BVg8MWrGSH7JjLuJsWWM/O+HgOzhxnVEOAqpZd3o+kccAn4CX +XjxbIc3/VJ4PXm7kOfU4NL7tJ1G81UQMTNMODisvi++yAa+Rw30NpnQ15GcdnWfF +D/w2ZQW1MSbNFJzXWettDUm87oszNtIBRbyqQZD1WmHjCc/DON7vx8+8sN152zjM +ZKsb/4K/3pG0JTvR7C0MM+5ZGUU/0j+JNUavF5gEuQKBgQDNfi2lP0YZ0oesA+Yu +I6c1TKJefXfLY2HMFTagEhI879hbcNvWKYL+/yIKf4GJCAvUTcmOzPCPGW90H7S8 +dTanmD2xW2yf+8ZMf1mqdbOflhebPCQwZz2QY5ccBZ8oaa/uvKrBagoPPOm1YCAc +LJGWxRGx5cmKg212sC+DKW/IvQKBgQDJOKgcgT7CvRR2pFn1oAgraFsfarw5XQuC +4LvE1m6q/qzfv3hZeqPTEMlFy4SGX+veuxfrbdtB5VBSWiXKNkHu+BBVd1uTfEbD +tx/M3Lxb/wOZ0cADZh0kOBLxvLtim/1wfH85WtImUfGzVLdB329HtoEIE6TdTOoq +iO4bZul8awKBgC0voMPkfPqyo6i8lsHwjxUWS+HxPwVXTir9QyzBrIb/ypiY4Y5f +RHHkEk0yqn5Caa9+h2LCR+d/lVV4n1qNf74sqOw2CVXInFs36bSk+yGNdJVrDR4j +pZL5g0HjLpNJYiliDT5Infupzk5W29i2KDF6FiEDQWUW71wY8+mok+8VAoGAa2m7 +E7xKbFnSmqKRAvUyZzmFqvenElgA1RRyJ1jwKodYcPgcnmdBHGJRjthdHf4GQxdM +ZXh3Gm32un80vQTJnW7+CSF12Pz2KXOPniQWyGUQ3wOApE/WLodgVXqR7MmoOGu8 +3jkFBT+o7jnCuX80P+vEZTNXRmrQdXQy5p3A9ZECgYEAiMTdP/GEGhD4LSfkmOh/ +jjL2llf68YgLyRvTCFh+iFWbpb3fVd2L9dUt1lQpgliO7vPhw5AYxLoEx3kne8BB +zGoTWMuGboeBgGS/1iuPLMvNfJum2CHXsofr9ICO0tLHrDUw94mY25TmxlZeB0hc +UPPZOQ5AsQX82MTBSnkdHQI= +-----END PRIVATE KEY----- \ No newline at end of file From 8a255c798e99f389547a8705ae0cc5fba3a4e569 Mon Sep 17 00:00:00 2001 From: Priya Date: Mon, 9 Dec 2024 15:36:09 +0530 Subject: [PATCH 2/6] [JENKINS-74964] Updating test cases --- .../impl/CertificateCredentialsImpl.java | 2 +- .../CertificateCredentialsImplFIPSTest.java | 33 +++++++++++-------- .../impl/{FIPSCerts.pem => validCerts.pem} | 0 .../impl/{FIPSKey.pem => validKey.pem} | 0 4 files changed, 20 insertions(+), 15 deletions(-) rename src/test/resources/com/cloudbees/plugins/credentials/impl/{FIPSCerts.pem => validCerts.pem} (100%) rename src/test/resources/com/cloudbees/plugins/credentials/impl/{FIPSKey.pem => validKey.pem} (100%) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index b3b8c29b..6237519e 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -132,7 +132,7 @@ public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, @NonNull KeyStoreSource keyStoreSource) throws Descriptor.FormException { super(scope, id, description); Objects.requireNonNull(keyStoreSource); - if (FIPS140.useCompliantAlgorithms() && StringUtils.isNotBlank(password) && password.length() < 14) { + if (FIPS140.useCompliantAlgorithms() && StringUtils.length(password) < 14) { throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_ShortPasswordFIPS(), "password"); } this.password = Secret.fromString(password); diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java index 6cb9f0c4..1a522bbd 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java @@ -29,14 +29,16 @@ public class CertificateCredentialsImplFIPSTest { @Rule public TemporaryFolder tmp = new TemporaryFolder(); - String pemCert; - String pemKey; + private String pemCert; + private String pemKey; + private static final String VALID_PASSWORD = "passwordFipsCheck"; + private static final String INVALID_PASSWORD = "foo"; @Before public void setup() throws IOException { - pemCert = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("FIPSCerts.pem"), + pemCert = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("validCerts.pem"), StandardCharsets.UTF_8); - pemKey = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("FIPSKey.pem"), + pemKey = IOUtils.toString(CertificateCredentialsImplFIPSTest.class.getResource("validKey.pem"), StandardCharsets.UTF_8); } @@ -45,9 +47,9 @@ public void doCheckPasswordTest() throws Throwable { rule.then(r -> { CertificateCredentialsImpl.DescriptorImpl descriptor = ExtensionList.lookupSingleton( CertificateCredentialsImpl.DescriptorImpl.class); - FormValidation result = descriptor.doCheckPassword("passwordFipsCheck"); + FormValidation result = descriptor.doCheckPassword(VALID_PASSWORD); assertThat(result.kind, is(FormValidation.Kind.OK)); - result = descriptor.doCheckPassword("foo"); + result = descriptor.doCheckPassword(INVALID_PASSWORD); assertThat(result.kind, is(FormValidation.Kind.ERROR)); assertThat(result.getMessage(), is(StringEscapeUtils.escapeHtml4(Messages.CertificateCredentialsImpl_ShortPasswordFIPS()))); @@ -55,19 +57,22 @@ public void doCheckPasswordTest() throws Throwable { } @Test - public void invalidPEMKeyStoreTest() throws Throwable { + public void invalidPEMKeyStoreAndPasswordTest() throws Throwable { CertificateCredentialsImpl.PEMEntryKeyStoreSource storeSource = new CertificateCredentialsImpl.PEMEntryKeyStoreSource( pemCert, pemKey); rule.then(r -> { - new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", - "Validate the certificate credentials", "passwordFipsCheck", storeSource); - + new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "valid-certificate-and-password-validation", + "Validate the certificate credentials", VALID_PASSWORD, storeSource); + assertThrows(Descriptor.FormException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "password-length-validation", + "Validate the certificate password", "", storeSource)); assertThrows(Descriptor.FormException.class, - () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", - "Validate the certificate password", "foo", storeSource)); + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "password-length-validation", + "Validate the certificate password", INVALID_PASSWORD, + storeSource)); assertThrows(Descriptor.FormException.class, - () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "certificate-validation", - "Validate the certificate keyStore", "passwordFipsCheck", + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "invalid-certificate-validation", + "Validate the certificate keyStore", VALID_PASSWORD, new CertificateCredentialsImpl.PEMEntryKeyStoreSource( null, null))); }); diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem similarity index 100% rename from src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSCerts.pem rename to src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem similarity index 100% rename from src/test/resources/com/cloudbees/plugins/credentials/impl/FIPSKey.pem rename to src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem From cfe1584d9cea6e34cfb9863d1443cc948c8bb14e Mon Sep 17 00:00:00 2001 From: Priya Date: Wed, 11 Dec 2024 11:24:28 +0530 Subject: [PATCH 3/6] [JENKINS-74964] Updating logger message and test cases --- .../impl/CertificateCredentialsImpl.java | 2 +- .../CertificateCredentialsImplFIPSTest.java | 4 +- .../plugins/credentials/impl/validCerts.pem | 37 ++++++++++--------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 6237519e..296da3af 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -142,7 +142,7 @@ public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, try { keyStoreSource.toKeyStore(toCharArray(this.password)); } catch (GeneralSecurityException | IOException e) { - LOGGER.log(Level.WARNING, Messages.CertificateCredentialsImpl_InvalidKeystore(), e); + LOGGER.log(Level.WARNING, "Failed to create CertificateCredentials", e); throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_InvalidKeystore(), e, "keyStoreSource"); } diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java index 1a522bbd..f2a7ede9 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java @@ -24,8 +24,8 @@ public class CertificateCredentialsImplFIPSTest { @Rule - public RealJenkinsRule rule = new RealJenkinsRule().javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true"); - + public RealJenkinsRule rule = new RealJenkinsRule().withFIPSEnabled().javaOptions("-Xmx512m"); + @Rule public TemporaryFolder tmp = new TemporaryFolder(); diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem index 7c15c314..06615750 100644 --- a/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem +++ b/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem @@ -1,20 +1,21 @@ -----BEGIN CERTIFICATE----- -MIIDTzCCAjegAwIBAgIUWcaB9PB40lCu4um/CD8Ni6yNxkwwDQYJKoZIhvcNAQEL +MIIDgzCCAmugAwIBAgIUdl3Su0vyakVGHSTiFvIh+nYzq0UwDQYJKoZIhvcNAQEL BQAwUDELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlROMREwDwYDVQQHDAhUaXJ1cHB1 -cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI0MTEyODE2 -MzI1OFoXDTI1MTEyODE2MzI1OFowUDELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlRO -MREwDwYDVQQHDAhUaXJ1cHB1cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8dgK5e2+wy3 -2uRxF8hiaSFnUjfKOVDYrj+RbZ+ss9Kzxm2T3nfGcjGKHVHTvkaoduVdF95V8RtQ -I2lbwPnb/RKY6+Rlx+74e6Aw+KvCAEFa0lt0dST7IpErEetDppkkR5N/RbVXvon8 -sbJSHRZQOazFD2fG6MJJYiHqLC9xqfKOrZ07JQ9/J/mXN05mTG3gWXIlaJxf6hv3 -+n2NfkaTioor9dHGwr03S9D4CJWbJvk2006WCh9TEKCrG+oOAU/Fm+mlWndeResv -EWHiH13BMOsqWj/q9QlMYnmRl4MN00SSKKREQAUAAd6WbXg6+iavxGpN8eJw4O72 -wti2bS6I4wIDAQABoyEwHzAdBgNVHQ4EFgQUsACQZeota/vgn2vOY0EVIMoKM7Iw -DQYJKoZIhvcNAQELBQADggEBAEZ7qOLFPL4QJYcD3DYohFyOR2QeQQ5sceLlCg+/ -vtET3TPqBGswHER4fkOUgehud9i/h6ff33KCgTGMpGCWkYISaAbCQgGeD8AJQIOX -aWQ12Ux/E98a4Bk9nid0moiXKTXzMBule44JolGWroxayrJnYDCo39IraJO6XxlK -SJnI1dA1uRuJl6XKHr1N/knH3QBE4wI3CjqHc9qjTXVprhRosmTykvIeLaNL/ZxW -/QnKD5VRWRmHFom03COkmPSyGWNp225dLgN+rv9e2Pvn0AP8CeUJAoWSXwxGpv9Y -AgIsGfIhwQIt3AsmmujlaLvjRQpxBh00ba77kteFR4S3WMk= ------END CERTIFICATE----- \ No newline at end of file +cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMCAXDTI0MTIxMDE1 +NTY0M1oYDzIxMjQxMTE2MTU1NjQzWjBQMQswCQYDVQQGEwJJTjELMAkGA1UECAwC +VE4xETAPBgNVBAcMCFRpcnVwcHVyMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz +IFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChhYxialRH +FZdEY1d6A0zTCi6pMnQlk/DU2xt2qV6xrI/Jkz0xIuDjCA/vD4EyEEh7aPEY5Oox +z6zztmrhF8nG2Nis4UBAxtTHqV2D5eLPfxtrRMccrIYh6121K0B45vqcHpBq/nFM +NGyKq1WJBmb4JTZ4MsZXcpe7rGBhApwgUdP/ey6jwf7oG2YCBOywZ7hu0Vs6yeSb +F84RuTuVBhVdNm2BJHMy7P2BxyDpm5RSbHQGYox68pn3kfVEdv/iC/8qwq7sbyzb +KsgvYJrpqAunTEkgLBkG/NtsSFlTzssEAtSXErfI+6b8Zd4UK6E9CoclKWyzEqMT +9mVchox4+XL/AgMBAAGjUzBRMB0GA1UdDgQWBBRcz3Oqr5FVbBP7nu5k5Pa5x1wa +VzAfBgNVHSMEGDAWgBRcz3Oqr5FVbBP7nu5k5Pa5x1waVzAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB0WeoppwXfGAJwhdItbgt+M11+Px2Q6NOa +/wDVwxw59TJ7fzQdMSgdaASsxPUV6nd5itvfaAQ7dnbMst4os/GZQKLXhLr+2J7e +2/yqW9RmRoWTL9zvRc+AG4Tv8Z2/iUDSYpGCJ1CWtUJXGVPNjKq9uXoU5sMyyfJH +pGZTM1UDxd58KFVg8gszTzyUJjESjLG6KlrBDARAsRHgGPY7QJSEWYdC8pjXoj1p +7XUnjWrSmEm8xon19SCvH26yB+ByG2T0gMY4de8bECu3lbzwphA0J6mEkoNNZpQn +zRUwOldMPOuSIedVN5nSwE4TlkmzWvLsP+2JxegtEKB+jqYg9dsq +-----END CERTIFICATE----- From 0df09713dedd50b5236c4a6dc352108265b38161 Mon Sep 17 00:00:00 2001 From: Priya Date: Fri, 13 Dec 2024 13:44:56 +0530 Subject: [PATCH 4/6] [JENKINS-74964] Descriptor#newInstanceImpl Linkage error handling in doAddCredentials --- .../credentials/CredentialsSelectHelper.java | 31 +++++++++- .../impl/CertificateCredentialsImpl.java | 8 +-- .../credentials/impl/Messages.properties | 1 - .../CredentialsSelectHelperTest.java | 2 - .../CertificateCredentialsImplFIPSTest.java | 28 +++++---- .../impl/CertificateCredentialsImplTest.java | 3 +- .../plugins/credentials/impl/validCerts.pem | 35 ++++++----- .../plugins/credentials/impl/validKey.pem | 58 ++++++++++--------- 8 files changed, 97 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java index ddbeb8d0..2ab53974 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java @@ -38,6 +38,8 @@ import hudson.model.User; import hudson.security.AccessControlled; import hudson.security.Permission; +import hudson.util.HttpResponses; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -47,17 +49,20 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + import jakarta.servlet.ServletException; import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.jenkins.ui.icon.IconSpec; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.Localizable; -import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.StaplerResponse2; @@ -77,6 +82,8 @@ public class CredentialsSelectHelper extends Descriptor */ public static final Permission CREATE = CredentialsProvider.CREATE; + private static final Logger LOGGER = Logger.getLogger(CredentialsSelectHelper.class.getName()); + /** * {@inheritDoc} */ @@ -614,7 +621,27 @@ public JSONObject doAddCredentials(StaplerRequest2 req, StaplerResponse2 rsp) th Credentials credentials = Descriptor.bindJSON(req, Credentials.class, data.getJSONObject("credentials")); credentialsWereAdded = store.addCredentials(wrapper.getDomain(), credentials); - } catch (HttpResponses.HttpResponseException e) { + } catch (LinkageError e) { + /* + * Descriptor#newInstanceImpl throws a LinkageError if the constructor throws any exception other + * than HTTP response exceptions such as Descriptor.FormException. + * + * This approach is taken to maintain backward compatibility, as throwing a FormException directly + * from the constructor would result in a source-incompatible change, potentially breaking dependent plugins. + * + * Here, known exceptions are caught specifically to provide meaningful error response. + */ + Throwable rootCause = ExceptionUtils.getRootCause(e); + if (rootCause instanceof IOException || rootCause instanceof IllegalArgumentException + || rootCause instanceof IllegalStateException) { + LOGGER.log(Level.WARNING, "Failed to create Credentials", e); + return new JSONObject().element("message", rootCause.getMessage()).element("notificationType", + "ERROR"); + } + throw e; + } catch (IOException | IllegalArgumentException | IllegalStateException | + HttpResponses.HttpResponseException e) { + LOGGER.log(Level.WARNING, "Failed to create Credentials", e); return new JSONObject().element("message", e.getMessage()).element("notificationType", "ERROR"); } if (credentialsWereAdded) { diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 296da3af..ebdf53c2 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -129,11 +129,11 @@ public class CertificateCredentialsImpl extends BaseStandardCredentials implemen public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, @CheckForNull String id, @CheckForNull String description, @CheckForNull String password, - @NonNull KeyStoreSource keyStoreSource) throws Descriptor.FormException { + @NonNull KeyStoreSource keyStoreSource) { super(scope, id, description); Objects.requireNonNull(keyStoreSource); if (FIPS140.useCompliantAlgorithms() && StringUtils.length(password) < 14) { - throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_ShortPasswordFIPS(), "password"); + throw new IllegalArgumentException(Messages.CertificateCredentialsImpl_ShortPasswordFIPS()); } this.password = Secret.fromString(password); this.keyStoreSource = keyStoreSource; @@ -142,9 +142,7 @@ public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, try { keyStoreSource.toKeyStore(toCharArray(this.password)); } catch (GeneralSecurityException | IOException e) { - LOGGER.log(Level.WARNING, "Failed to create CertificateCredentials", e); - throw new Descriptor.FormException(Messages.CertificateCredentialsImpl_InvalidKeystore(), e, - "keyStoreSource"); + throw new IllegalArgumentException("KeyStore is not valid.", e); } } diff --git a/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties b/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties index 92dc7953..90ab3277 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/impl/Messages.properties @@ -24,7 +24,6 @@ UsernamePasswordCredentialsImpl.DisplayName=Username with password CertificateCredentialsImpl.DisplayName=Certificate CertificateCredentialsImpl.EmptyKeystore=Empty keystore -CertificateCredentialsImpl.InvalidKeystore=KeyStore is not valid CertificateCredentialsImpl.LoadKeyFailed=Could retrieve key "{0}" CertificateCredentialsImpl.LoadKeyFailedQueryEmptyPassword=Could retrieve key "{0}". You may need to provide a password CertificateCredentialsImpl.LoadKeystoreFailed=Could not load keystore diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java index a4483302..83eacc6b 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java @@ -7,7 +7,6 @@ import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; -import com.cloudbees.plugins.credentials.impl.Messages; import hudson.model.UnprotectedRootAction; import java.io.IOException; import java.util.List; @@ -101,7 +100,6 @@ public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore() throws E wc.waitForBackgroundJavaScript(4000); Page submit = HtmlFormUtil.submit(form); JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); - assertThat(responseJson.getString("message"), is(Messages.CertificateCredentialsImpl_InvalidKeystore())); assertThat(responseJson.getString("notificationType"), is("ERROR")); } } diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java index f2a7ede9..2e64483d 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplFIPSTest.java @@ -12,7 +12,6 @@ import org.jvnet.hudson.test.RealJenkinsRule; import hudson.ExtensionList; -import hudson.model.Descriptor; import hudson.util.FormValidation; import com.cloudbees.plugins.credentials.CredentialsScope; @@ -25,14 +24,15 @@ public class CertificateCredentialsImplFIPSTest { @Rule public RealJenkinsRule rule = new RealJenkinsRule().withFIPSEnabled().javaOptions("-Xmx512m"); - + @Rule public TemporaryFolder tmp = new TemporaryFolder(); private String pemCert; private String pemKey; private static final String VALID_PASSWORD = "passwordFipsCheck"; - private static final String INVALID_PASSWORD = "foo"; + private static final String INVALID_PASSWORD = "invalidPasswordFipsCheck"; + private static final String SHORT_PASSWORD = "password"; @Before public void setup() throws IOException { @@ -49,7 +49,7 @@ public void doCheckPasswordTest() throws Throwable { CertificateCredentialsImpl.DescriptorImpl.class); FormValidation result = descriptor.doCheckPassword(VALID_PASSWORD); assertThat(result.kind, is(FormValidation.Kind.OK)); - result = descriptor.doCheckPassword(INVALID_PASSWORD); + result = descriptor.doCheckPassword(SHORT_PASSWORD); assertThat(result.kind, is(FormValidation.Kind.ERROR)); assertThat(result.getMessage(), is(StringEscapeUtils.escapeHtml4(Messages.CertificateCredentialsImpl_ShortPasswordFIPS()))); @@ -63,16 +63,22 @@ public void invalidPEMKeyStoreAndPasswordTest() throws Throwable { rule.then(r -> { new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "valid-certificate-and-password-validation", "Validate the certificate credentials", VALID_PASSWORD, storeSource); - assertThrows(Descriptor.FormException.class, - () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "password-length-validation", - "Validate the certificate password", "", storeSource)); - assertThrows(Descriptor.FormException.class, + assertThrows(IllegalArgumentException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "empty-password-validation", + "Validate the certificate empty password", "", + storeSource)); + assertThrows(IllegalArgumentException.class, () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "password-length-validation", + "Validate the certificate password length", + SHORT_PASSWORD, storeSource)); + assertThrows(IllegalArgumentException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "invalid-password-validation", "Validate the certificate password", INVALID_PASSWORD, storeSource)); - assertThrows(Descriptor.FormException.class, - () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "invalid-certificate-validation", - "Validate the certificate keyStore", VALID_PASSWORD, + assertThrows(IllegalArgumentException.class, + () -> new CertificateCredentialsImpl(CredentialsScope.GLOBAL, "empty-keystore-validation", + "Validate the invalid certificate keyStore", + VALID_PASSWORD, new CertificateCredentialsImpl.PEMEntryKeyStoreSource( null, null))); }); diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 182d3697..96ff209a 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -47,7 +47,6 @@ import org.htmlunit.html.HtmlRadioButtonInput; import hudson.Util; -import hudson.model.Descriptor; import hudson.model.ItemGroup; import hudson.security.ACL; import hudson.util.Secret; @@ -107,7 +106,7 @@ public void setup() throws IOException { } @Test - public void displayName() throws IOException, Descriptor.FormException { + public void displayName() throws IOException { SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore); assertEquals(EXPECTED_DISPLAY_NAME, CredentialsNameProvider.name(new CertificateCredentialsImpl(null, "abc123", null, "password", storeSource))); diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem index 06615750..b5afc800 100644 --- a/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem +++ b/src/test/resources/com/cloudbees/plugins/credentials/impl/validCerts.pem @@ -1,21 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIUdl3Su0vyakVGHSTiFvIh+nYzq0UwDQYJKoZIhvcNAQEL +MIIDUTCCAjmgAwIBAgIUM2p34T12bOzrnyga5GarEMbBI3QwDQYJKoZIhvcNAQEL BQAwUDELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlROMREwDwYDVQQHDAhUaXJ1cHB1 -cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMCAXDTI0MTIxMDE1 -NTY0M1oYDzIxMjQxMTE2MTU1NjQzWjBQMQswCQYDVQQGEwJJTjELMAkGA1UECAwC +cjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMCAXDTI0MTIxMjA5 +NTgzM1oYDzIxMjQxMTE4MDk1ODMzWjBQMQswCQYDVQQGEwJJTjELMAkGA1UECAwC VE4xETAPBgNVBAcMCFRpcnVwcHVyMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz -IFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChhYxialRH -FZdEY1d6A0zTCi6pMnQlk/DU2xt2qV6xrI/Jkz0xIuDjCA/vD4EyEEh7aPEY5Oox -z6zztmrhF8nG2Nis4UBAxtTHqV2D5eLPfxtrRMccrIYh6121K0B45vqcHpBq/nFM -NGyKq1WJBmb4JTZ4MsZXcpe7rGBhApwgUdP/ey6jwf7oG2YCBOywZ7hu0Vs6yeSb -F84RuTuVBhVdNm2BJHMy7P2BxyDpm5RSbHQGYox68pn3kfVEdv/iC/8qwq7sbyzb -KsgvYJrpqAunTEkgLBkG/NtsSFlTzssEAtSXErfI+6b8Zd4UK6E9CoclKWyzEqMT -9mVchox4+XL/AgMBAAGjUzBRMB0GA1UdDgQWBBRcz3Oqr5FVbBP7nu5k5Pa5x1wa -VzAfBgNVHSMEGDAWgBRcz3Oqr5FVbBP7nu5k5Pa5x1waVzAPBgNVHRMBAf8EBTAD -AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB0WeoppwXfGAJwhdItbgt+M11+Px2Q6NOa -/wDVwxw59TJ7fzQdMSgdaASsxPUV6nd5itvfaAQ7dnbMst4os/GZQKLXhLr+2J7e -2/yqW9RmRoWTL9zvRc+AG4Tv8Z2/iUDSYpGCJ1CWtUJXGVPNjKq9uXoU5sMyyfJH -pGZTM1UDxd58KFVg8gszTzyUJjESjLG6KlrBDARAsRHgGPY7QJSEWYdC8pjXoj1p -7XUnjWrSmEm8xon19SCvH26yB+ByG2T0gMY4de8bECu3lbzwphA0J6mEkoNNZpQn -zRUwOldMPOuSIedVN5nSwE4TlkmzWvLsP+2JxegtEKB+jqYg9dsq ------END CERTIFICATE----- +IFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoI5pI11oR +2nIvBMdViNN9RpztDYZuJ/tDl5JZz3jFHxwUFuneABMIqiySUFS3HK1Kfsz6Lvbz +qZlLriE1cKKk84GBOlkpISaizAMUyYOkZagmVrsf/QrneLQdYjNZFpOjTCduWmsC +3WLXWGA+t2qlT3jmJr+J5yyHBGme27dtab7UgoSdD2o0FFxXCG15NMkWMi+m+3zW +UK9f4wmaY1wIBd3/umsHM1dv+vwqAfgMMp/I4ISTda2sm3txmDzYGBPzvc/bnsCe +zyahE85Fi/otkqxqzDww2a7YhGY0hjcyihpqYffFw7zWo2syylLcFMFJN1nRoQyj +lshfAeLcGYLHAgMBAAGjITAfMB0GA1UdDgQWBBSSmy62eRSGGzkJnFWrcJDoU3Za +4jANBgkqhkiG9w0BAQsFAAOCAQEAW1YhiizwHdxIqf336vqk0Qn8omcg3v/GotjF +hNqFHiCnr2Y1/+7NwqsvpIpjb354atU5nxJ1X1LkIJz/yPztbVbHzqKn/RUztSV6 ++wCOArAZzTfXcgOf/jgqPRQMJxhtUeU01MKcoliBLzCIdYvNe3XdkjMkPUHxK5vt +Yv5hxx+sfZ1T68xCFCgdkXtJKxkBDj9tR56vPOTN7kxpVPKFQzsI644xxID+JRbi +RFwV8GYK8rF6nVw9EQAfi+PuJj+V8DecUshMy6d7heUKWdu5i/+HgVKx46/RZMkj +gwAW4FDt8+qsdvgxYzCTkrkxbREwAtTv6xgPO2BAPZsECG/DSg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem b/src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem index 94b70bdd..99373ed3 100644 --- a/src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem +++ b/src/test/resources/com/cloudbees/plugins/credentials/impl/validKey.pem @@ -1,28 +1,30 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChhYxialRHFZdE -Y1d6A0zTCi6pMnQlk/DU2xt2qV6xrI/Jkz0xIuDjCA/vD4EyEEh7aPEY5Ooxz6zz -tmrhF8nG2Nis4UBAxtTHqV2D5eLPfxtrRMccrIYh6121K0B45vqcHpBq/nFMNGyK -q1WJBmb4JTZ4MsZXcpe7rGBhApwgUdP/ey6jwf7oG2YCBOywZ7hu0Vs6yeSbF84R -uTuVBhVdNm2BJHMy7P2BxyDpm5RSbHQGYox68pn3kfVEdv/iC/8qwq7sbyzbKsgv -YJrpqAunTEkgLBkG/NtsSFlTzssEAtSXErfI+6b8Zd4UK6E9CoclKWyzEqMT9mVc -hox4+XL/AgMBAAECggEANYYAqBgeB1QzRRk6QpdXXNOR9MVgUZd9hbt5lU+4rl3F -ZAGjlGW/adwhE5HquQFGU4bJ5frtVEZCRJxdPGvalEcFPfyCgzSgC+2mrG+AQkwX -dOtco7bT1+ebrM5BVg8MWrGSH7JjLuJsWWM/O+HgOzhxnVEOAqpZd3o+kccAn4CX -XjxbIc3/VJ4PXm7kOfU4NL7tJ1G81UQMTNMODisvi++yAa+Rw30NpnQ15GcdnWfF -D/w2ZQW1MSbNFJzXWettDUm87oszNtIBRbyqQZD1WmHjCc/DON7vx8+8sN152zjM -ZKsb/4K/3pG0JTvR7C0MM+5ZGUU/0j+JNUavF5gEuQKBgQDNfi2lP0YZ0oesA+Yu -I6c1TKJefXfLY2HMFTagEhI879hbcNvWKYL+/yIKf4GJCAvUTcmOzPCPGW90H7S8 -dTanmD2xW2yf+8ZMf1mqdbOflhebPCQwZz2QY5ccBZ8oaa/uvKrBagoPPOm1YCAc -LJGWxRGx5cmKg212sC+DKW/IvQKBgQDJOKgcgT7CvRR2pFn1oAgraFsfarw5XQuC -4LvE1m6q/qzfv3hZeqPTEMlFy4SGX+veuxfrbdtB5VBSWiXKNkHu+BBVd1uTfEbD -tx/M3Lxb/wOZ0cADZh0kOBLxvLtim/1wfH85WtImUfGzVLdB329HtoEIE6TdTOoq -iO4bZul8awKBgC0voMPkfPqyo6i8lsHwjxUWS+HxPwVXTir9QyzBrIb/ypiY4Y5f -RHHkEk0yqn5Caa9+h2LCR+d/lVV4n1qNf74sqOw2CVXInFs36bSk+yGNdJVrDR4j -pZL5g0HjLpNJYiliDT5Infupzk5W29i2KDF6FiEDQWUW71wY8+mok+8VAoGAa2m7 -E7xKbFnSmqKRAvUyZzmFqvenElgA1RRyJ1jwKodYcPgcnmdBHGJRjthdHf4GQxdM -ZXh3Gm32un80vQTJnW7+CSF12Pz2KXOPniQWyGUQ3wOApE/WLodgVXqR7MmoOGu8 -3jkFBT+o7jnCuX80P+vEZTNXRmrQdXQy5p3A9ZECgYEAiMTdP/GEGhD4LSfkmOh/ -jjL2llf68YgLyRvTCFh+iFWbpb3fVd2L9dUt1lQpgliO7vPhw5AYxLoEx3kne8BB -zGoTWMuGboeBgGS/1iuPLMvNfJum2CHXsofr9ICO0tLHrDUw94mY25TmxlZeB0hc -UPPZOQ5AsQX82MTBSnkdHQI= ------END PRIVATE KEY----- \ No newline at end of file +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQHYC8RH2eVm05YRcn +VVJVYgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEMXZ/JFe75dATa+k +FvUVFzYEggTQsScTfFvsV/4Qc4wT/U+iEidlUm5hrVYEsKLBsa+Bk11CAK7Lsak+ +gNxnYhvrs+xUll8YkFNUjxwaWGHjgrKNM+dZwO1JqKTy2VYK4LRHcY7oM6PJJszl +DMHWDLVZxMmRS0U9hpwdKA3Xt4lC5hOinlVsWR5geYcmeGjUE+SyydsD7Bypk01g +KSVhTqq1eGJq1s+nmVRlWvFLtw2ftLfH49nhhUvlpFtLO34iDjxs9MX93WJCvvqC +OzbjdoBxjaHI4a3TypjZWGTS/z4itSzRjOV7zn0LH0t30Bp0LUjS0Lxtug+i+kIo +4nFl8RM6SMQLIgYmOT4Y7bcyTq2ZkIGju1Z3I0PEsaIkE2EkIR8E8kvvsG6fXBvH +ahS2Zbga+oOuYAD7VSspgIvfIYkBriEoN3RU9QfOsk/If6JY1vHjkUQU+Yv60/ia +OajDnhOa1cks30P2DYOPBP+lpbMsmg5iUU5QEMF2oEH9afZjpbs2ZvXwo9Gh+qgQ +G7kZ2+v++AZgX5XLjXdIlpo+iXb9VQL9he0nEcbm2yCFPliwHd7S1N1pnZsyyI6U +uVE2cqFv44d/kxB67wYft6yVPsExgvEMlHDIRAMyk6AuaKmDoIYpCGaiCQnQCqBG +xgckie+vjD5zA4q5Ttdzg9inizBEwQgSDHxOh9nuZYjobDfjiDQ00A4NZVNJn/6z +JoUa0eaKEepwW0a0NflKSO98llADaJyE4jqaWAimImvp1frO/OldlqLgFXHSrhZg +l9te4Huj/NwnXAC60hq3OK9H1fEfUiFf+PYBUiLioGyJ9Yd3m5NQAxFDjPi2oBi/ ++hdn1x4e+5rOmO6Y6BRec8v+ofLsnfRgLMnuyFi4uo6jbyqN1JC5z05LPsQcCPYX +lv9lD4LazeJobzOzTQGJretFjuPhGeHx+PzbulC7S3sVq7/ddlFLEmmiqFUzynBo +gpxEpPQMgB5fdVrUOsTKThsVveIQw4wVk3+uvl4AiO0yWaP9tD7Iwf2WP8SzpViU +lnb1f6bQeWtk0FxywHcdFonFIwB0qzOoO38W1tqqXBiYTcJre+bBOrprpLisxyZC +IscEJXYHjicft1PCD71Tau5muN/RB1iTXmURKYoybQ23NFlLEunD8q5kQjYAhgIx +57pcrp6dMc3qinyrSW6EK8myUywlozgEHob+azspIA+HDHz5sUNjQ0+oSBJ8VbAy +s9fo0e9ck8skPQNntgrqzSpF+qh0DXiAKKrHads0qloFcXzQ7znn4X7fKjGfE8bu +jFOd8vo9v3aZ7palgOJzYVh1IVfGGRuZXmmJR1e6qay/ZxBVNd6Kg83xRw4/9117 +ByyTdYZaVsKOhgcqLqfoELyxf5lSg3ep+4x7jL5iQHf4u3u5gnjo6bd84a7W9NkG +z2mUH079NCmIHUNPPafHF+SnNOqf93VkltjafutaDhIJ7eZ4rJd6Cuhrqy7bKB4H +P/2VVcRfIV65wY5KXFYEIDprKdHrRzvMhswY5KggQ2dGGpiBOIweruqKyBd6BFDf +aQitGNUqDsw1CpulRxSAl7V+eVBPxfzzXp/JHADMAZ0qfuqNL//DaLNyElh3ozA/ +RbCFEn/Lei1Poh9Eu4qiMJaTmpp8oKe3GqpzIvLrMZ+vXK7HOCpPrL4= +-----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file From fe5d2664991f63112b1263ad2aa8a349d774d4f1 Mon Sep 17 00:00:00 2001 From: Priya Date: Fri, 13 Dec 2024 18:17:46 +0530 Subject: [PATCH 5/6] [JENKINS-74964] Descriptor#newInstanceImpl Linkage error handling test cases --- .../credentials/CredentialsSelectHelper.java | 10 +- .../lib/credentials/select/select.js | 2 +- .../CredentialsSelectHelperTest.java | 129 +++++++++++++++--- 3 files changed, 112 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java index 2ab53974..978d50ae 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java @@ -38,9 +38,9 @@ import hudson.model.User; import hudson.security.AccessControlled; import hudson.security.Permission; -import hudson.util.HttpResponses; import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -633,17 +633,13 @@ public JSONObject doAddCredentials(StaplerRequest2 req, StaplerResponse2 rsp) th */ Throwable rootCause = ExceptionUtils.getRootCause(e); if (rootCause instanceof IOException || rootCause instanceof IllegalArgumentException - || rootCause instanceof IllegalStateException) { + || rootCause instanceof GeneralSecurityException) { LOGGER.log(Level.WARNING, "Failed to create Credentials", e); return new JSONObject().element("message", rootCause.getMessage()).element("notificationType", "ERROR"); } throw e; - } catch (IOException | IllegalArgumentException | IllegalStateException | - HttpResponses.HttpResponseException e) { - LOGGER.log(Level.WARNING, "Failed to create Credentials", e); - return new JSONObject().element("message", e.getMessage()).element("notificationType", "ERROR"); - } + } if (credentialsWereAdded) { return new JSONObject() .element("message", "Credentials created") diff --git a/src/main/resources/lib/credentials/select/select.js b/src/main/resources/lib/credentials/select/select.js index 932f19e7..f1ef36a6 100644 --- a/src/main/resources/lib/credentials/select/select.js +++ b/src/main/resources/lib/credentials/select/select.js @@ -123,7 +123,7 @@ window.credentials.addSubmit = function (_) { .catch((e) => { // notificationBar.show(...) with logging ID could be handy here? console.error("Could not add credentials:", e); - window.notificationBar.show("Credentials creation failed.", window.notificationBar["ERROR"]); + window.notificationBar.show("Credentials creation failed", window.notificationBar["ERROR"]); }) } }; diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java index 83eacc6b..3d8c7ec0 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsSelectHelperTest.java @@ -7,10 +7,14 @@ import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImplTest; + import hudson.model.UnprotectedRootAction; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.List; import net.sf.json.JSONObject; +import org.apache.commons.io.IOUtils; import org.hamcrest.Matchers; import org.htmlunit.Page; import org.htmlunit.html.DomNode; @@ -23,6 +27,7 @@ import org.htmlunit.html.HtmlOption; import org.htmlunit.html.HtmlPage; import org.htmlunit.html.HtmlRadioButtonInput; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -33,6 +38,21 @@ public class CredentialsSelectHelperTest { @Rule public JenkinsRule j = new JenkinsRule(); + private String pemCert; + private String pemKey; + + private static final String VALID_PASSWORD = "password"; + private static final String INVALID_PASSWORD = "bla"; + + @Before + public void setup() throws IOException { + pemCert = IOUtils.toString(CertificateCredentialsImplTest.class.getResource("certs.pem"), + StandardCharsets.UTF_8); + pemKey = IOUtils.toString(CertificateCredentialsImplTest.class.getResource("key.pem"), + StandardCharsets.UTF_8); + } + + @Test public void doAddCredentialsFromPopupWorksAsExpected() throws Exception { try (JenkinsRule.WebClient wc = j.createWebClient()) { @@ -71,39 +91,106 @@ public void doAddCredentialsFromPopupWorksAsExpected() throws Exception { @Test @Issue("JENKINS-74964") - public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore() throws Exception { + public void doAddCredentialsFromPopupForPEMCertificateKeystore() throws Exception { try (JenkinsRule.WebClient wc = j.createWebClient()) { HtmlPage htmlPage = wc.goTo("credentials-selection"); + HtmlForm form = selectPEMCertificateKeyStore(htmlPage, wc); + form.getTextAreaByName("_.certChain").setTextContent(pemCert); + form.getTextAreaByName("_.privateKey").setTextContent(pemKey); + form.getInputsByName("_.password").forEach(input -> input.setValue(VALID_PASSWORD)); + Page submit = HtmlFormUtil.submit(form); + JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); + assertThat(responseJson.getString("notificationType"), is("SUCCESS")); + } + } - HtmlButton addCredentialsButton = htmlPage.querySelector(".credentials-add-menu"); - addCredentialsButton.fireEvent("mouseenter"); - addCredentialsButton.click(); + @Test + @Issue("JENKINS-74964") + public void doAddCredentialsFromPopupForPEMCertificateKeystore_missingKeyStore() throws Exception { - HtmlButton jenkinsCredentialsOption = htmlPage.querySelector(".jenkins-dropdown__item"); - jenkinsCredentialsOption.click(); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage htmlPage = wc.goTo("credentials-selection"); + HtmlForm form = selectPEMCertificateKeyStore(htmlPage, wc); + Page submit = HtmlFormUtil.submit(form); + JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); + assertThat(responseJson.getString("notificationType"), is("ERROR")); + } + } - wc.waitForBackgroundJavaScript(4000); - HtmlForm form = htmlPage.querySelector("#credentials-dialog-form"); - String certificateDisplayName = j.jenkins.getDescriptor(CertificateCredentialsImpl.class).getDisplayName(); - String KeyStoreSourceDisplayName = j.jenkins.getDescriptor( - CertificateCredentialsImpl.PEMEntryKeyStoreSource.class).getDisplayName(); - DomNodeList allOptions = htmlPage.getDocumentElement().querySelectorAll( - "select.dropdownList option"); - boolean optionFound = selectOption(allOptions, certificateDisplayName); - assertTrue("The Certificate option was not found in the credentials type select", optionFound); - List inputs = htmlPage.getDocumentElement().getByXPath( - "//input[contains(@name, 'keyStoreSource') and following-sibling::label[contains(.,'" - + KeyStoreSourceDisplayName + "')]]"); - assertThat("query should return only a singular input", inputs, hasSize(1)); - HtmlElementUtil.click(inputs.get(0)); - wc.waitForBackgroundJavaScript(4000); + @Test + @Issue("JENKINS-74964") + public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore_missingCert() throws Exception { + + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage htmlPage = wc.goTo("credentials-selection"); + HtmlForm form = selectPEMCertificateKeyStore(htmlPage, wc); + form.getTextAreaByName("_.certChain").setTextContent(null); + form.getTextAreaByName("_.privateKey").setTextContent(pemKey); + form.getInputsByName("_.password").forEach(input -> input.setValue(VALID_PASSWORD)); + Page submit = HtmlFormUtil.submit(form); + JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); + assertThat(responseJson.getString("notificationType"), is("ERROR")); + } + } + + @Test + @Issue("JENKINS-74964") + public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore_missingPassword() throws Exception { + + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage htmlPage = wc.goTo("credentials-selection"); + HtmlForm form = selectPEMCertificateKeyStore(htmlPage, wc); + form.getTextAreaByName("_.certChain").setTextContent(pemCert); + form.getTextAreaByName("_.privateKey").setTextContent(pemKey); Page submit = HtmlFormUtil.submit(form); JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); assertThat(responseJson.getString("notificationType"), is("ERROR")); } } + @Test + @Issue("JENKINS-74964") + public void doAddCredentialsFromPopupForInvalidPEMCertificateKeystore_invalidPassword() throws Exception { + + try (JenkinsRule.WebClient wc = j.createWebClient()) { + HtmlPage htmlPage = wc.goTo("credentials-selection"); + HtmlForm form = selectPEMCertificateKeyStore(htmlPage, wc); + form.getTextAreaByName("_.certChain").setTextContent(pemCert); + form.getTextAreaByName("_.privateKey").setTextContent(pemKey); + form.getInputsByName("_.password").forEach(input -> input.setValue(INVALID_PASSWORD)); + Page submit = HtmlFormUtil.submit(form); + JSONObject responseJson = JSONObject.fromObject(submit.getWebResponse().getContentAsString()); + assertThat(responseJson.getString("notificationType"), is("ERROR")); + } + } + + private HtmlForm selectPEMCertificateKeyStore(HtmlPage htmlPage, JenkinsRule.WebClient wc) throws IOException { + HtmlButton addCredentialsButton = htmlPage.querySelector(".credentials-add-menu"); + addCredentialsButton.fireEvent("mouseenter"); + addCredentialsButton.click(); + + HtmlButton jenkinsCredentialsOption = htmlPage.querySelector(".jenkins-dropdown__item"); + jenkinsCredentialsOption.click(); + + wc.waitForBackgroundJavaScript(4000); + HtmlForm form = htmlPage.querySelector("#credentials-dialog-form"); + String certificateDisplayName = j.jenkins.getDescriptor(CertificateCredentialsImpl.class).getDisplayName(); + String KeyStoreSourceDisplayName = j.jenkins.getDescriptor( + CertificateCredentialsImpl.PEMEntryKeyStoreSource.class).getDisplayName(); + DomNodeList allOptions = htmlPage.getDocumentElement().querySelectorAll( + "select.dropdownList option"); + boolean optionFound = selectOption(allOptions, certificateDisplayName); + assertTrue("The Certificate option was not found in the credentials type select", optionFound); + List inputs = htmlPage.getDocumentElement().getByXPath( + "//input[contains(@name, 'keyStoreSource') and following-sibling::label[contains(.,'" + + KeyStoreSourceDisplayName + "')]]"); + assertThat("query should return only a singular input", inputs, hasSize(1)); + HtmlElementUtil.click(inputs.get(0)); + wc.waitForBackgroundJavaScript(4000); + return form; + } + private static boolean selectOption(DomNodeList allOptions, String optionName) { return allOptions.stream().anyMatch(domNode -> { if (domNode instanceof HtmlOption option) { From 1f170860fdee909543f48c19913ec3738d5168e6 Mon Sep 17 00:00:00 2001 From: Priya Date: Fri, 20 Dec 2024 14:59:11 +0530 Subject: [PATCH 6/6] [JENKINS-74964] Updating the Linkage error handling comment --- .../plugins/credentials/CredentialsSelectHelper.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java index 978d50ae..04ddfe5b 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsSelectHelper.java @@ -38,7 +38,6 @@ import hudson.model.User; import hudson.security.AccessControlled; import hudson.security.Permission; - import java.io.IOException; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -623,8 +622,12 @@ public JSONObject doAddCredentials(StaplerRequest2 req, StaplerResponse2 rsp) th credentialsWereAdded = store.addCredentials(wrapper.getDomain(), credentials); } catch (LinkageError e) { /* - * Descriptor#newInstanceImpl throws a LinkageError if the constructor throws any exception other - * than HTTP response exceptions such as Descriptor.FormException. + * Descriptor#newInstanceImpl throws a LinkageError if the DataBoundConstructor or any DataBoundSetter + * throw any exception other than RuntimeException implementing HttpResponse. + * + * Checked exceptions implementing HttpResponse like FormException are wrapped and + * rethrown as HttpResponseException (a RuntimeException implementing HttpResponse) in + * RequestImpl#invokeConstructor. * * This approach is taken to maintain backward compatibility, as throwing a FormException directly * from the constructor would result in a source-incompatible change, potentially breaking dependent plugins.