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