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