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      */
HashUserRealm()85     public HashUserRealm()
86     {}
87 
88     /* ------------------------------------------------------------ */
89     /** Constructor.
90      * @param name Realm Name
91      */
HashUserRealm(String name)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      */
HashUserRealm(String name, String config)102     public HashUserRealm(String name, String config)
103         throws IOException
104     {
105         _realmName=name;
106         setConfig(config);
107     }
108 
getConfig()109     public String getConfig()
110     {
111         return _config;
112     }
113 
getConfigResource()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      */
setConfig(String config)127     public void setConfig(String config)
128         throws IOException
129     {
130         _config=config;
131         _configResource=Resource.newResource(_config);
132        loadConfig();
133 
134     }
135 
136 
setRefreshInterval(int msec)137     public void setRefreshInterval (int msec)
138     {
139         _refreshInterval=msec;
140     }
141 
getRefreshInterval()142     public int getRefreshInterval()
143     {
144         return _refreshInterval;
145     }
146 
loadConfig()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      */
setName(String name)193     public void setName(String name)
194     {
195         _realmName=name;
196     }
197 
198     /* ------------------------------------------------------------ */
199     /**
200      * @return The realm name.
201      */
getName()202     public String getName()
203     {
204         return _realmName;
205     }
206 
207     /* ------------------------------------------------------------ */
getPrincipal(String username)208     public Principal getPrincipal(String username)
209     {
210         return (Principal)_users.get(username);
211     }
212 
213     /* ------------------------------------------------------------ */
authenticate(String username,Object credentials,Request request)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     /* ------------------------------------------------------------ */
disassociate(Principal user)231     public void disassociate(Principal user)
232     {
233     }
234 
235     /* ------------------------------------------------------------ */
pushRole(Principal user, String role)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     /* ------------------------------------------------------------ */
popRole(Principal user)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      */
put(Object name, Object credentials)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      */
addUserToRole(String userName, String roleName)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     /* -------------------------------------------------------- */
reauthenticate(Principal user)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      */
isUserInRole(Principal user, String roleName)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     /* ------------------------------------------------------------ */
logout(Principal user)311     public void logout(Principal user)
312     {}
313 
314     /* ------------------------------------------------------------ */
toString()315     public String toString()
316     {
317         return "Realm["+_realmName+"]=="+_users.keySet();
318     }
319 
320     /* ------------------------------------------------------------ */
dump(PrintStream out)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      */
getSSORealm()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      */
setSSORealm(SSORealm ssoRealm)342     public void setSSORealm(SSORealm ssoRealm)
343     {
344         _ssoRealm = ssoRealm;
345     }
346 
347     /* ------------------------------------------------------------ */
getSingleSignOn(Request request,Response response)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     /* ------------------------------------------------------------ */
setSingleSignOn(Request request,Response response,Principal principal,Credential credential)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     /* ------------------------------------------------------------ */
clearSingleSignOn(String username)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      */
doStart()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      */
doStop()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         /* ------------------------------------------------------------ */
getUserRealm()452         private UserRealm getUserRealm()
453         {
454             return HashUserRealm.this;
455         }
456 
getName()457         public String getName()
458         {
459             return "Anonymous";
460         }
461 
isAuthenticated()462         public boolean isAuthenticated()
463         {
464             return false;
465         }
466 
toString()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         /* -------------------------------------------------------- */
KnownUser(String name,Credential credential)482         KnownUser(String name,Credential credential)
483         {
484             _userName=name;
485             _cred=credential;
486         }
487 
488         /* -------------------------------------------------------- */
authenticate(Object credentials)489         boolean authenticate(Object credentials)
490         {
491             return _cred!=null && _cred.check(credentials);
492         }
493 
494         /* ------------------------------------------------------------ */
getName()495         public String getName()
496         {
497             return _userName;
498         }
499 
500         /* -------------------------------------------------------- */
isAuthenticated()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 
WrappedUser(Principal user, String role)515         WrappedUser(Principal user, String role)
516         {
517             this.user=user;
518             this.role=role;
519         }
520 
getUserPrincipal()521         Principal getUserPrincipal()
522         {
523             return user;
524         }
525 
getName()526         public String getName()
527         {
528             return "role:"+role;
529         }
530 
isAuthenticated()531         public boolean isAuthenticated()
532         {
533             return true;
534         }
535 
isUserInRole(String role)536         public boolean isUserInRole(String role)
537         {
538             return this.role.equals(role);
539         }
540     }
541 }
542