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 }