[java-identity-provider] branch master updated: IDP-1595 Code to unpack zip and tar.gz files

Rod Widdowson rdw at steadingsoftware.com
Sat Jun 27 11:04:34 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=9f897339dc775c717c7f363eadf9c8e12e7b4368

The following commit(s) were added to refs/heads/master by this push:
       new  9f897339d IDP-1595 Code to unpack zip and tar.gz files
9f897339d is described below

commit 9f897339dc775c717c7f363eadf9c8e12e7b4368
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Wed Jun 24 13:48:13 2020 +0100

    IDP-1595 Code to unpack zip and tar.gz files
    
    https://issues.shibboleth.net/jira/browse/IDP-1595
---
 idp-conf/src/main/resources/conf/logback.xml       |   2 +-
 idp-installer/pom.xml                              |   5 +
 .../idp/installer/plugin/impl/PluginInstaller.java | 163 ++++++++++++++++++---
 .../idp/installer/plugin/impl/TrustStore.java      |   2 +-
 .../installer/plugin/impl/PluginInstallerTest.java |  36 ++++-
 5 files changed, 177 insertions(+), 31 deletions(-)

diff --git a/idp-conf/src/main/resources/conf/logback.xml b/idp-conf/src/main/resources/conf/logback.xml
index 2b7677029..7b28be1ea 100644
--- a/idp-conf/src/main/resources/conf/logback.xml
+++ b/idp-conf/src/main/resources/conf/logback.xml
@@ -14,7 +14,7 @@
 
     <!-- Location and retention. -->
     
-    <variable name="idp.logfiles" value="${idp.home}/logs" />
+    <variable name="idp.logfiles" value="c:/users/rdw/desktop/logs" />
     <variable name="idp.loghistory" value="${idp.loghistory:-180}" />
     
     <!-- Much higher performance if you operate on DEBUG. -->
diff --git a/idp-installer/pom.xml b/idp-installer/pom.xml
index 9e0564cd7..575eb3c5c 100644
--- a/idp-installer/pom.xml
+++ b/idp-installer/pom.xml
@@ -22,6 +22,11 @@
 
     <dependencies>
         <!-- Compile Dependencies -->
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-compress</artifactId>
+          <version>1.20</version>
+        </dependency>
         
         <dependency>
             <groupId>${project.groupId}</groupId>
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 42834733c..36634e3ff 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
@@ -17,18 +17,33 @@
 
 package net.shibboleth.idp.installer.plugin.impl;
 
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ServiceLoader;
 import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.apache.commons.compress.utils.IOUtils;
 import org.apache.tools.ant.BuildException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,7 +51,6 @@ import org.slf4j.LoggerFactory;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.plugin.PluginDescription;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -44,7 +58,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 /**
  *  The class where the heavy lifting of managing a plugin happens. 
  */
-public final class PluginInstaller extends AbstractInitializableComponent {
+public final class PluginInstaller extends AbstractInitializableComponent implements AutoCloseable {
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(PluginInstaller.class);
@@ -53,10 +67,16 @@ public final class PluginInstaller extends AbstractInitializableComponent {
     @NonnullAfterInit private Path idpHome;
     
     /** What we are dealing with. */
-    @NonnullAfterInit private String pluginId;
+    private String pluginId;
+    
+    /** Where we have unpacked into. */
+    private Path unpackDirectory;
+    
+    /** Where we have downloaded. */
+    private Path downloadDirectory;
     
     /** Our TrustStore. */
-    @NonnullAfterInit private TrustStore trustStore;
+    private TrustStore trustStore;
       
     /** set IdP Home.
      * @param home Where we are working from
@@ -72,25 +92,15 @@ public final class PluginInstaller extends AbstractInitializableComponent {
         pluginId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Plugin id should be be non-null");
     }    
 
-    /** {@inheritDoc} */
-    protected void doInitialize() throws ComponentInitializationException {
-        trustStore = new TrustStore();
-        trustStore.setIdpHome(idpHome);
-        trustStore.setPluginId(pluginId);
-        trustStore.initialize();
-        super.doInitialize();
-    }
-    
     /** Install the plugin from the provided URL.  Involves downloading
-     *  the file and then doing a {@link #installPlugin(Path, String, boolean)}.
+     *  the file and then doing a {@link #installPlugin(Path, String)}.
      * @param baseURL where we get the files from
      * @param fileName the name
-     * @param isTgz true if this is tgz, false if this is zip
      */
     public void installPlugin(@Nonnull final URL baseURL,
-                              @Nonnull @NotEmpty final String fileName,
-                              final boolean isTgz) {
-        
+                              @Nonnull @NotEmpty final String fileName) {
+        //download(baseURL, fileName);
+        installPlugin(downloadDirectory, fileName);
     }
     
     /** Install the plugin from a local path.
@@ -99,14 +109,89 @@ public final class PluginInstaller extends AbstractInitializableComponent {
      * <li>Install from the folder</li></ul>
      * @param base the directory where the files are
      * @param fileName the name
-     * @param isTgz true if this is tgz, false if this is zip
      */
     public  void installPlugin(@Nonnull final Path base, 
-                               @Nonnull @NotEmpty final String fileName,
-                               final boolean isTgz) {
-        
+                               @Nonnull @NotEmpty final String fileName) {
+        unpack(base, fileName);
+
     }
     
+    /** Method to unpack a zip or tgz file into out {{@link #unpackDirectory}. 
+     * @param base Where the zip/tgz file is
+     * @param fileName the name.
+     * @throws BuildException if badness is detected.
+     */
+    private void unpack(final Path base, final String fileName) throws BuildException {
+        Constraint.isNull(unpackDirectory, "cannot unpack multiple times");
+        try {
+            unpackDirectory = Files.createTempDirectory("plugin-installer");
+            
+            final Path fullName = base.resolve(fileName);
+            try (final ArchiveInputStream inStream = getStreamFor(fullName, isZip(fileName))) {
+                
+                ArchiveEntry entry = null;
+                while ((entry = inStream.getNextEntry()) != null) {
+                    if (!inStream.canReadEntryData(entry)) {
+                        log.warn("Could not read next entry from {}", inStream);
+                        continue;
+                    }
+                    final File output = unpackDirectory.resolve(entry.getName()).toFile();
+                    log.debug("Unpacking {} to {}", entry.getName(), output);
+                    if (entry.isDirectory()) {
+                        if (!output.isDirectory() && !output.mkdirs()) {
+                            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);
+                            throw new BuildException("failed to create unpacked directory");
+                        }
+                        try (OutputStream outStream = Files.newOutputStream(output.toPath())) {
+                            IOUtils.copy(inStream, outStream);
+                        }
+                    }
+                }
+            }
+        } catch (final IOException e) {
+            throw new BuildException(e);
+        }
+    }
+    
+    /** does the file name end in .zip?
+     * @param fileName the name to consider
+     * @return true if it ends with .zip
+     * @throws BuildException if the name is too short
+     */
+    private boolean isZip(final String fileName) throws BuildException {
+        if (fileName.length() <= 7) {
+            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);
+        }
+        return false;
+    }
+
+    /** Create the correct {@link ArchiveInputStream} for the input.
+     * @param fullName the path of the zip file to unpack.
+     * @param isZip if true then this is a zip file, otherwise a tgz file
+     * @return the the appropriate  {@link ArchiveInputStream} 
+     * @throws IOException  if we trip over an unpack
+     */
+    private ArchiveInputStream getStreamFor(final Path fullName, final boolean isZip) throws IOException {
+        final InputStream inStream = new BufferedInputStream(new FileInputStream(fullName.toFile()));
+        if (isZip) {
+            return new ZipArchiveInputStream(inStream);
+        }
+        return new TarArchiveInputStream(new GzipCompressorInputStream(inStream));
+    }
+
     /**
      * Return a list of the installed plugins.
      * @return All the plugins.
@@ -134,5 +219,39 @@ public final class PluginInstaller extends AbstractInitializableComponent {
         }
     }
     
+    /** Delete a directory tree. 
+     * @param directory what to delete
+     */
+    private void deleteTree(@Nullable final Path directory) {
+        if (directory == null) {
+            return;
+        }
+        try {
+            Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+                @Override 
+                public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+                    Files.delete(file);
+                    return FileVisitResult.CONTINUE;
+                }
+                @Override 
+                public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
+                    if (exc != null) {
+                        throw exc;
+                    }
+                    Files.delete(dir);
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        } catch (final IOException e) {
+            log.error("Couldn't delete {}", directory, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void close() {
+        deleteTree(downloadDirectory);
+        deleteTree(unpackDirectory);
+    }
+    
 }
 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java
index 27344b4c8..b5fe1a5c2 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java
@@ -234,7 +234,7 @@ public final class TrustStore extends AbstractInitializableComponent {
         }
         
         if (pluginId == null) {
-            throw new ComponentInitializationException("Plugin IN not set up");
+            throw new ComponentInitializationException("Plugin Id not set up");
         }
         
         if (!Files.exists(idpHome)) {
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerTest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerTest.java
index c1d469a39..3d475884a 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerTest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerTest.java
@@ -19,7 +19,9 @@ package net.shibboleth.idp.installer.plugin.impl;
 
 import static org.testng.Assert.assertEquals;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 
 import org.springframework.core.io.ClassPathResource;
@@ -32,15 +34,35 @@ import net.shibboleth.utilities.java.support.resource.Resource;
 @SuppressWarnings("javadoc")
 public class PluginInstallerTest {
 
-    @Test public void TestListing() throws ComponentInitializationException, IOException {
-        PluginInstaller inst = new PluginInstaller();
-        inst.setIdpHome(new ClassPathResource("idphome-test").getFile().toPath());
-        inst.setPluginId("net.shibboleth.idp.plugin.scripting.nashorn");
-        inst.initialize();
-        List<PluginDescription> plugins = inst.getInstalledPlugins();
-        assertEquals(plugins.get(0).getPluginId(), "org.example.Plugin");
+    @Test public void testListing() throws ComponentInitializationException, IOException {
+        
+        try (final PluginInstaller inst = new PluginInstaller()) {
+            inst.setIdpHome(new ClassPathResource("idphome-test").getFile().toPath());
+            inst.initialize();
+            List<PluginDescription> plugins = inst.getInstalledPlugins();
+            assertEquals(plugins.get(0).getPluginId(), "org.example.Plugin");
+        }
+    }
+    
+    @Test public void testUnpackZip() throws ComponentInitializationException, IOException {
+        try (final PluginInstaller inst = new PluginInstaller()) {
+            inst.setIdpHome(new ClassPathResource("idphome-test").getFile().toPath());
+            inst.initialize();
+            final File f = new File("H:\\Perforce\\Juno\\New\\plugins\\java-idp-plugin-scripting\\nashorn-dist\\target");
+            inst.installPlugin(f.toPath(),"shibboleth-idp-plugin-nashorn-0.0.1-SNAPSHOT.zip");
+        }
     }
     
+    @Test public void testUnpackTgz() throws ComponentInitializationException, IOException {
+        try (final PluginInstaller inst = new PluginInstaller()) {
+            inst.setIdpHome(new ClassPathResource("idphome-test").getFile().toPath());
+            inst.initialize();
+            final File f = new File("H:\\Perforce\\Juno\\New\\plugins\\java-idp-plugin-scripting\\nashorn-dist\\target");
+            inst.installPlugin(f.toPath(),"shibboleth-idp-plugin-nashorn-0.0.1-SNAPSHOT.tar.gz");
+        }
+    }
+
+    
     public static class Wibble extends PluginDescription {
 
         /** {@inheritDoc} */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list