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