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