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