1   // ========================================================================
2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.security;
16  
17  import java.io.File;
18  import java.io.FilenameFilter;
19  import java.io.IOException;
20  import java.io.PrintStream;
21  import java.security.Principal;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.StringTokenizer;
30  
31  import org.mortbay.component.AbstractLifeCycle;
32  import org.mortbay.jetty.Request;
33  import org.mortbay.jetty.Response;
34  import org.mortbay.log.Log;
35  import org.mortbay.resource.Resource;
36  import org.mortbay.util.Scanner;
37  import org.mortbay.util.Scanner.BulkListener;
38  
39  /* ------------------------------------------------------------ */
40  /** HashMapped User Realm.
41   *
42   * An implementation of UserRealm that stores users and roles in-memory in
43   * HashMaps.
44   * <P>
45   * Typically these maps are populated by calling the load() method or passing
46   * a properties resource to the constructor. The format of the properties
47   * file is: <PRE>
48   *  username: password [,rolename ...]
49   * </PRE>
50   * Passwords may be clear text, obfuscated or checksummed.  The class 
51   * com.mortbay.Util.Password should be used to generate obfuscated
52   * passwords or password checksums.
53   * 
54   * If DIGEST Authentication is used, the password must be in a recoverable
55   * format, either plain text or OBF:.
56   *
57   * The HashUserRealm also implements SSORealm but provides no implementation
58   * of SSORealm. Instead setSSORealm may be used to provide a delegate
59   * SSORealm implementation. 
60   *
61   * @see Password
62   * @author Greg Wilkins (gregw)
63   */
64  public class HashUserRealm extends AbstractLifeCycle implements UserRealm, SSORealm
65  {
66  
67      /** HttpContext Attribute to set to activate SSO.
68       */
69      public static final String __SSO = "org.mortbay.http.SSO";
70      
71      /* ------------------------------------------------------------ */
72      private String _realmName;
73      private String _config;
74      private Resource _configResource;
75      protected HashMap _users=new HashMap();
76      protected HashMap _roles=new HashMap(7);
77      private SSORealm _ssoRealm;
78      private Scanner _scanner;
79      private int _refreshInterval=0;//default is not to reload
80      
81  
82      /* ------------------------------------------------------------ */
83      /** Constructor. 
84       */
85      public HashUserRealm()
86      {}
87      
88      /* ------------------------------------------------------------ */
89      /** Constructor. 
90       * @param name Realm Name
91       */
92      public HashUserRealm(String name)
93      {
94          _realmName=name;
95      }
96      
97      /* ------------------------------------------------------------ */
98      /** Constructor. 
99       * @param name Realm name
100      * @param config Filename or url of user properties file.
101      */
102     public HashUserRealm(String name, String config)
103         throws IOException
104     {
105         _realmName=name;
106         setConfig(config);
107     }
108     
109     public String getConfig()
110     {
111         return _config;
112     }
113 
114     /* ------------------------------------------------------------ */
115     /** Load realm users from properties file.
116      * The property file maps usernames to password specs followed by
117      * an optional comma separated list of role names.
118      *
119      * @param config Filename or url of user properties file.
120      * @exception IOException 
121      */
122     public void setConfig(String config)
123         throws IOException
124     {
125         _config=config;
126         _configResource=Resource.newResource(_config);
127        loadConfig();
128  
129     }
130     
131 
132     public void setRefreshInterval (int msec)
133     {
134         _refreshInterval=msec;
135     }
136     
137     public int getRefreshInterval()
138     {
139         return _refreshInterval;
140     }
141     
142     public void loadConfig () 
143     throws IOException
144     {
145         synchronized (this)
146         {
147             _users.clear();
148             _roles.clear();
149             
150             if(Log.isDebugEnabled())Log.debug("Load "+this+" from "+_config);
151             Properties properties = new Properties();
152             properties.load(_configResource.getInputStream());
153 
154             Iterator iter = properties.entrySet().iterator();
155             while(iter.hasNext())
156             {
157                 Map.Entry entry = (Map.Entry)iter.next();
158 
159                 String username=entry.getKey().toString().trim();
160                 String credentials=entry.getValue().toString().trim();
161                 String roles=null;
162                 int c=credentials.indexOf(',');
163                 if (c>0)
164                 {
165                     roles=credentials.substring(c+1).trim();
166                     credentials=credentials.substring(0,c).trim();
167                 }
168 
169                 if (username!=null && username.length()>0 &&
170                         credentials!=null && credentials.length()>0)
171                 {
172                     put(username,credentials);
173                     if(roles!=null && roles.length()>0)
174                     {
175                         StringTokenizer tok = new StringTokenizer(roles,", ");
176                         while (tok.hasMoreTokens())
177                             addUserToRole(username,tok.nextToken());
178                     }
179                 }
180             }
181         }
182     }
183     
184     /* ------------------------------------------------------------ */
185     /** 
186      * @param name The realm name 
187      */
188     public void setName(String name)
189     {
190         _realmName=name;
191     }
192     
193     /* ------------------------------------------------------------ */
194     /** 
195      * @return The realm name. 
196      */
197     public String getName()
198     {
199         return _realmName;
200     }
201 
202     /* ------------------------------------------------------------ */
203     public Principal getPrincipal(String username)
204     {
205         return (Principal)_users.get(username);
206     }
207     
208     /* ------------------------------------------------------------ */
209     public Principal authenticate(String username,Object credentials,Request request)
210     {
211         KnownUser user;
212         synchronized (this)
213         {
214             user = (KnownUser)_users.get(username);
215         }
216         if (user==null)
217             return null;
218         
219         if (user.authenticate(credentials))
220             return user;
221         
222         return null;
223     }
224     
225     /* ------------------------------------------------------------ */
226     public void disassociate(Principal user)
227     {
228     }
229     
230     /* ------------------------------------------------------------ */
231     public Principal pushRole(Principal user, String role)
232     {
233         if (user==null)
234             user=new User();
235         
236         return new WrappedUser(user,role);
237     }
238 
239     /* ------------------------------------------------------------ */
240     public Principal popRole(Principal user)
241     {
242         WrappedUser wu = (WrappedUser)user;
243         return wu.getUserPrincipal();
244     }
245     
246     /* ------------------------------------------------------------ */
247     /** Put user into realm.
248      * @param name User name
249      * @param credentials String password, Password or UserPrinciple
250      *                    instance. 
251      * @return Old UserPrinciple value or null
252      */
253     public synchronized Object put(Object name, Object credentials)
254     {
255         if (credentials instanceof Principal)
256             return _users.put(name.toString(),credentials);
257         
258         if (credentials instanceof Password)
259             return _users.put(name,new KnownUser(name.toString(),(Password)credentials));
260         if (credentials != null)
261             return _users.put(name,new KnownUser(name.toString(),Credential.getCredential(credentials.toString())));
262         return null;
263     }
264 
265     /* ------------------------------------------------------------ */
266     /** Add a user to a role.
267      * @param userName 
268      * @param roleName 
269      */
270     public synchronized void addUserToRole(String userName, String roleName)
271     {
272         HashSet userSet = (HashSet)_roles.get(roleName);
273         if (userSet==null)
274         {
275             userSet=new HashSet(11);
276             _roles.put(roleName,userSet);
277         }
278         userSet.add(userName);
279     }
280     
281     /* -------------------------------------------------------- */
282     public boolean reauthenticate(Principal user)
283     {
284         return ((User)user).isAuthenticated();
285     }
286     
287     /* ------------------------------------------------------------ */
288     /** Check if a user is in a role.
289      * @param user The user, which must be from this realm 
290      * @param roleName 
291      * @return True if the user can act in the role.
292      */
293     public synchronized boolean isUserInRole(Principal user, String roleName)
294     {
295         if (user instanceof WrappedUser)
296             return ((WrappedUser)user).isUserInRole(roleName);
297          
298         if (user==null || !(user instanceof User) || ((User)user).getUserRealm()!=this)
299             return false;
300         
301         HashSet userSet = (HashSet)_roles.get(roleName);
302         return userSet!=null && userSet.contains(user.getName());
303     }
304 
305     /* ------------------------------------------------------------ */
306     public void logout(Principal user)
307     {}
308     
309     /* ------------------------------------------------------------ */
310     public String toString()
311     {
312         return "Realm["+_realmName+"]=="+_users.keySet();
313     }
314     
315     /* ------------------------------------------------------------ */
316     public void dump(PrintStream out)
317     {
318         out.println(this+":");
319         out.println(super.toString());
320         out.println(_roles);
321     }
322     
323     /* ------------------------------------------------------------ */
324     /** 
325      * @return The SSORealm to delegate single sign on requests to.
326      */
327     public SSORealm getSSORealm()
328     {
329         return _ssoRealm;
330     }
331     
332     /* ------------------------------------------------------------ */
333     /** Set the SSORealm.
334      * A SSORealm implementation may be set to enable support for SSO.
335      * @param ssoRealm The SSORealm to delegate single sign on requests to.
336      */
337     public void setSSORealm(SSORealm ssoRealm)
338     {
339         _ssoRealm = ssoRealm;
340     }
341     
342     /* ------------------------------------------------------------ */
343     public Credential getSingleSignOn(Request request,Response response)
344     {
345         if (_ssoRealm!=null)
346             return _ssoRealm.getSingleSignOn(request,response);
347         return null;
348     }
349     
350     /* ------------------------------------------------------------ */
351     public void setSingleSignOn(Request request,Response response,Principal principal,Credential credential)
352     {
353         if (_ssoRealm!=null)
354             _ssoRealm.setSingleSignOn(request,response,principal,credential);
355     }
356     
357     /* ------------------------------------------------------------ */
358     public void clearSingleSignOn(String username)
359     {
360         if (_ssoRealm!=null)
361             _ssoRealm.clearSingleSignOn(username);
362     }
363     
364   
365     
366     
367     
368     /** 
369      * @see org.mortbay.component.AbstractLifeCycle#doStart()
370      */
371     protected void doStart() throws Exception
372     {
373         super.doStart();
374         if (_scanner!=null)
375             _scanner.stop(); 
376 
377         if (getRefreshInterval() > 0)
378         {
379             _scanner = new Scanner();
380             _scanner.setScanInterval(getRefreshInterval());
381             List dirList = new ArrayList(1);
382             dirList.add(_configResource.getFile());
383             _scanner.setScanDirs(dirList);
384             _scanner.setFilenameFilter(new FilenameFilter ()
385             {
386                 public boolean accept(File dir, String name)
387                 {
388                     File f = new File(dir,name);
389                     try
390                     {
391                         if (f.compareTo(_configResource.getFile())==0)
392                             return true;
393                     }
394                     catch (IOException e)
395                     {
396                         return false;
397                     }
398 
399                     return false;
400                 }
401 
402             });
403             _scanner.addListener(new BulkListener()
404             {
405                 public void filesChanged(List filenames) throws Exception
406                 {
407                     if (filenames==null)
408                         return;
409                     if (filenames.isEmpty())
410                         return;
411                     if (filenames.size()==1 && filenames.get(0).equals(_config))
412                         loadConfig();
413                 }
414                 public String toString()
415                 {
416                     return "HashUserRealm$Scanner";
417                 }
418 
419             });
420             _scanner.setReportExistingFilesOnStartup(false);
421             _scanner.setRecursive(false);
422             _scanner.start();
423         }
424     }
425 
426     /** 
427      * @see org.mortbay.component.AbstractLifeCycle#doStop()
428      */
429     protected void doStop() throws Exception
430     {
431         super.doStop();
432         if (_scanner!=null)
433             _scanner.stop();
434         _scanner=null;
435     }
436 
437 
438 
439     /* ------------------------------------------------------------ */
440     /* ------------------------------------------------------------ */
441     /* ------------------------------------------------------------ */
442     private class User implements Principal
443     {
444         List roles=null;
445 
446         /* ------------------------------------------------------------ */
447         private UserRealm getUserRealm()
448         {
449             return HashUserRealm.this;
450         }
451         
452         public String getName()
453         {
454             return "Anonymous";
455         }
456                 
457         public boolean isAuthenticated()
458         {
459             return false;
460         }
461         
462         public String toString()
463         {
464             return getName();
465         }        
466     }
467     
468     /* ------------------------------------------------------------ */
469     /* ------------------------------------------------------------ */
470     /* ------------------------------------------------------------ */
471     private class KnownUser extends User
472     {
473         private String _userName;
474         private Credential _cred;
475         
476         /* -------------------------------------------------------- */
477         KnownUser(String name,Credential credential)
478         {
479             _userName=name;
480             _cred=credential;
481         }
482         
483         /* -------------------------------------------------------- */
484         boolean authenticate(Object credentials)
485         {
486             return _cred!=null && _cred.check(credentials);
487         }
488         
489         /* ------------------------------------------------------------ */
490         public String getName()
491         {
492             return _userName;
493         }
494         
495         /* -------------------------------------------------------- */
496         public boolean isAuthenticated()
497         {
498             return true;
499         }
500     }
501 
502     /* ------------------------------------------------------------ */
503     /* ------------------------------------------------------------ */
504     /* ------------------------------------------------------------ */
505     private class WrappedUser extends User
506     {   
507         private Principal user;
508         private String role;
509 
510         WrappedUser(Principal user, String role)
511         {
512             this.user=user;
513             this.role=role;
514         }
515 
516         Principal getUserPrincipal()
517         {
518             return user;    
519         }
520 
521         public String getName()
522         {
523             return "role:"+role;
524         }
525                 
526         public boolean isAuthenticated()
527         {
528             return true;
529         }
530         
531         public boolean isUserInRole(String role)
532         {
533             return this.role.equals(role);
534         }
535     }
536 }