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
This commit is contained in:
Jesse McConnell 2010-10-12 23:54:58 +00:00
parent 3df18cb180
commit fd90e82e64
2 changed files with 469 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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());
}
}