View Javadoc

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