View Javadoc

1   package org.eclipse.jetty.security;
2   
3   import java.io.File;
4   import java.io.FilenameFilter;
5   import java.io.IOException;
6   import java.util.ArrayList;
7   import java.util.HashSet;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Properties;
12  import java.util.Set;
13  
14  import org.eclipse.jetty.http.security.Credential;
15  import org.eclipse.jetty.util.Scanner;
16  import org.eclipse.jetty.util.Scanner.BulkListener;
17  import org.eclipse.jetty.util.component.AbstractLifeCycle;
18  import org.eclipse.jetty.util.log.Log;
19  import org.eclipse.jetty.util.log.Logger;
20  import org.eclipse.jetty.util.resource.Resource;
21  
22  /**
23   * PropertyUserStore
24   * 
25   * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
26   * 
27   * <PRE>
28   *  username: password [,rolename ...]
29   * </PRE>
30   * 
31   * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
32   * checksums.
33   * 
34   * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
35   */
36  public class PropertyUserStore extends AbstractLifeCycle
37  {
38      private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
39  
40      private String _config;
41      private Resource _configResource;
42      private Scanner _scanner;
43      private int _refreshInterval = 0;// default is not to reload
44  
45      private boolean _firstLoad = true; // true if first load, false from that point on
46      private final List<String> _knownUsers = new ArrayList<String>();
47      private List<UserListener> _listeners;
48  
49      /* ------------------------------------------------------------ */
50      public String getConfig()
51      {
52          return _config;
53      }
54  
55      /* ------------------------------------------------------------ */
56      public void setConfig(String config)
57      {
58          _config = config;
59      }
60  
61      /* ------------------------------------------------------------ */
62      /**
63       * returns the resource associated with the configured properties file, creating it if necessary
64       */
65      public Resource getConfigResource() throws IOException
66      {
67          if (_configResource == null)
68          {
69              _configResource = Resource.newResource(_config);
70          }
71  
72          return _configResource;
73      }
74  
75      /* ------------------------------------------------------------ */
76      /**
77       * sets the refresh interval (in seconds)
78       */
79      public void setRefreshInterval(int msec)
80      {
81          _refreshInterval = msec;
82      }
83  
84      /* ------------------------------------------------------------ */
85      /**
86       * refresh interval in seconds for how often the properties file should be checked for changes
87       */
88      public int getRefreshInterval()
89      {
90          return _refreshInterval;
91      }
92  
93      /* ------------------------------------------------------------ */
94      private void loadUsers() throws IOException
95      {
96          if (_config == null)
97              return;
98  
99          if (LOG.isDebugEnabled())
100             LOG.debug("Load " + this + " from " + _config);
101         Properties properties = new Properties();
102         if (getConfigResource().exists())
103             properties.load(getConfigResource().getInputStream());
104         Set<String> known = new HashSet<String>();
105 
106         for (Map.Entry<Object, Object> entry : properties.entrySet())
107         {
108             String username = ((String)entry.getKey()).trim();
109             String credentials = ((String)entry.getValue()).trim();
110             String roles = null;
111             int c = credentials.indexOf(',');
112             if (c > 0)
113             {
114                 roles = credentials.substring(c + 1).trim();
115                 credentials = credentials.substring(0,c).trim();
116             }
117 
118             if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
119             {
120                 String[] roleArray = IdentityService.NO_ROLES;
121                 if (roles != null && roles.length() > 0)
122                     roleArray = roles.split(",");
123                 known.add(username);
124                 notifyUpdate(username,Credential.getCredential(credentials),roleArray);
125             }
126         }
127 
128         synchronized (_knownUsers)
129         {
130             /*
131              * if its not the initial load then we want to process removed users
132              */
133             if (!_firstLoad)
134             {
135                 Iterator<String> users = _knownUsers.iterator();
136                 while (users.hasNext())
137                 {
138                     String user = users.next();
139                     if (!known.contains(user))
140                     {
141                         notifyRemove(user);
142                     }
143                 }
144             }
145 
146             /*
147              * reset the tracked _users list to the known users we just processed
148              */
149 
150             _knownUsers.clear();
151             _knownUsers.addAll(known);
152 
153         }
154 
155         /*
156          * set initial load to false as there should be no more initial loads
157          */
158         _firstLoad = false;
159     }
160 
161     /* ------------------------------------------------------------ */
162     /**
163      * 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
164      * 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.
165      * 
166      * 
167      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
168      */
169     protected void doStart() throws Exception
170     {
171         super.doStart();
172 
173         if (getRefreshInterval() > 0)
174         {
175             _scanner = new Scanner();
176             _scanner.setScanInterval(getRefreshInterval());
177             List<File> dirList = new ArrayList<File>(1);
178             dirList.add(getConfigResource().getFile().getParentFile());
179             _scanner.setScanDirs(dirList);
180             _scanner.setFilenameFilter(new FilenameFilter()
181             {
182                 public boolean accept(File dir, String name)
183                 {
184                     File f = new File(dir,name);
185                     try
186                     {
187                         if (f.compareTo(getConfigResource().getFile()) == 0)
188                         {
189                             return true;
190                         }
191                     }
192                     catch (IOException e)
193                     {
194                         return false;
195                     }
196 
197                     return false;
198                 }
199 
200             });
201 
202             _scanner.addListener(new BulkListener()
203             {
204                 public void filesChanged(List filenames) throws Exception
205                 {
206                     if (filenames == null)
207                         return;
208                     if (filenames.isEmpty())
209                         return;
210                     if (filenames.size() == 1 && filenames.get(0).equals(getConfigResource().getFile().getAbsolutePath()))
211                     {
212                         loadUsers();
213                     }
214                 }
215 
216                 public String toString()
217                 {
218                     return "PropertyUserStore$Scanner";
219                 }
220 
221             });
222 
223             _scanner.setReportExistingFilesOnStartup(true);
224             _scanner.setRecursive(false);
225             _scanner.start();
226         }
227         else
228         {
229             loadUsers();
230         }
231     }
232 
233     /* ------------------------------------------------------------ */
234     /**
235      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
236      */
237     protected void doStop() throws Exception
238     {
239         super.doStop();
240         if (_scanner != null)
241             _scanner.stop();
242         _scanner = null;
243     }
244 
245     /**
246      * Notifies the registered listeners of potential updates to a user
247      * 
248      * @param username
249      * @param credential
250      * @param roleArray
251      */
252     private void notifyUpdate(String username, Credential credential, String[] roleArray)
253     {
254         if (_listeners != null)
255         {
256             for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
257             {
258                 i.next().update(username,credential,roleArray);
259             }
260         }
261     }
262 
263     /**
264      * notifies the registered listeners that a user has been removed.
265      * 
266      * @param username
267      */
268     private void notifyRemove(String username)
269     {
270         if (_listeners != null)
271         {
272             for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
273             {
274                 i.next().remove(username);
275             }
276         }
277     }
278 
279     /**
280      * registers a listener to be notified of the contents of the property file
281      */
282     public void registerUserListener(UserListener listener)
283     {
284         if (_listeners == null)
285         {
286             _listeners = new ArrayList<UserListener>();
287         }
288         _listeners.add(listener);
289     }
290 
291     /**
292      * UserListener
293      */
294     public interface UserListener
295     {
296         public void update(String username, Credential credential, String[] roleArray);
297 
298         public void remove(String username);
299     }
300 }