package com.cloudbees.plugins.credentials;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.domains.DomainSpecification;
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
import com.gargoylesoftware.htmlunit.WebResponse;
import hudson.ExtensionList;
import hudson.Util;
import hudson.model.Items;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.xmlunit.matchers.CompareMatcher;
import static com.cloudbees.plugins.credentials.XmlMatchers.isSimilarToIgnoringPrivateAttrs;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;
public class CredentialsStoreActionTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private SystemCredentialsProvider.ProviderImpl system;
private CredentialsStore systemStore;
@Before
public void setUp() throws Exception {
system = ExtensionList.lookup(CredentialsProvider.class).get(SystemCredentialsProvider.ProviderImpl.class);
systemStore = system.getStore(j.getInstance());
}
@Test
public void smokes() throws Exception {
List<Domain> domainList = new ArrayList<Domain>(systemStore.getDomains());
domainList.remove(Domain.global());
for (Domain d : domainList) {
systemStore.removeDomain(d);
}
List<Credentials> credentialsList = new ArrayList<Credentials>(systemStore.getCredentials(Domain.global()));
for (Credentials c : credentialsList) {
systemStore.removeCredentials(Domain.global(), c);
}
JenkinsRule.WebClient wc = j.createWebClient();
WebResponse response = wc.goTo("credentials/store/system/api/xml?depth=5", "application/xml").getWebResponse();
assertThat(response.getContentAsString(), isSimilarToIgnoringPrivateAttrs("<userFacingAction>"
+ "<domains>"
+ "<_>"
+ "<description>"
+ "Credentials that should be available irrespective of domain specification to requirements "
+ "matching."
+ "</description>"
+ "<displayName>Global credentials (unrestricted)</displayName>"
+ "<fullDisplayName>System » Global credentials (unrestricted)</fullDisplayName>"
+ "<fullName>system/_</fullName>"
+ "<global>true</global>"
+ "<urlName>_</urlName>"
+ "</_>"
+ "</domains>"
+ "</userFacingAction>").ignoreWhitespace().ignoreComments());
Random entropy = new Random();
String domainName = "test" + entropy.nextInt();
String domainDescription = "test description " + entropy.nextLong();
String credentialId = "test-id-" + entropy.nextInt();
String credentialDescription = "test-account-" + entropy.nextInt();
String credentialUsername = "test-user-" + entropy.nextInt();
systemStore.addDomain(new Domain(domainName, domainDescription, Collections.<DomainSpecification>emptyList()),
new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, credentialId,
credentialDescription, credentialUsername, "test-secret"));
response = wc.goTo("credentials/store/system/api/xml?depth=5", "application/xml").getWebResponse();
assertThat(response.getContentAsString(), isSimilarToIgnoringPrivateAttrs("<userFacingAction>"
+ "<domains>"
+ "<_>"
+ "<description>"
+ "Credentials that should be available irrespective of domain specification to requirements "
+ "matching."
+ "</description>"
+ "<displayName>Global credentials (unrestricted)</displayName>"
+ "<fullDisplayName>System » Global credentials (unrestricted)</fullDisplayName>"
+ "<fullName>system/_</fullName>"
+ "<global>true</global>"
+ "<urlName>_</urlName>"
+ "</_>"
+ "<" + domainName + ">"
+ "<credential>"
+ "<description>" + credentialDescription + "</description>"
+ "<displayName>" + credentialUsername + "/****** (" + credentialDescription + ")</displayName>"
+ "<fullName>system/" + domainName + "/" + credentialId + "</fullName>"
+ "<id>" + credentialId + "</id>"
+ "<typeName>Username with password</typeName>"
+ "</credential>"
+ "<description>"
+ domainDescription
+ "</description>"
+ "<displayName>" + domainName + "</displayName>"
+ "<fullDisplayName>System » " + domainName + "</fullDisplayName>"
+ "<fullName>system/" + domainName + "</fullName>"
+ "<global>false</global>"
+ "<urlName>" + domainName + "</urlName>"
+ "</" + domainName + ">"
+ "</domains>"
+ "</userFacingAction>").ignoreComments().ignoreWhitespace());
}
@Test
public void restCRUDSmokes() throws Exception {
JenkinsRule.WebClient wc = j.createWebClient();
j.getInstance().setCrumbIssuer(null);
assertThat(systemStore.getDomainByName("smokes"), nullValue());
// create domain
HttpURLConnection con =
postCreateByXml(systemStore, "<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>");
assertThat(con.getResponseCode(), is(200));
assertThat(Items.XSTREAM2.toXML(systemStore.getDomainByName("smokes")), CompareMatcher.isIdenticalTo(
"<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>").ignoreWhitespace().ignoreComments());
// read domain
WebResponse response = wc.goTo("credentials/store/system/domain/smokes/config.xml", "application/xml").getWebResponse();
assertThat(response.getContentAsString(), CompareMatcher.isIdenticalTo(
"<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>").ignoreWhitespace().ignoreComments());
// update domain
con = postConfigDotXml(systemStore, "smokes", "<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "<specifications>\n"
+ "<com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "<includes>smokes.example.com</includes>\n"
+ "<excludes/>\n"
+ "</com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "</specifications>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>");
assertThat(con.getResponseCode(), is(200));
assertThat(Items.XSTREAM2.toXML(systemStore.getDomainByName("smokes")), CompareMatcher.isIdenticalTo(
"<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "<specifications>\n"
+ "<com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "<includes>smokes.example.com</includes>\n"
+ "<excludes/>\n"
+ "</com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "</specifications>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>").ignoreWhitespace().ignoreComments());
// create credential
con = postCreateByXml(systemStore, "smokes",
"<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n"
+ " <scope>GLOBAL</scope>\n"
+ " <id>smokey-id</id>\n"
+ " <description>created from xml</description>\n"
+ " <username>example-com-deployer</username>\n"
+ " <password>super-secret</password>\n"
+ "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>");
assertThat(con.getResponseCode(), is(200));
List<Credentials> credentials = systemStore.getCredentials(systemStore.getDomainByName("smokes"));
assertThat(credentials, notNullValue());
Credentials cred = credentials.isEmpty() ? null : credentials.get(0);
assertThat(cred, instanceOf(UsernamePasswordCredentialsImpl.class));
UsernamePasswordCredentialsImpl c = (UsernamePasswordCredentialsImpl) cred;
assertThat(c.getScope(), is(CredentialsScope.GLOBAL));
assertThat(c.getId(), is("smokey-id"));
assertThat(c.getDescription(), is("created from xml"));
assertThat(c.getUsername(), is("example-com-deployer"));
assertThat(c.getPassword().getPlainText(), is("super-secret"));
// read credential
response = wc.goTo("credentials/store/system/domain/smokes/credential/smokey-id/config.xml", "application/xml").getWebResponse();
assertThat(response.getContentAsString(), CompareMatcher.isIdenticalTo(
"<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n"
+ " <scope>GLOBAL</scope>\n"
+ " <id>smokey-id</id>\n"
+ " <description>created from xml</description>\n"
+ " <username>example-com-deployer</username>\n"
+ " <password><secret-redacted/></password>\n"
+ "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>").ignoreWhitespace().ignoreComments());
// update credentials
con = postConfigDotXml(systemStore, "smokes", "smokey-id",
"<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n"
+ " <scope>SYSTEM</scope>\n"
+ " <id>smokey-id</id>\n"
+ " <description>updated by xml</description>\n"
+ " <username>example-org-deployer</username>\n"
+ " <password>super-duper-secret</password>\n"
+ "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>");
assertThat(con.getResponseCode(), is(200));
credentials = systemStore.getCredentials(systemStore.getDomainByName("smokes"));
assertThat(credentials, notNullValue());
cred = credentials.isEmpty() ? null : credentials.get(0);
assertThat(cred, instanceOf(UsernamePasswordCredentialsImpl.class));
c = (UsernamePasswordCredentialsImpl) cred;
assertThat(c.getScope(), is(CredentialsScope.SYSTEM));
assertThat(c.getId(), is("smokey-id"));
assertThat(c.getDescription(), is("updated by xml"));
assertThat(c.getUsername(), is("example-org-deployer"));
assertThat(c.getPassword().getPlainText(), is("super-duper-secret"));
// delete credentials
con = deleteConfigDotXml(systemStore, "smokes", "smokey-id");
assertThat(con.getResponseCode(), is(200));
assertThat(systemStore.getCredentials(systemStore.getDomainByName("smokes")), is(Collections.<Credentials>emptyList()));
// delete domain
con = deleteConfigDotXml(systemStore, "smokes");
assertThat(con.getResponseCode(), is(200));
assertThat(systemStore.getDomainByName("smokes"), nullValue());
}
@Test
public void restCRUDNonHappy() throws Exception {
JenkinsRule.WebClient wc = j.createWebClient();
j.getInstance().setCrumbIssuer(null);
assertThat(systemStore.getDomainByName("smokes"), nullValue());
// create domain
HttpURLConnection con =
postCreateByXml(systemStore, "<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>");
assumeThat(con.getResponseCode(), is(200));
con = postCreateByXml(systemStore, "<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>smokes</name>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>");
assertThat(con.getResponseCode(), is(HttpServletResponse.SC_CONFLICT));
// update domain
con = postConfigDotXml(systemStore, "no-smokes", "<com.cloudbees.plugins.credentials.domains.Domain>\n"
+ " <name>no-smokes</name>\n"
+ "<specifications>\n"
+ "<com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "<includes>smokes.example.com</includes>\n"
+ "<excludes/>\n"
+ "</com.cloudbees.plugins.credentials.domains.HostnameSpecification>\n"
+ "</specifications>\n"
+ "</com.cloudbees.plugins.credentials.domains.Domain>");
assertThat(con.getResponseCode(), is(404));
// create credential
con = postCreateByXml(systemStore, "smokes",
"<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n"
+ " <scope>GLOBAL</scope>\n"
+ " <id>smokey-id</id>\n"
+ " <description>created from xml</description>\n"
+ " <username>example-com-deployer</username>\n"
+ " <password>super-secret</password>\n"
+ "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>");
assumeThat(con.getResponseCode(), is(200));
con = postCreateByXml(systemStore, "smokes",
"<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n"
+ " <scope>GLOBAL</scope>\n"
+ " <id>smokey-id</id>\n"
+ " <description>created from xml</description>\n"
+ " <username>example-com-deployer</username>\n"
+ " <password>super-secret</password>\n"
+ "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>");
assertThat(con.getResponseCode(), is(HttpServletResponse.SC_CONFLICT));
}
private HttpURLConnection postCreateByXml(CredentialsStore store, String xml)
throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/createDomain").openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/xml;charset=utf-8");
con.setDoOutput(true);
con.getOutputStream().write(xml.getBytes("UTF-8"));
return con;
}
private HttpURLConnection postCreateByXml(CredentialsStore store, String domainName, String xml)
throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/domain/" + Util
.rawEncode(StringUtils.defaultIfBlank(domainName, "_")) + "/createCredentials")
.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/xml;charset=utf-8");
con.setDoOutput(true);
con.getOutputStream().write(xml.getBytes("UTF-8"));
return con;
}
private HttpURLConnection postConfigDotXml(CredentialsStore store, String domainName, String xml)
throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/domain/" + Util
.rawEncode(StringUtils.defaultIfBlank(domainName, "_")) + "/config.xml").openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/xml;charset=utf-8");
con.setDoOutput(true);
con.getOutputStream().write(xml.getBytes("UTF-8"));
return con;
}
private HttpURLConnection postConfigDotXml(CredentialsStore store, String domainName, String credentialsId,
String xml) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/domain/" + Util
.rawEncode(StringUtils.defaultIfBlank(domainName, "_")) + "/credential/" + Util
.rawEncode(credentialsId) + "/config.xml").openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/xml;charset=utf-8");
con.setDoOutput(true);
con.getOutputStream().write(xml.getBytes("UTF-8"));
return con;
}
private HttpURLConnection deleteConfigDotXml(CredentialsStore store, String domainName)
throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/domain/" + Util
.rawEncode(StringUtils.defaultIfBlank(domainName, "_")) + "/config.xml").openConnection();
con.setRequestMethod("DELETE");
return con;
}
private HttpURLConnection deleteConfigDotXml(CredentialsStore store, String domainName, String credentialsId)
throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(),
"credentials/store/" + store.getStoreAction().getUrlName() + "/domain/" + Util
.rawEncode(StringUtils.defaultIfBlank(domainName, "_")) + "/credential/" + Util
.rawEncode(credentialsId) + "/config.xml").openConnection();
con.setRequestMethod("DELETE");
return con;
}
}