[java-identity-provider] 03/06: IDP-1595 Install Remote and From Fle
Rod Widdowson
rdw at steadingsoftware.com
Tue Aug 4 13:06:43 UTC 2020
This is an automated email from the git hooks/post-receive script.
rdw pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=fe9a88c4bd4f8342299aae9c0c0d32e617e2c76c
commit fe9a88c4bd4f8342299aae9c0c0d32e617e2c76c
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Mon Aug 3 17:32:48 2020 +0100
IDP-1595 Install Remote and From Fle
https://issues.shibboleth.net/jira/browse/IDP-1595
Plus tests.
NOTA: The "accept cert" callback is still pending.
---
.../installer/plugin/PluginInstallerArguments.java | 82 +++++++++
.../idp/installer/plugin/PluginInstallerCLI.java | 33 +++-
.../idp/installer/plugin/impl/PluginInstaller.java | 95 ++++++-----
.../idp/installer/plugin/PluginCLITest.java | 82 ++++++++-
.../src/test/resources/credentials/truststore.asc | 189 +++++++++++++++++++++
5 files changed, 427 insertions(+), 54 deletions(-)
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerArguments.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerArguments.java
index 41a07228f..732d2d659 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerArguments.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerArguments.java
@@ -17,10 +17,17 @@
package net.shibboleth.idp.installer.plugin;
+import java.io.File;
import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
import javax.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.beust.jcommander.Parameter;
import net.shibboleth.ext.spring.cli.AbstractCommandLineArguments;
@@ -30,6 +37,9 @@ import net.shibboleth.ext.spring.cli.AbstractCommandLineArguments;
*/
public class PluginInstallerArguments extends AbstractCommandLineArguments {
+ /** Logger. */
+ private final Logger log = LoggerFactory.getLogger(PluginInstallerArguments.class);
+
/** The PluginId - usually used to drive the update. */
@Parameter(names= {"-p", "--pluginId"})
@Nullable private String pluginId;
@@ -42,6 +52,19 @@ public class PluginInstallerArguments extends AbstractCommandLineArguments {
@Parameter(names= {"-fl", "--full-list"})
@Nullable private boolean fullList;
+ /** What to install. */
+ @Parameter(names= {"-i", "--input"})
+ @Nullable private String input;
+
+ /** Decomposed input - name. */
+ @Nullable private String inputName;
+
+ /** Decomposed input - directory . */
+ @Nullable private Path inputDirectory;
+
+ /** Decomposed input - base URL. */
+ @Nullable private URL inputURL;
+
/** Operation enum. */
public enum OperationType {
/** Update a known install. */
@@ -66,6 +89,27 @@ public class PluginInstallerArguments extends AbstractCommandLineArguments {
return pluginId;
}
+ /** Get the digested parent URL.
+ * @return Returns the digested parent URL.
+ */
+ public URL getInputURL() {
+ return inputURL;
+ }
+
+ /** Get the file Name.
+ * @return Returns the digested file Name.
+ */
+ public String getInputFileName() {
+ return inputName;
+ }
+
+ /** Get the digested input directory.
+ * @return Returns the digested input directory.
+ */
+ public Path getInputDirectory() {
+ return inputDirectory;
+ }
+
/** Are we doing a full List?
* @return {@link #fullList}
*/
@@ -98,7 +142,44 @@ public class PluginInstallerArguments extends AbstractCommandLineArguments {
}
if (list || fullList) {
operation = OperationType.LIST;
+ if (input != null) {
+ log.error("Cannot List and Install in the same operation.");
+ throw new IllegalArgumentException("Cannot List and Install in the same operation.");
+ }
+ return;
+ }
+ if (input != null) {
+ operation = decodeInput() ;
+ }
+ }
+
+ /** Given an inout string, work out what the parts are.
+ * @return Whether this is a remote install or a local one.
+ */
+ private OperationType decodeInput() {
+ try {
+ final URL inputAsURL = new URL(input);
+ if ("https".equals(inputAsURL.getProtocol()) || "http".equals(inputAsURL.getProtocol())) {
+ final int i = input.lastIndexOf('/')+1;
+ inputURL = new URL(input.substring(0, i));
+ inputName = input.substring(i);
+ log.trace("Found URL: {}\t{}", inputDirectory, inputName);
+ return OperationType.INSTALLREMOTE;
+ }
+ } catch (final MalformedURLException e) {
+ log.trace("urg");
+ }
+ // Must be a file
+ final File inputAsFile = new File(input);
+ if (!inputAsFile.exists()) {
+ log.error("File {} does not exist", inputAsFile.getAbsolutePath());
+ throw new IllegalArgumentException("Input File does not exist");
}
+ final Path inputAsPath = Path.of(inputAsFile.getAbsolutePath());
+ inputDirectory = inputAsPath.getParent();
+ inputName = inputAsPath.getFileName().toString();
+ log.trace("Found File: {}\t{}", inputDirectory, inputName);
+ return OperationType.INSTALLDIR;
}
/** {@inheritDoc} */
@@ -113,6 +194,7 @@ public class PluginInstallerArguments extends AbstractCommandLineArguments {
out.println();
out.println(String.format(" %-22s %s", "-l, --list", "Brief Information of all installed plugins"));
out.println(String.format(" %-22s %s", "-fl, --full-list", "Full details of all installed plugins"));
+ out.println(String.format(" %-22s %s", "-i, --input <what>", "Install (file name or web address)"));
out.println();
}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerCLI.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerCLI.java
index 5f1e50fd8..197b279ca 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerCLI.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/PluginInstallerCLI.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.installer.plugin;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.security.Security;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -28,6 +29,8 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.http.client.HttpClient;
+import org.apache.tools.ant.BuildException;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -121,11 +124,15 @@ public final class PluginInstallerCLI extends AbstractCommandLine<PluginInstalle
}
/** {@inheritDoc} */
+ //CheckStyle: CyclomaticComplexity OFF
protected int doRun(final PluginInstallerArguments args) {
final int ret = super.doRun(args);
if (ret != RC_OK) {
return ret;
}
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
final Set<Entry<String, HttpClient>> clients =
getApplicationContext().getBeansOfType(HttpClient.class).entrySet();
if (clients.isEmpty()) {
@@ -147,17 +154,35 @@ public final class PluginInstallerCLI extends AbstractCommandLine<PluginInstalle
doList(args.getFullList(), args.getPluginId());
break;
+ case INSTALLDIR:
+ if (args.getPluginId() != null) {
+ installer.setPluginId(args.getPluginId());
+ }
+ installer.installPlugin(args.getInputDirectory(), args.getInputFileName());
+ break;
+
+ case INSTALLREMOTE:
+ if (args.getPluginId() != null) {
+ installer.setPluginId(args.getPluginId());
+ }
+ installer.installPlugin(args.getInputURL(), args.getInputFileName());
+ break;
+
default:
getLogger().error("Invalid operation");
- return RC_IO;
+ return RC_INIT;
}
- } catch (final ComponentInitializationException | BeansException e) {
- getLogger().error("Plugin failed", e);
+ } catch (final BeansException e) {
+ getLogger().error("Plugin Install failed", e);
+ return RC_INIT;
+ } catch (final ComponentInitializationException | BuildException e) {
+ getLogger().error("Plugin Install failed:", e);
return RC_IO;
- }
+ }
return ret;
}
+ //CheckStyle: CyclomaticComplexity OM
/** Build the installer.
* @throws ComponentInitializationException as required*/
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
index f5475e6ba..f42befe84 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
@@ -84,7 +84,8 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
private static List<String> disallowedPaths = List.of("dist", "system", "webapp");
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(PluginInstaller.class);
+ @Nonnull
+ private static final Logger LOG = LoggerFactory.getLogger(PluginInstaller.class);
/** Where we are installing to. */
@NonnullAfterInit private Path idpHome;
@@ -171,11 +172,11 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
public void installPlugin(@Nonnull final Path base,
@Nonnull @NotEmpty final String fileName) throws BuildException {
if (!Files.exists(base.resolve(fileName))) {
- log.error("Could not find distribution {}", base.resolve(fileName));
+ LOG.error("Could not find distribution {}", base.resolve(fileName));
throw new BuildException("Could not find distribution");
}
if (!Files.exists(base.resolve(fileName + ".asc"))) {
- log.error("Could not find distribution {}", base.resolve(fileName + ".asc"));
+ LOG.error("Could not find distribution {}", base.resolve(fileName + ".asc"));
throw new BuildException("Could not find signature for distribution");
}
@@ -183,15 +184,15 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
setupPluginId();
checkSignature(base, fileName);
getDescription();
- log.info("Installing Plugin {} version {}.{}.{}", pluginId,
+ LOG.info("Installing Plugin {} version {}.{}.{}", pluginId,
description.getMajorVersion(),description.getMinorVersion(), description.getPatchVersion());
if (!description.getAdditionalPropertyFiles().isEmpty()) {
- log.error("Additional property files not supported");
+ LOG.error("Additional property files not supported");
throw new BuildException("Uninstallable plugin");
}
if (!description.getPropertyMerges().isEmpty()) {
- log.error("Prroperty merges not supported");
+ LOG.error("Prroperty merges not supported");
throw new BuildException("Uninstallable plugin");
}
@@ -218,16 +219,16 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
for (final Pair<URL, Path> pair : description.getExternalFilePathsToCopy()) {
final Path to = idpHome.resolve(pair.getSecond());
if (Files.exists(to)) {
- log.warn("{} exists, not copied", to);
+ LOG.warn("{} exists, not copied", to);
continue;
}
if (!acceptDownload.test(new Pair<>(pair.getFirst(), to))) {
- log.info("Did not download {} to {}", pair.getFirst(), to);
+ LOG.info("Did not download {} to {}", pair.getFirst(), to);
continue;
}
buildHttpClient();
createParent(to);
- log.debug("Copying from {} to {}", pair.getFirst(), to);
+ LOG.debug("Copying from {} to {}", pair.getFirst(), to);
final Resource from = new HTTPResource(httpClient, pair.getFirst());
try (final InputStream in = new BufferedInputStream(from.getInputStream());
final OutputStream out = new BufferedOutputStream(new FileOutputStream(to.toFile()))) {
@@ -235,7 +236,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
in.transferTo(out);
} catch (final IOException e) {
- log.error("Could not copy from {} to {}", from, to, e);
+ LOG.error("Could not copy from {} to {}", from, to, e);
throw new BuildException(e);
}
}
@@ -259,18 +260,18 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
final ServiceLoader<PluginDescription> plugins = ServiceLoader.load(PluginDescription.class, loader);
for (final PluginDescription plugin:plugins) {
- log.debug("Found Service announcing itself as {}", plugin.getPluginId() );
+ LOG.debug("Found Service announcing itself as {}", plugin.getPluginId() );
if (pluginId.equals(plugin.getPluginId())) {
description = plugin;
return;
}
- log.trace("Did not match {}", pluginId);
+ LOG.trace("Did not match {}", pluginId);
}
}
- log.error("Could not locate description for {} in distribution {}", pluginId, libDir);
+ LOG.error("Could not locate description for {} in distribution {}", pluginId, libDir);
throw new BuildException("Could not locate PluginDescription");
} catch (final IOException e) {
- log.error("Could not get description of {} from {}", pluginId, libDir, e);
+ LOG.error("Could not get description of {} from {}", pluginId, libDir, e);
throw new BuildException(e);
}
}
@@ -282,7 +283,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
for (final Path p : description.getFilePathsToCopy()) {
for (final String disallowedPath : disallowedPaths) {
if (p.startsWith(disallowedPath)) {
- log.error("Path {} contained disallowed location", p);
+ LOG.error("Path {} contained disallowed location", p);
throw new BuildException("Copy to banned location");
}
}
@@ -290,22 +291,22 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
final Path from = distribution.resolve(p);
final Path to = idpHome.resolve(p);
if (Files.exists(to)) {
- log.debug("File {} exists, skipping", to);
+ LOG.debug("File {} exists, skipping", to);
continue;
}
if (!Files.exists(from)) {
- log.warn("Source File {} does not exists, skipping", from);
+ LOG.warn("Source File {} does not exists, skipping", from);
continue;
}
try {
createParent(to);
- log.debug("Copying from {} to {}", from, to);
+ LOG.debug("Copying from {} to {}", from, to);
try (final InputStream in = new BufferedInputStream(new FileInputStream(from.toFile()));
final OutputStream out = new BufferedOutputStream(new FileOutputStream(to.toFile()))) {
in.transferTo(out);
}
} catch (final IOException e) {
- log.error("Could not copy from {} to {}", from, to, e);
+ LOG.error("Could not copy from {} to {}", from, to, e);
throw new BuildException(e);
}
}
@@ -319,13 +320,13 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
private void createParent(final Path file) throws IOException, BuildException {
final Path parent = file.resolve("..");
if (!Files.exists(parent)) {
- log.debug("Creating parent directory {}", parent);
+ LOG.debug("Creating parent directory {}", parent);
Files.createDirectories(parent);
} else if (!Files.isDirectory(parent)) {
- log.error("{} exists and is not a directory", parent);
+ LOG.error("{} exists and is not a directory", parent);
throw new BuildException("Parent of target file was not a directory");
} else {
- log.trace("Parent directory {} existed", parent);
+ LOG.trace("Parent directory {} existed", parent);
}
}
@@ -336,7 +337,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
*/
private void installWebapp(final Path myWebApp) throws BuildException {
final Path from = distribution.resolve("edit-webapp");
- log.debug("Copying distribution from {} to {}", from, myWebApp);
+ LOG.debug("Copying distribution from {} to {}", from, myWebApp);
final Copy copy = InstallerSupport.getCopyTask(from, myWebApp);
copy.execute();
}
@@ -354,7 +355,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
download(baseResource, fileName);
download(baseResource, fileName + ".asc");
} catch (final IOException e) {
- log.error("Error in download", e);
+ LOG.error("Error in download", e);
throw new BuildException(e);
}
}
@@ -362,11 +363,11 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
/** Build the Http Client if it doesn't exist. */
private void buildHttpClient() {
if (httpClient == null) {
- log.debug("No HttpClient built, creating default");
+ LOG.debug("No HttpClient built, creating default");
try {
httpClient = new HttpClientBuilder().buildClient();
} catch (final Exception e) {
- log.error("Could not create HttpClient", e);
+ LOG.error("Could not create HttpClient", e);
throw new BuildException(e);
}
}
@@ -380,7 +381,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
private void download(final Resource baseResource, final String fileName) throws IOException {
final Resource fileResource = baseResource.createRelativeResource(fileName);
final Path filePath = downloadDirectory.resolve(fileName);
- log.debug("Downloading from {} to {}", fileResource.getDescription(), filePath);
+ LOG.debug("Downloading from {} to {}", fileResource.getDescription(), filePath);
try (final OutputStream fileOut = new BufferedOutputStream(
new FileOutputStream(filePath.toFile()))) {
fileResource.getInputStream().transferTo(fileOut);
@@ -404,20 +405,20 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
ArchiveEntry entry = null;
while ((entry = inStream.getNextEntry()) != null) {
if (!inStream.canReadEntryData(entry)) {
- log.warn("Could not read next entry from {}", inStream);
+ LOG.warn("Could not read next entry from {}", inStream);
continue;
}
final File output = unpackDirectory.resolve(entry.getName()).toFile();
- log.trace("Unpacking {} to {}", entry.getName(), output);
+ LOG.trace("Unpacking {} to {}", entry.getName(), output);
if (entry.isDirectory()) {
if (!output.isDirectory() && !output.mkdirs()) {
- log.error("Failed to create directory {}", output);
+ LOG.error("Failed to create directory {}", output);
throw new BuildException("failed to create unpacked directory");
}
} else {
final File parent = output.getParentFile();
if (!parent.isDirectory() && !parent.mkdirs()) {
- log.error("Failed to create parent directory {}", parent);
+ LOG.error("Failed to create parent directory {}", parent);
throw new BuildException("failed to create unpacked directory");
}
try (OutputStream outStream = Files.newOutputStream(output.toPath())) {
@@ -429,12 +430,12 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
try (final DirectoryStream<Path> unpackDirStream = Files.newDirectoryStream(unpackDirectory)) {
final Iterator<Path> contents = unpackDirStream.iterator();
if (!contents.hasNext()) {
- log.error("No contents unpacked from {}", fullName);
+ LOG.error("No contents unpacked from {}", fullName);
throw new BuildException("Distro was empty");
}
distribution = contents.next();
if (contents.hasNext()) {
- log.error("Too many packages in distributions {}", fullName);
+ LOG.error("Too many packages in distributions {}", fullName);
throw new BuildException("Too many packages in distributions");
}
}
@@ -451,14 +452,14 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
*/
private boolean isZip(final String fileName) throws BuildException {
if (fileName.length() <= 7) {
- log.error("Improbably small file name: {}", fileName);
+ LOG.error("Improbably small file name: {}", fileName);
throw new BuildException("Improbably small file name");
}
if (".zip".equalsIgnoreCase(fileName.substring(fileName.length()-4))) {
return true;
}
if (!".tar.gz".equalsIgnoreCase(fileName.substring(fileName.length()-7))) {
- log.warn("FileName {} did not end with .zip or .tar.gz, assuming tar-gz", fileName);
+ LOG.warn("FileName {} did not end with .zip or .tar.gz, assuming tar-gz", fileName);
}
return false;
}
@@ -483,7 +484,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
private void setupPluginId() throws BuildException {
final File propertyFile = distribution.resolve("bootstrap").resolve("id.property").toFile();
if (!propertyFile.exists()) {
- log.error("Could not locate identity of plugin at {}", propertyFile);
+ LOG.error("Could not locate identity of plugin at {}", propertyFile);
throw new BuildException("Could not locate identity of plugin");
}
try (final InputStream inStream = new BufferedInputStream(new FileInputStream(propertyFile))) {
@@ -491,16 +492,16 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
idProperties.load(inStream);
final String id = StringSupport.trimOrNull(idProperties.getProperty("pluginid"));
if (id == null) {
- log.error("identity property file {} did not contain 'pluginid' property", propertyFile);
+ LOG.error("identity property file {} did not contain 'pluginid' property", propertyFile);
throw new BuildException("No property in ID file");
}
if (pluginId != null && !pluginId.equals(id)) {
- log.error("Downloaded plugin id {} overriden by provided id {}", id, pluginId);
+ LOG.error("Downloaded plugin id {} overriden by provided id {}", id, pluginId);
} else {
setPluginId(id);
}
} catch (final IOException e) {
- log.error("Could not load plugin identity at {}", propertyFile, e);
+ LOG.error("Could not load plugin identity at {}", propertyFile, e);
throw new BuildException(e);
}
}
@@ -519,10 +520,10 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
trust.initialize();
final Signature sig = TrustStore.signatureOf(sigStream);
if (!trust.contains(sig)) {
- log.info("TrustStore does not contain signature {}", sig);
+ LOG.info("TrustStore does not contain signature {}", sig);
final File certs = distribution.resolve("bootstrap").resolve("keys.txt").toFile();
if (!certs.exists()) {
- log.info("No embedded keys file, signature check fails");
+ LOG.info("No embedded keys file, signature check fails");
throw new BuildException("No Certificate found to check signiture o distribution");
}
try (final InputStream keysStream = new BufferedInputStream(
@@ -530,7 +531,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
trust.importCertificateFromStream(sig, keysStream, acceptCert);
}
if (!trust.contains(sig)) {
- log.info("Certificate not added to Trust Store");
+ LOG.info("Certificate not added to Trust Store");
throw new BuildException("Could not check signature of distribution");
}
}
@@ -538,13 +539,13 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
try (final InputStream distroStream = new BufferedInputStream(
new FileInputStream(base.resolve(fileName).toFile()))) {
if (!trust.checkSignature(distroStream, sig)) {
- log.info("Signature checked for {} failed", fileName);
+ LOG.info("Signature checked for {} failed", fileName);
throw new BuildException("Signature check failed");
}
}
} catch (final ComponentInitializationException | IOException e) {
- log.error("Could not manage truststore for [{}, {}] ", idpHome, pluginId, e);
+ LOG.error("Could not manage truststore for [{}, {}] ", idpHome, pluginId, e);
throw new BuildException(e);
}
}
@@ -582,11 +583,11 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
/** Delete a directory tree.
* @param directory what to delete
*/
- private void deleteTree(@Nullable final Path directory) {
+ public static void deleteTree(@Nullable final Path directory) {
if (directory == null || !Files.exists(directory)) {
return;
}
- log.debug("Deleting directory {}", directory);
+ LOG.debug("Deleting directory {}", directory);
try {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
@@ -604,7 +605,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
}
});
} catch (final IOException e) {
- log.error("Couldn't delete {}", directory, e);
+ LOG.error("Couldn't delete {}", directory, e);
}
}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/PluginCLITest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/PluginCLITest.java
index f2d87ff99..5b8b1ffe8 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/PluginCLITest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/PluginCLITest.java
@@ -18,34 +18,110 @@
package net.shibboleth.idp.installer.plugin;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.http.client.HttpClient;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
+import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
import net.shibboleth.ext.spring.cli.AbstractCommandLine;
+import net.shibboleth.ext.spring.resource.HTTPResource;
+import net.shibboleth.idp.installer.plugin.impl.PluginInstaller;
+import net.shibboleth.utilities.java.support.httpclient.HttpClientBuilder;
@SuppressWarnings("javadoc")
public class PluginCLITest extends BasePluginTest {
- @Test(enabled = true) public void testList() throws IOException {
+ private final String RHINO_DISTRO = "https://build.shibboleth.net/nexus/service/local/repositories/releases/content/net/shibboleth/idp/plugin/scripting/idp-plugin-rhino-dist/0.1.0/idp-plugin-rhino-dist-0.1.0.tar.gz";
+
+ private File plugin;
+
+ @BeforeSuite public void setUp() throws IOException
+ {
System.setProperty("net.shibboleth.idp.cli.idp.home",getIdpHome().toString());
final Resource pluginInstaller = new ClassPathResource("conf/admin/plugin-installer.xml");
- final File plugin = getIdpHome().resolve("conf").resolve("admin").resolve("plugin-installer.xml").toFile();
+ plugin = getIdpHome().resolve("conf").resolve("admin").resolve("plugin-installer.xml").toFile();
plugin.createNewFile();
try (final InputStream is = pluginInstaller.getInputStream();
final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(plugin))) {
is.transferTo(os);
}
-
+ }
+
+ @Test(enabled = true) public void testList() throws IOException {
assertEquals(PluginInstallerCLI.runMain(new String[] { plugin.getAbsolutePath(), "-fl"}),
AbstractCommandLine.RC_OK);
}
+
+ @Test(enabled = true) public void testLocal() {
+ assertEquals(PluginInstallerCLI.runMain(new String[] { plugin.getAbsolutePath(), "-i", "a"}),
+ AbstractCommandLine.RC_INIT);
+ }
+
+ @Test(enabled = true, dependsOnMethods = {"testRhinoLocal"}) public void testRhinoWeb() {
+ assertEquals(PluginInstallerCLI.runMain(new String[] { plugin.getAbsolutePath(),
+ "-i", RHINO_DISTRO,
+ "-p", "net.shibboleth.idp.plugin.rhino"}),
+ AbstractCommandLine.RC_OK);
+ }
+
+ @Test(enabled = true) public void testRhinoLocal() {
+ Path unpack = null;
+ try {
+ unpack = Files.createTempDirectory("rhinoLocal");
+ final HttpClient client = new HttpClientBuilder().buildClient();
+ Resource from = new HTTPResource(client, RHINO_DISTRO);
+ try (final InputStream in = from.getInputStream();
+ final OutputStream out = new BufferedOutputStream(new FileOutputStream(unpack.resolve("rhino.tar.gz").toFile()))) {
+ in.transferTo(out);
+ }
+ from = new HTTPResource(client, RHINO_DISTRO + ".asc");
+ try (final InputStream in = from.getInputStream();
+ final OutputStream out = new BufferedOutputStream(new FileOutputStream(unpack.resolve("rhino.tar.gz.asc").toFile()))) {
+ in.transferTo(out);
+ }
+
+ assertEquals(PluginInstallerCLI.runMain(new String[] { plugin.getAbsolutePath(),
+ "-p", "net.shibboleth.idp.plugin.rhino",
+ "-i", unpack.resolve("rhino.tar.gz").toString()}),
+ AbstractCommandLine.RC_IO);
+ //
+ // Populate the new key store
+ //
+ final Path trustStorePath = getIdpHome().
+ resolve("credentials").
+ resolve("net.shibboleth.idp.plugin.rhino").
+ resolve("truststore.asc");
+ from = new ClassPathResource("credentials/truststore.asc");
+ try (final InputStream in = from.getInputStream();
+ final OutputStream out = new BufferedOutputStream(new FileOutputStream(trustStorePath.toFile(), true))) {
+ in.transferTo(out);
+ }
+ //
+ // try again
+ //
+ assertEquals(PluginInstallerCLI.runMain(new String[] { plugin.getAbsolutePath(),
+ "-i", unpack.resolve("rhino.tar.gz").toString(),
+ "-p", "net.shibboleth.idp.plugin.rhino"}),
+ AbstractCommandLine.RC_OK);
+ } catch (Exception e) {
+ fail("Failed" + e);
+ } finally {
+ if (unpack != null) {
+ PluginInstaller.deleteTree(unpack);
+ }
+ }
+ }
}
diff --git a/idp-installer/src/test/resources/credentials/truststore.asc b/idp-installer/src/test/resources/credentials/truststore.asc
new file mode 100644
index 000000000..0ccc32281
--- /dev/null
+++ b/idp-installer/src/test/resources/credentials/truststore.asc
@@ -0,0 +1,189 @@
+
+pub 4096R/2A4B3FF0 2019-09-18
+uid Rod Widdowson <rdw at steadingsoftware.com>
+sub 4096R/441D628D 2019-09-18
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+HEdQFQ==
+=/gwj
+-----END PGP PUBLIC KEY BLOCK-----
+
+OBS security:shibboleth RPM repository key
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+mQGiBEholYgRBACW/kTYZi5mAEMP8j1qui2bRnWmYblFbiZvb5JMJYUWL/jyMjHj
+LLJvVrnOre/AxexH9KmaJwBuNoa/X9l/tQGIb49QRhR346QQUbQcwlYpckg5ccqN
+qlAURHEdjCxRxMhzPs+C/F6Nqa/fHpAectW0JNRqAVVd9CWjCG3l6I2CywCgk9UI
+SUAvaB5bVMxEVrFAKrVh4MUEAISeUwOaTIZftIamjo0VrnYemHS4SmGqMALEtHeG
+/o7ecMhLb/MvreEVISrE1hbfmnObYoiVXWJrorOEZDh1hOVdGRkHJOYHvQSRp9uC
+/uy4Mmog4R7ba5Ct5wpw0RCav2HMqwOJEyCX6jnip5P5LMmaFdGg/RQZM/e9mbyF
+VeL0A/0WK//+VWrQya1rQIG+v4IZ+rWvrkqXKtrzELkWd/3vWNMXi4BfeBM/itLQ
+yYOdhnx55FHxBzv+dK+UErrdTM8Ingljy7oztV/G6+K15CcGvEy9ITb62v2bDdS2
+uYWEFOQJK+I6aU4paNysvtKsOlTt9FhyfJJW9G3kJDUBPM9HbLRIc2VjdXJpdHk6
+c2hpYmJvbGV0aCBPQlMgUHJvamVjdCA8c2VjdXJpdHk6c2hpYmJvbGV0aEBidWls
+ZC5vcGVuc3VzZS5vcmc+iGYEExECACYFAlgaKTsCGwMFCRPQQ7MGCwkIBwMCBBUC
+CAMEFgIDAQIeAQIXgAAKCRBzyTdFfQobPWC4AJ96FdlQrvlAHzQjqT994h6lZxfZ
+pgCfSBPRhrdNGh72+d288vgZ7ptV4/SIRgQTEQIABgUCSGiViAAKCRA7MBG3a51l
+Iy3vAKCUPurlZup+vzQtpij3FMo0JAVW9ACgjdkt/hWt0WsjfHb+/cPwXtgx9X8=
+=6fV2
+-----END PGP PUBLIC KEY BLOCK-----
+
+Henri Mikkonen PGP key
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBF46zL0BEACUeQllAAViSlyL8uFBCjlCXdH12GpDL9y8fubm+N50ofonIloA
+YLbJtETVrqpxfeh+SDiERbEG5W02fbM1y3wdSjef0jzAEP3PoXydv/SdNKvomvBP
+U7I9eALgHJI4Nkqzf8ggTrOBHcWbRIRGbVXFRhOE1Z86akmVz3fe3aQzddvzAS7I
+YYX0RxbKiNt8iaxUXUo+P1LopD9Zo2I1NTY8u27RuhtxBr5tnHnsuf38mzjG/l6U
+RzJ8qhHJr6D4E+MLqRo9ndTREOT/d1TeJUvQddXC59VEL75TrYCEc2v/NZ5m9fD6
+yg0+oqgyrQHmZhPVOqoJiz0lkd3rl7lUqCH9yjREr1H5PUchiuhBKBOogwtirqw3
+NMKH6bs0Bu6qUy5fIJRqjxKVv+6fOEty/xnp0xN7xoBEUPEt1M/V3ewwH1zhOwTo
+g4cr4zhTT9RNno3eM0eenEQYapQZ8dFmrNVmhvx9VJlshYGyakrxPwrF3coyC3hh
+HjWE9SzmoyGmmbRgvJVt//SqoGpDyaM+d1hPys9tX2N/E1TlwZiD2brWAtjr2K49
+NC9Skizw4qHAbphq4EMGCKzrp9ksnBvwZAY9JjL0JvdjAabqkyRFVh2Mpm5xSxbw
+d+Twryh5hXaT/EQXsKMC1WlQnIDREjHpm1UOXTzcsFPa9tEW8XUftPWbQQARAQAB
+tCZIZW5yaSBNaWtrb25lbiA8aGVucmkubWlra29uZW5AaWtpLmZpPokCVAQTAQgA
+PhYhBG0Y/WNwj8ygebaMzgJmkYOTVevKBQJeOsy9AhsDBQkHhh+ABQsJCAcCBhUK
+CQgLAgQWAgMBAh4BAheAAAoJEAJmkYOTVevKwWcP+gLrjnrNxqwEx7/Ly/KdjkGD
+0W7aMiQc8acvC9oo74/XXpAD0W1jkK/BXyLH1q/o5Lyjymmm6w7VvEWLSY1Q0+gC
+l+hUOqccH572767UrGEeZeJV8+tNhziTU2S7NagK2A0BelHoA3hIhfGmWLJ+ooJe
+HZXFCov4ThZOpGzu5d04dEYoOv2jVaWwnrjOBzoKcgws9J6RLX+6gOFhZ3Dh5Rxs
+UGhl0ZJuEBQCDT7X9jI4mHsA0Ngo27inb3gxfeCm/ziZhHDV2gZtl777dKVc/sQN
+fqGaRGVi1p37La6KKpfIA3KHRjGf4jfg17AQ1Ix+ZgRIpbPXb7fXQHtBElhIbbn/
+VR2CG0Jdchdc4UozelKU6WNsNlcMn3kfTNFosW7+gTiYEGSxZQC9ylSSl1s9oIFM
+dvk70u4AgTY6w+27TrTRuEpdARoNZG4NhBTJ8g0BkiX6cHVyc5ir5IOVpmewsxN5
+yLg0ed6OwpcK5V8SwGT60hgkkJp71OeBsnLzyzO3/YoI5GVAIgcwtdzptRUt0iL8
+GUccO3mO6Hm4EfJAZHFWRbxX3ITTfCzw4blbXURlIXkPefprptAYX2+rn/z4iC1F
+mJUANl+4WilKuPoAimKGDNi6CvlbckQW2i2i5gsoM3iMxRMsExoZUnoMpfY70Trg
+ToF/jwURMQSCsJnZvyQDuQINBF46zL0BEAD9AuFJ7J1R5AOW9OzFTRdyMh4bCOtt
+p761l0UmaW5tkgtmKH977E/xB+RhgXTTL7tqWZD3rAt+/uP/4/kAzO9WpaiRnFIC
+oZcE1O6BU4+jbl16PJRf20LOfZlsGT5nEmYvTGTIsZYcTalE+iNiFbK3ehe2MOeZ
+96GTH+r10zcOI6j0k8fKnkKzs1BeqdbgxBQlqOy4fBoS2tgGYHsqyH4/IHqfQbxM
+QPQPxgNE8WMh7CqA3jkOw6tNj/RmsQ1Y8qjVmyQjNFt5p49+UEx2lRkYHfSSQADd
+uCbs0D1ccyI3vlvIy5Hn+aLqKR7Y2LpLgCUkXqPWDNv/nTzvbIkbKy6ZNrDyiuq1
+7L/HOnE5nR964zR5fhEMTDBAi/TwT454xkNnnTHhvGKlP2VCe30J8z4O1XoCCy3r
+BFImgU0t10lpxnIXiZFu8GeFT2ddgLph8EHXk5M/IjrKGW9I1JV2HgWF5T13Izff
+k9dvHETijvGyFpFezJfjRuDP3dzPCsXR4FJJiClXm0S3H+bLYLf0rrWDQzPU3c14
+fdh7HIZsRaZIPM0PjM3as1DMjm5TtuZi089Q78Yi5WdEwivZlvPfVckvTJUGcWhe
+sYR6ynQ749ORLz8jjbrhT6DDkjjvzVCepRLsARKRAvVF+I00ddeH0JxvPjHpyyUc
+zhKXqTzD813SJwARAQABiQI7BBgBCAAmFiEEbRj9Y3CPzKB5tozOAmaRg5NV68oF
+Al46zL0CGwwFCQeGH4AACgkQAmaRg5NV68pV3g/0C7clD4qsIU3TOLMZcWRHzvgp
+Z+yhSf80B3TYPempR6aOntqkDWsqVmt7D4nIehdCHfVDyW+PF+Jf17iot7AfsrSy
+lTQsOKwMM5Rw05VfqKIZBlJsHnKUmprC3yDV0CdidC9Cq0pQdiVeHzvS7R9HmMPG
+da30HikBHiFsYMIS+1hJKa//X75ncKiPc8ypoM7O6HrtArXZiWRjLfpcHBBHbVea
+ixOotHM271C3KsWTqURgzCX+GrumMS7QvXnHq4xketuBsVD6X/rlHzLjxSE0p7Tj
+G/B2VV1WPkb+QgPDC/1rXIi6NMm9GE/tzbPXTcGHX2irHlvGvotg32vWwehRnqNF
+exLuW4t30p+8E27+l03kGILCNLhhAFjjjPp4Vza/E3ZaQprSVBr9gH1HwZKUTThM
+EqGmypTmvnmx3Kw6pG0tia4wdLSxfyZh5XltUnwVSqptWdvt5tlceMFJlGxvIuw1
+ubDr97aIVM7kME6E5D59IXDWnxkIbdoAtaeQegO2OeyvbffuKnnX0ogF4Gcu6Zed
+ap7nWr2LBEwN3S9+hDIrXfs3QMy3bZIPkVCo0ncwaJPZFIMWWqeUkkjTzOKbQ53P
+6REH0FGoCXOH3qTqbS3bPNmyD3TVtN8OwiUZsOr/zu9VdNqUW3oq+aix4tU00pu7
+A+i1fd0Gifis1HhBeA==
+=ObHY
+-----END PGP PUBLIC KEY BLOCK-----
+
+philsmart GPG key 4096-bit RSA key, ID C21771DD, created 2020-02-25
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.22 (GNU/Linux)
+
+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
+3aNjQSVwGC4Owz97QpUq9StZb1qhvIw=
+=1e4x
+-----END PGP PUBLIC KEY BLOCK-----
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list