Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse McConnell2010-10-12 23:54:58 +0000
committerJesse McConnell2010-10-12 23:54:58 +0000
commitfd90e82e6406be4063270d5fa5da8858af38a7ed (patch)
tree9fe83439f7fda88cb8db0310ef6270a9966b093c
parent3df18cb180363dd730366c3469620ee6386d6b5f (diff)
downloadorg.eclipse.jetty.project-fd90e82e6406be4063270d5fa5da8858af38a7ed.tar.gz
org.eclipse.jetty.project-fd90e82e6406be4063270d5fa5da8858af38a7ed.tar.xz
org.eclipse.jetty.project-fd90e82e6406be4063270d5fa5da8858af38a7ed.zip
Bug 323311 adding new property user store object with shared logic between HashLoginService and PropertyFileLoginModule, initial commit is the new class and associated unit test, need to test other integrations before committing them
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2341 7e9141cc-0065-0410-87d8-b60c137991c4
-rw-r--r--jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java302
-rw-r--r--jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java167
2 files changed, 469 insertions, 0 deletions
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
new file mode 100644
index 0000000000..4808a029cc
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
@@ -0,0 +1,302 @@
+package org.eclipse.jetty.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.eclipse.jetty.http.security.Credential;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * PropertyUserStore
+ *
+ * This class monitors a property file of the format mentioned below and
+ * notifies registered listeners of the changes to the the given file.
+ *
+ * <PRE>
+ * username: password [,rolename ...]
+ * </PRE>
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class
+ * com.eclipse.Util.Password should be used to generate obfuscated passwords or
+ * password checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable
+ * format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+ private String _config;
+ private Resource _configResource;
+ private Scanner _scanner;
+ private int _refreshInterval = 0;// default is not to reload
+
+ private boolean _firstLoad = true; // true if first load, false from that point on
+ private final List<String> _knownUsers=new ArrayList<String>();
+ private List<UserListener> _listeners;
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setConfig(String config)
+ {
+ _config=config;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * returns the resource associated with the configured properties
+ * file, creating it if necessary
+ */
+ public Resource getConfigResource() throws IOException
+ {
+ if ( _configResource == null )
+ {
+ _configResource = Resource.newResource(_config);
+ }
+
+ return _configResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * sets the refresh interval (in seconds)
+ */
+ public void setRefreshInterval(int msec)
+ {
+ _refreshInterval = msec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * refresh interval in seconds for how often the properties
+ * file should be checked for changes
+ */
+ public int getRefreshInterval()
+ {
+ return _refreshInterval;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private void loadUsers() throws IOException
+ {
+ if (_config==null)
+ return;
+
+ if (Log.isDebugEnabled()) Log.debug("Load " + this + " from " + _config);
+ Properties properties = new Properties();
+ properties.load(getConfigResource().getInputStream());
+ Set<String> known = new HashSet<String>();
+
+ for (Map.Entry<Object, Object> entry : properties.entrySet())
+ {
+ String username = ((String) entry.getKey()).trim();
+ String credentials = ((String) entry.getValue()).trim();
+ String roles = null;
+ int c = credentials.indexOf(',');
+ if (c > 0)
+ {
+ roles = credentials.substring(c + 1).trim();
+ credentials = credentials.substring(0, c).trim();
+ }
+
+ if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+ {
+ String[] roleArray = IdentityService.NO_ROLES;
+ if (roles != null && roles.length() > 0)
+ roleArray = roles.split(",");
+ known.add(username);
+ notifyUpdate(username,Credential.getCredential(credentials),roleArray);
+ }
+ }
+
+ synchronized (_knownUsers)
+ {
+ /*
+ * if its not the initial load then we want to process removed users
+ */
+ if (!_firstLoad)
+ {
+ Iterator<String> users = _knownUsers.iterator();
+ while (users.hasNext())
+ {
+ String user = users.next();
+ if (!known.contains(user))
+ {
+ notifyRemove(user);
+ }
+ }
+ }
+
+ /*
+ * reset the tracked _users list to the known users we just processed
+ */
+
+ _knownUsers.clear();
+ _knownUsers.addAll(known);
+
+ }
+
+ /*
+ * set initial load to false as there should be no more initial loads
+ */
+ _firstLoad = false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Depending on the value of the refresh interval, this method will either
+ * start up a scanner thread that will monitor the properties file
+ * for changes after it has initially loaded it. Otherwise the users
+ * will be loaded and there will be no active monitoring thread so changes
+ * will not be detected.
+ *
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (getRefreshInterval() > 0)
+ {
+ _scanner = new Scanner();
+ _scanner.setScanInterval(getRefreshInterval());
+ List<File> dirList = new ArrayList<File>(1);
+ dirList.add(getConfigResource().getFile().getParentFile());
+ _scanner.setScanDirs(dirList);
+ _scanner.setFilenameFilter(new FilenameFilter()
+ {
+ public boolean accept(File dir, String name)
+ {
+ File f = new File(dir, name);
+ try
+ {
+ if (f.compareTo(getConfigResource().getFile()) == 0)
+ {
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+
+ return false;
+ }
+
+ });
+
+ _scanner.addListener(new BulkListener()
+ {
+ public void filesChanged(List filenames) throws Exception
+ {
+ if (filenames == null) return;
+ if (filenames.isEmpty()) return;
+ if (filenames.size() == 1 && filenames.get(0).equals(getConfigResource().getFile().getAbsolutePath()))
+ {
+ loadUsers();
+ }
+ }
+
+ public String toString()
+ {
+ return "PropertyUserStore$Scanner";
+ }
+
+ });
+
+ _scanner.setReportExistingFilesOnStartup(true);
+ _scanner.setRecursive(false);
+ _scanner.start();
+ }
+ else
+ {
+ loadUsers();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_scanner != null) _scanner.stop();
+ _scanner = null;
+ }
+
+ /**
+ * Notifies the registered listeners of potential updates to a user
+ *
+ * @param username
+ * @param credential
+ * @param roleArray
+ */
+ private void notifyUpdate(String username, Credential credential, String[] roleArray)
+ {
+ if (_listeners != null)
+ {
+ for ( Iterator<UserListener> i = _listeners.iterator();i.hasNext();)
+ {
+ i.next().update(username,credential,roleArray);
+ }
+ }
+ }
+
+ /**
+ * notifies the registered listeners that a user has been removed.
+ *
+ * @param username
+ */
+ private void notifyRemove(String username)
+ {
+ if (_listeners != null)
+ {
+ for ( Iterator<UserListener> i = _listeners.iterator();i.hasNext();)
+ {
+ i.next().remove(username);
+ }
+ }
+ }
+
+ /**
+ * registers a listener to be notified of the contents of the property file
+ */
+ public void registerUserListener(UserListener listener)
+ {
+ if (_listeners == null)
+ {
+ _listeners = new ArrayList<UserListener>();
+ }
+ _listeners.add(listener);
+ }
+
+ /**
+ * UserListener
+ */
+ public interface UserListener
+ {
+ public void update(String username, Credential credential, String[] roleArray );
+
+ public void remove(String username);
+ }
+}
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
new file mode 100644
index 0000000000..279d015015
--- /dev/null
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java
@@ -0,0 +1,167 @@
+package org.eclipse.jetty.security;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.Assert;
+
+import org.eclipse.jetty.http.security.Credential;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PropertyUserStoreTest
+{
+ String testFileDir="target" + File.separator + "property-user-store-test";
+ String testFile = testFileDir + File.separator + "users.txt";
+
+ @Before
+ public void before() throws Exception
+ {
+ File file = new File(testFileDir);
+ file.mkdirs();
+
+ writeInitialUsers(testFile);
+ }
+
+ @After
+ public void after() throws Exception
+ {
+ File file = new File(testFile);
+
+ file.delete();
+ }
+
+ private void writeInitialUsers(String testFile) throws Exception
+ {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(testFile));
+ writer.append("tom: tom, roleA\n");
+ writer.append("dick: dick, roleB\n");
+ writer.append("harry: harry, roleA, roleB\n");
+ writer.close();
+ }
+
+ private void writeAdditionalUser(String testFile) throws Exception
+ {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(testFile, true));
+ writer.append("skip: skip, roleA\n");
+ writer.close();
+ }
+
+
+ @Test
+ public void testPropertyUserStoreLoad() throws Exception
+ {
+ final AtomicInteger userCount = new AtomicInteger();
+
+ PropertyUserStore store = new PropertyUserStore();
+
+ store.setConfig(testFile);
+
+ store.registerUserListener(new PropertyUserStore.UserListener() {
+
+ public void update(String username, Credential credential, String[] roleArray)
+ {
+ userCount.getAndIncrement();
+ }
+
+ public void remove(String username)
+ {
+
+ }
+ });
+
+ store.start();
+
+ Assert.assertEquals(3, userCount.get());
+ }
+
+ @Test
+ public void testPropertyUserStoreLoadUpdateUser() throws Exception
+ {
+
+ final AtomicInteger userCount = new AtomicInteger();
+
+ final List<String> users = new ArrayList<String>();
+
+ PropertyUserStore store = new PropertyUserStore();
+ store.setRefreshInterval(2);
+ store.setConfig(testFile);
+
+ store.registerUserListener(new PropertyUserStore.UserListener() {
+
+ public void update(String username, Credential credential, String[] roleArray)
+ {
+ if ( !users.contains(username))
+ {
+ users.add(username);
+ userCount.getAndIncrement();
+ }
+ }
+
+ public void remove(String username)
+ {
+
+ }
+ });
+
+ store.start();
+
+ Assert.assertEquals(3, userCount.get());
+
+ store.start();
+
+ Thread.sleep(2000);
+
+ writeAdditionalUser(testFile);
+
+ Thread.sleep(2000);
+
+ Assert.assertEquals(4, userCount.get());
+ Assert.assertTrue(users.contains("skip"));
+ }
+
+ @Test
+ public void testPropertyUserStoreLoadRemoveUser() throws Exception
+ {
+ writeAdditionalUser(testFile);
+
+ final AtomicInteger userCount = new AtomicInteger();
+
+ final List<String> users = new ArrayList<String>();
+
+ PropertyUserStore store = new PropertyUserStore();
+ store.setRefreshInterval(2);
+ store.setConfig(testFile);
+
+ store.registerUserListener(new PropertyUserStore.UserListener() {
+
+ public void update(String username, Credential credential, String[] roleArray) {
+ if ( !users.contains(username))
+ {
+ users.add(username);
+ userCount.getAndIncrement();
+ }
+ }
+
+ public void remove(String username) {
+ users.remove(username);
+ userCount.getAndDecrement();
+ }
+ });
+
+ store.start();
+
+ Assert.assertEquals(4, userCount.get());
+
+ Thread.sleep(2000);
+ writeInitialUsers(testFile);
+ Thread.sleep(3000);
+ Assert.assertEquals(3, userCount.get());
+ }
+
+}

Back to the top