diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index c3bdd1c5b3e..0c418ea65a6 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -23,14 +23,16 @@ import org.eclipse.jetty.util.PathWatcher.PathWatchEvent; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.JarResource; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.security.Credential; import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -57,6 +59,8 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener { private static final Logger LOG = Log.getLogger(PropertyUserStore.class); + private static final String JAR_FILE = "jar:file:"; + protected Path _configPath; protected Resource _configResource; @@ -111,7 +115,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener /** * Set the Config Path from a String reference to a file - * @param configFile the config file + * @param configFile the config file can a be a file path or a reference to a file within a jar file jar:file: */ public void setConfigPath(String configFile) { @@ -119,10 +123,39 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener { _configPath = null; } - else + else if (new File( configFile ).exists()) { _configPath = new File(configFile).toPath(); } + if ( !new File( configFile ).exists() && configFile.startsWith( JAR_FILE )) + { + // format of the url is jar:file:/foo/bar/beer.jar!/mountain_goat/pale_ale.txt + // ideally we'd like to extract this to Resource class? + try + { + _configPath = extractPackedFile( configFile ); + } + catch ( IOException e ) + { + throw new RuntimeException( "cannot extract file from url:" + configFile, e ); + } + } + } + + private Path extractPackedFile( String configFile ) + throws IOException + { + int fileIndex = configFile.indexOf( "!" ); + String entryPath = configFile.substring( fileIndex + 1, configFile.length() ); + + Path tmpDirectory = Files.createTempDirectory( "users_store" ); + Path extractedPath = Paths.get(tmpDirectory.toString(), entryPath); + // delete if exists as copyTo do not overwrite + Files.deleteIfExists( extractedPath ); + // delete on shutdown + extractedPath.toFile().deleteOnExit(); + JarResource.newResource( configFile ).copyTo( tmpDirectory.toFile() ); + return extractedPath; } /** diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java index 935df6f3e81..2f24ebb6d56 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java @@ -18,20 +18,6 @@ package org.eclipse.jetty.security; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.junit.Assume.*; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.Writer; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; @@ -40,6 +26,25 @@ import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + public class PropertyUserStoreTest { private final class UserCount implements PropertyUserStore.UserListener @@ -97,15 +102,55 @@ public class PropertyUserStoreTest Path dir = testdir.getPath().toRealPath(); FS.ensureDirExists(dir.toFile()); File users = dir.resolve("users.txt").toFile(); - - try (Writer writer = new BufferedWriter(new FileWriter(users))) + + writeUser( users ); + return users; + } + + private String initUsersPackedFileText() + throws Exception + { + Path dir = testdir.getPath().toRealPath(); + FS.ensureDirExists( dir.toFile() ); + File users = dir.resolve( "users.txt" ).toFile(); + writeUser( users ); + File usersJar = dir.resolve( "users.jar" ).toFile(); + String entryPath = "mountain_goat/pale_ale.txt"; + try (FileInputStream fileInputStream = new FileInputStream( users )) + { + try (OutputStream outputStream = new FileOutputStream( usersJar )) + { + try (JarOutputStream jarOutputStream = new JarOutputStream( outputStream )) + { + // add fake entry + jarOutputStream.putNextEntry( new JarEntry( "foo/wine" ) ); + + JarEntry jarEntry = new JarEntry( entryPath ); + jarOutputStream.putNextEntry( jarEntry ); + byte[] buffer = new byte[1024]; + int bytesRead; + while ( ( bytesRead = fileInputStream.read( buffer ) ) != -1 ) + { + jarOutputStream.write( buffer, 0, bytesRead ); + } + // add fake entry + jarOutputStream.putNextEntry( new JarEntry( "foo/cheese" ) ); + } + } + + } + return "jar:file:" + usersJar.getCanonicalPath() + "!/" + entryPath; + } + + private void writeUser(File usersFile) + throws Exception + { + try (Writer writer = new BufferedWriter(new FileWriter(usersFile))) { writer.append("tom: tom, roleA\n"); writer.append("dick: dick, roleB\n"); writer.append("harry: harry, roleA, roleB\n"); } - - return users; } private void addAdditionalUser(File usersFile, String userRef) throws Exception @@ -137,6 +182,30 @@ public class PropertyUserStoreTest userCount.awaitCount(3); } + @Test + public void testPropertyUserStoreLoadFromJarFile() throws Exception + { + final UserCount userCount = new UserCount(); + final String usersFile = initUsersPackedFileText(); + + PropertyUserStore store = new PropertyUserStore(); + store.setConfigPath(usersFile); + + store.registerUserListener(userCount); + + store.start(); + + assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", // + store.getUserIdentity("tom"), notNullValue()); + assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", // + store.getUserIdentity("dick"), notNullValue()); + assertThat("Failed to retrieve UserIdentity directly from PropertyUserStore", // + store.getUserIdentity("harry"), notNullValue()); + userCount.assertThatCount(is(3)); + userCount.awaitCount(3); + } + + @Test public void testPropertyUserStoreLoadUpdateUser() throws Exception {