/* * The MIT License * * Copyright (c) 2016, CloudBees, Inc.. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.cloudbees.plugins.credentials.cli; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsSelectHelper; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.domains.DomainSpecification; import com.cloudbees.plugins.credentials.domains.HostnameSpecification; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import hudson.cli.CLICommand; import hudson.cli.CLICommandInvoker; import hudson.model.Items; import java.io.ByteArrayInputStream; import java.nio.charset.Charset; import java.util.Collections; import java.util.List; import jenkins.model.Jenkins; import org.hamcrest.Matcher; 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 hudson.cli.CLICommandInvoker.Matcher.failedWith; import static hudson.cli.CLICommandInvoker.Matcher.succeeded; import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; 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 CLICommandsTest { @Rule public JenkinsRule r = new JenkinsRule(); private CredentialsStore store = null; @Before public void clearCredentials() { SystemCredentialsProvider.getInstance().setDomainCredentialsMap( Collections.singletonMap(Domain.global(), Collections.<Credentials>emptyList())); for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.getInstance())) { if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) { store = s; break; } } assertThat("The system credentials provider is enabled", store, notNullValue()); } @Test public void createSmokes() { CLICommand cmd = new CreateCredentialsDomainByXmlCommand(); CLICommandInvoker invoker = new CLICommandInvoker(r, cmd); assertThat(SystemCredentialsProvider.getInstance().getDomainCredentialsMap().keySet(), (Matcher) not(hasItem(hasProperty("name", is("smokes"))))); assertThat(invoker.withStdin(asStream( "<com.cloudbees.plugins.credentials.domains.Domain>\n" + " <name>smokes</name>\n" + "</com.cloudbees.plugins.credentials.domains.Domain>")) .invokeWithArgs("system::system::jenkins"), succeededSilently()); assertThat(SystemCredentialsProvider.getInstance().getDomainCredentialsMap().keySet(), (Matcher) hasItem(hasProperty("name", is("smokes")))); cmd = new CreateCredentialsByXmlCommand(); invoker = new CLICommandInvoker(r, cmd); assertThat(invoker.withStdin(asStream( "<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>")) .invokeWithArgs("system::system::jenkins", "smokes"), succeededSilently()); Domain domain = null; for (Domain d : SystemCredentialsProvider.getInstance().getDomainCredentialsMap().keySet()) { if ("smokes".equals(d.getName())) { domain = d; break; } } List<Credentials> credentials = SystemCredentialsProvider.getInstance().getDomainCredentialsMap().get(domain); 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")); } @Test public void createNonHappy() { CLICommand cmd = new CreateCredentialsDomainByXmlCommand(); CLICommandInvoker invoker = new CLICommandInvoker(r, cmd); assertThat(SystemCredentialsProvider.getInstance().getDomainCredentialsMap().keySet(), (Matcher) not(hasItem(hasProperty("name", is("smokes"))))); assumeThat(invoker.withStdin(asStream( "<com.cloudbees.plugins.credentials.domains.Domain>\n" + " <name>smokes</name>\n" + "</com.cloudbees.plugins.credentials.domains.Domain>")) .invokeWithArgs("system::system::jenkins"), succeededSilently()); assertThat(invoker.withStdin(asStream( "<com.cloudbees.plugins.credentials.domains.Domain>\n" + " <name>smokes</name>\n" + "</com.cloudbees.plugins.credentials.domains.Domain>")) .invokeWithArgs("system::system::jenkins"), failedWith(1)); cmd = new CreateCredentialsByXmlCommand(); invoker = new CLICommandInvoker(r, cmd); assumeThat(invoker.withStdin(asStream( "<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>")) .invokeWithArgs("system::system::jenkins", "smokes"), succeededSilently()); assertThat(invoker.withStdin(asStream( "<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>")) .invokeWithArgs("system::system::jenkins", "smokes"), failedWith(1)); } @Test public void readSmokes() throws Exception { Domain smokes = new Domain("smokes", "smoke test domain", Collections.<DomainSpecification>singletonList(new HostnameSpecification("smokes.example.com", null))); UsernamePasswordCredentialsImpl smokey = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "smokes-id", "smoke testing", "smoke", "smoke text"); store.addDomain(smokes, smokey); CLICommandInvoker invoker = new CLICommandInvoker(r, new GetCredentialsDomainAsXmlCommand()); CLICommandInvoker.Result result = invoker.invokeWithArgs("system::system::jenkins", "smokes"); assertThat(result, succeeded()); assertThat(result.stdout(), CompareMatcher.isIdenticalTo(Items.XSTREAM2.toXML(smokes)) .ignoreComments() .ignoreWhitespace() ); invoker = new CLICommandInvoker(r, new GetCredentialsAsXmlCommand()); result = invoker.invokeWithArgs("system::system::jenkins", "smokes", "smokes-id"); assertThat(result, succeeded()); assertThat("secrets are redacted", result.stdout(), not(CompareMatcher.isIdenticalTo(Items.XSTREAM2.toXML(smokey)) .ignoreComments() .ignoreWhitespace() )); assertThat("secrets are redacted", result.stdout(), not(containsString(smokey.getPassword().getEncryptedValue()))); assertThat("secrets are redacted", result.stdout(), not(containsString(smokey.getPassword().getPlainText()))); assertThat("secrets are redacted", result.stdout(), CompareMatcher.isIdenticalTo( Items.XSTREAM2.toXML(smokey).replace(smokey.getPassword().getEncryptedValue(), "<secret-redacted/>")) .ignoreComments() .ignoreWhitespace() ); } @Test public void listSmokes() throws Exception { Domain smokes = new Domain("smokes", "smoke test domain", Collections.<DomainSpecification>singletonList(new HostnameSpecification("smokes.example.com", null))); UsernamePasswordCredentialsImpl smokey = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "smokes-id", "smoke testing", "smoke", "smoke text"); // check getting the providers CLICommandInvoker invoker = new CLICommandInvoker(r, new ListCredentialsProvidersCommand()); CLICommandInvoker.Result result = invoker.invoke(); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("\nsystem ")); assertThat(result.stdout(), containsString("\nSystemCredentialsProvider ")); assertThat(result.stdout(), containsString("\n"+SystemCredentialsProvider.ProviderImpl.class.getName()+" ")); // now check getting the resolvers invoker = new CLICommandInvoker(r, new ListCredentialsContextResolversCommand()); result = invoker.invoke(); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("\nsystem ")); assertThat(result.stdout(), containsString("\nSystemContextResolver ")); assertThat(result.stdout(), containsString("\n" + CredentialsSelectHelper.SystemContextResolver.class.getName()+" ")); // now check listing credentials (expect empty) invoker = new CLICommandInvoker(r, new ListCredentialsCommand()); result = invoker.invokeWithArgs("system::system::jenkins"); assertThat(result, succeeded()); assertThat(result.stdout().replaceAll("\\s+", " "), allOf( containsString(" Domain (global) Description # of Credentials 0 "), not(containsString(" Domain smokes ")) )); store.addDomain(smokes, smokey); invoker = new CLICommandInvoker(r, new ListCredentialsCommand()); result = invoker.invokeWithArgs("system::system::jenkins"); assertThat(result, succeeded()); assertThat(result.stdout().replaceAll("\\s+", " "), allOf( containsString(" Domain (global) Description # of Credentials 0 "), containsString(" Domain smokes Description smoke test domain # of Credentials 1 "), containsString(" smokes-id smoke/****** (smoke testing) ") )); } @Test public void updateSmokes() throws Exception { Domain smokes = new Domain("smokes", "smoke test domain", Collections.<DomainSpecification>singletonList(new HostnameSpecification("smokes.example.com", null))); UsernamePasswordCredentialsImpl smokey = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "smokes-id", "smoke testing", "smoke", "smoke text"); store.addDomain(smokes, smokey); CLICommandInvoker invoker = new CLICommandInvoker(r, new UpdateCredentialsDomainByXmlCommand()); Domain replacement = new Domain("smokes", "smoke test domain updated", Collections.<DomainSpecification>singletonList(new HostnameSpecification("smokes.example.com", "update.example.com"))); assertThat(invoker.withStdin(asStream(Items.XSTREAM2.toXML(replacement))) .invokeWithArgs("system::system::jenkins", "smokes"), succeededSilently()); Domain updated = store.getDomainByName("smokes"); assertThat(Items.XSTREAM2.toXML(updated), CompareMatcher.isIdenticalTo(Items.XSTREAM2.toXML(replacement)) .ignoreComments() .ignoreWhitespace() ); assertThat(Items.XSTREAM2.toXML(updated), not(CompareMatcher.isIdenticalTo(Items.XSTREAM2.toXML(smokes)) .ignoreComments() .ignoreWhitespace() )); invoker = new CLICommandInvoker(r, new UpdateCredentialsByXmlCommand()); assertThat(invoker.withStdin(asStream( "<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>\n" + " <scope>SYSTEM</scope>\n" + " <id>smokes-id</id>\n" + " <description>updated by xml</description>\n" + " <username>soot</username>\n" + " <password>vapour text</password>\n" + "</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>")) .invokeWithArgs("system::system::jenkins", "smokes", "smokes-id"), succeededSilently()); assertThat(store.getCredentials(smokes).size(), is(1)); Credentials cred = store.getCredentials(smokes).get(0); assertThat(cred, instanceOf(UsernamePasswordCredentialsImpl.class)); UsernamePasswordCredentialsImpl c = (UsernamePasswordCredentialsImpl) cred; assertThat(c.getScope(), is(CredentialsScope.SYSTEM)); assertThat(c.getId(), is("smokes-id")); assertThat(c.getDescription(), is("updated by xml")); assertThat(c.getUsername(), is("soot")); assertThat(c.getPassword().getPlainText(), is("vapour text")); } @Test public void deleteSmokes() throws Exception { Domain smokes = new Domain("smokes", "smoke test domain", Collections.<DomainSpecification>singletonList(new HostnameSpecification("smokes.example.com", null))); UsernamePasswordCredentialsImpl smokey = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "smokes-id", "smoke testing", "smoke", "smoke text"); store.addDomain(smokes, smokey); CLICommandInvoker invoker = new CLICommandInvoker(r, new DeleteCredentialsCommand()); assertThat(store.getCredentials(smokes), not(is(Collections.<Credentials>emptyList()))); assertThat(invoker.invokeWithArgs("system::system::jenkins", "smokes", "smokes-id"), succeededSilently()); assertThat(store.getCredentials(smokes), is(Collections.<Credentials>emptyList())); invoker = new CLICommandInvoker(r, new DeleteCredentialsDomainCommand()); assertThat(invoker.invokeWithArgs("system::system::jenkins", "smokes"), succeededSilently()); assertThat(store.getDomainByName("smokes"), nullValue()); } private static ByteArrayInputStream asStream(String text) { return new ByteArrayInputStream(text.getBytes(Charset.forName("UTF-8"))); } }