1 /* 2 * The Apache Software License, Version 1.1 3 * 4 * Copyright (c) 1999 The Apache Software Foundation. All rights 5 * reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * 3. The end-user documentation included with the redistribution, if 20 * any, must include the following acknowlegement: 21 * "This product includes software developed by the 22 * Apache Software Foundation (http://www.apache.org/)." 23 * Alternately, this acknowlegement may appear in the software itself, 24 * if and wherever such third-party acknowlegements normally appear. 25 * 26 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software 27 * Foundation" must not be used to endorse or promote products derived 28 * from this software without prior written permission. For written 29 * permission, please contact apache@apache.org. 30 * 31 * 5. Products derived from this software may not be called "Apache" 32 * nor may "Apache" appear in their names without prior written 33 * permission of the Apache Group. 34 * 35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 46 * SUCH DAMAGE. 47 * ==================================================================== 48 * 49 * This software consists of voluntary contributions made by many 50 * individuals on behalf of the Apache Software Foundation. For more 51 * information on the Apache Software Foundation, please see 52 * <http://www.apache.org/>. 53 * 54 * [Additional notices, if required by prior licensing conditions] 55 * 56 */ 57 58 59 package org.apache.tomcat.security.file; 60 61 import java.io.ByteArrayOutputStream; 62 import java.io.InputStream; 63 import java.io.IOException; 64 import java.io.OutputStream; 65 import java.io.PrintWriter; 66 import java.util.Enumeration; 67 import java.util.Hashtable; 68 import java.util.Vector; 69 import org.apache.tomcat.util.HexUtils; 70 import org.apache.tomcat.util.StringManager; 71 import org.apache.tomcat.util.XMLParser; 72 import org.apache.tomcat.util.XMLTree; 73 import org.xml.sax.SAXException; 74 import org.xml.sax.SAXParseException; 75 76 /** 77 * In-memory cache of the set of users, groups, and their associated roles, 78 * stored in an XML-formatted file that conforms to DTD found in the 79 * <code>tomcat-users.dtd</code> file in this directory. 80 * 81 * @author Craig R. McClanahan 82 * @version $Revision: 1.2 $ $Date: 2000/02/26 02:32:14 $ 83 */ 84 85 public final class FileRealmDatabase { 86 87 88 /** 89 * The set of groups defined within this database, keyed by group name. 90 */ 91 private Hashtable groups = new Hashtable(); 92 93 94 /** 95 * The set of roles defined within this database, keyed by role name. 96 * The value objects are arbitrary. 97 */ 98 private Hashtable roles = new Hashtable(); 99 100 101 /** 102 * The internationalized string constants for this package. 103 */ 104 private StringManager sm = 105 StringManager.getManager(Constants.Package); 106 107 108 /** 109 * The set of users defined within this database, keyed by username. 110 */ 111 private Hashtable users = new Hashtable(); 112 113 114 /** 115 * Construct a new empty database. 116 */ 117 public FileRealmDatabase() { 118 119 super(); 120 121 } 122 123 124 /** 125 * Construct a new database initialized from the specified input stream 126 * 127 * @param stream Stream from which to load the contents of this database 128 * 129 * @exception IOException if an input/output error occurs 130 * @exception SAXParseException if a parsing exception occurs 131 * @exception SAXException if a processing exception occurs 132 */ 133 public FileRealmDatabase(InputStream stream) 134 throws IOException, SAXParseException, SAXException { 135 136 super(); 137 read(stream); 138 139 } 140 141 142 /** 143 * [Package Private] Add this group to the set of defined groups. 144 */ 145 void addGroup(FileRealmGroup group) { 146 147 groups.put(group.getName(), group); 148 149 } 150 151 152 /** 153 * [Package Private] Add this role to the set of defined roles. 154 */ 155 void addRole(String role) { 156 157 roles.put(role, role); 158 159 } 160 161 162 /** 163 * [Package Private] Add this user to the set of defined users. 164 */ 165 void addUser(FileRealmUser user) { 166 167 users.put(user.getName(), user); 168 169 } 170 171 172 /** 173 * Create and return a new group. 174 * 175 * @param name Group name of the newly created group 176 * 177 * @exception IllegalArgumentException if this group name is already in use 178 */ 179 public FileRealmGroup createGroup(String name) { 180 181 if (getGroup(name) != null) 182 throw new IllegalArgumentException( 183 sm.getString("file.createGroup.exists", name)); 184 185 return (new FileRealmGroup(this, name)); 186 187 } 188 189 190 /** 191 * Create and return a new user. 192 * 193 * @param name Username of the newly created user 194 * @param password Cleartext password of the newly created user 195 * 196 * @exception IllegalArgumentException if this username is already in use 197 */ 198 public FileRealmUser createUser(String name, String password) { 199 200 if (getUser(name) != null) 201 throw new IllegalArgumentException( 202 sm.getString("file.createUser.exists", name)); 203 204 return (new FileRealmUser(this, name, password)); 205 206 } 207 208 209 /** 210 * Create and return a new user. 211 * 212 * @param name Username of the newly created user 213 * @param password Encrypted password of the newly created user 214 * 215 * @exception IllegalArgumentException if this username is already in use 216 */ 217 public FileRealmUser createUser(String name, byte[] password) { 218 219 if (getUser(name) != null) 220 throw new IllegalArgumentException( 221 sm.getString("file.createUser.exists", name)); 222 223 return (new FileRealmUser(this, name, password)); 224 225 } 226 227 228 /** 229 * Return the group with the specified name, if any. 230 * 231 * @param name Name of the desired group 232 */ 233 public FileRealmGroup getGroup(String name) { 234 235 return ((FileRealmGroup) groups.get(name)); 236 237 } 238 239 240 /** 241 * Return an enumeration of the defined groups in this database. 242 */ 243 public Enumeration getGroups() { 244 245 return (groups.elements()); 246 247 } 248 249 250 /** 251 * Return an enumeration of the defined roles in this database. 252 */ 253 public Enumeration getRoles() { 254 255 return (roles.keys()); 256 257 } 258 259 260 /** 261 * Return the user with the specified name, if any. 262 * 263 * @param name Name of the desired user 264 */ 265 public FileRealmUser getUser(String name) { 266 267 return ((FileRealmUser) users.get(name)); 268 269 } 270 271 272 /** 273 * Return an enumeration of the defined users in this database. 274 */ 275 public Enumeration getUsers() { 276 277 return (users.elements()); 278 279 } 280 281 282 /** 283 * Is the specified role valid within this database? 284 * 285 * @param role Role to be tested 286 */ 287 public boolean hasRole(String role) { 288 289 return (roles.get(role) != null); 290 291 } 292 293 294 /** 295 * Load the contents of this database from the specified input stream. 296 * IMPLEMENTATION NOTE: The order of processing (users, groups, and 297 * then roles) is important to correctly process XML files with forward 298 * references in them. 299 * 300 * @param stream Input stream to read from 301 * 302 * @exception IOException if an input/output error occurs 303 * @exception SAXParseException if a parsing exception occurs 304 * @exception SAXException if a processing exception occurs 305 */ 306 public void read(InputStream stream) 307 throws IOException, SAXParseException, SAXException { 308 309 reset(); 310 311 // Parse the input stream into an XMLTree 312 XMLParser parser = new XMLParser(); 313 XMLTree config = parser.process(stream); 314 if (!config.getName().equals(Constants.Element.TOMCAT_USERS)) 315 return; 316 Enumeration e; 317 318 // Process the defined users 319 e = config.getElements(Constants.Element.USER).elements(); 320 while (e.hasMoreElements()) 321 readUser((XMLTree) e.nextElement()); 322 323 // Process the defined groups 324 e = config.getElements(Constants.Element.GROUP).elements(); 325 while (e.hasMoreElements()) 326 readGroup((XMLTree) e.nextElement()); 327 328 // Process the defined roles 329 e = config.getElements(Constants.Element.ROLE).elements(); 330 while (e.hasMoreElements()) 331 readRole((XMLTree) e.nextElement()); 332 333 } 334 335 336 /** 337 * Convert the specified XML element into a new group. 338 * 339 * @param element XML element for this group 340 */ 341 private void readGroup(XMLTree element) { 342 343 // Construct the group itself 344 String name = 345 (String) element.getAttribute(Constants.Attribute.NAME); 346 FileRealmGroup group = createGroup(name); 347 348 // Process the associated group memberships 349 Enumeration e = 350 element.getElements(Constants.Element.USER_MEMBER).elements(); 351 while (e.hasMoreElements()) { 352 XMLTree um = (XMLTree) e.nextElement(); 353 String username = 354 (String) um.getAttribute(Constants.Attribute.NAME); 355 FileRealmUser user = getUser(username); 356 if (user != null) 357 user.addGroup(group); 358 } 359 360 // XXX: Does not support the "anyone" sub-element 361 362 } 363 364 365 /** 366 * Convert the specified XML element into a new role. 367 * 368 * @param element XML element for this role 369 */ 370 private void readRole(XMLTree element) { 371 372 // Construct the role itself 373 String role = 374 (String) element.getAttribute(Constants.Attribute.NAME); 375 Enumeration e = null; 376 377 // Process the associated group memberships 378 e = element.getElements(Constants.Element.GROUP_MEMBER).elements(); 379 while (e.hasMoreElements()) { 380 XMLTree gm = (XMLTree) e.nextElement(); 381 String groupname = 382 (String) gm.getAttribute(Constants.Attribute.NAME); 383 FileRealmGroup group = getGroup(groupname); 384 if (group != null) 385 group.addRole(role); 386 } 387 388 // Process the associated user memberships 389 e = element.getElements(Constants.Element.USER_MEMBER).elements(); 390 while (e.hasMoreElements()) { 391 XMLTree um = (XMLTree) e.nextElement(); 392 String username = 393 (String) um.getAttribute(Constants.Attribute.NAME); 394 FileRealmUser user = getUser(username); 395 if (user != null) 396 user.addRole(role); 397 } 398 399 // XXX: Does not support the "anyone" sub-element 400 401 } 402 403 404 /** 405 * Convert the specified XML element into a new user. 406 * 407 * @param element XML element for this user 408 */ 409 private void readUser(XMLTree element) { 410 411 // Construct the user itself 412 String name = 413 (String) element.getAttribute(Constants.Attribute.NAME); 414 byte[] password = 415 HexUtils.convert 416 ((String) element.getAttribute(Constants.Attribute.PASSWORD)); 417 createUser(name, password); 418 419 } 420 421 422 /** 423 * [Package Private] Remove this group from the set of defined groups. 424 * 425 * @param group Group to be removed 426 */ 427 void remove(FileRealmGroup group) { 428 429 groups.remove(group.getName()); 430 431 } 432 433 434 /** 435 * [Package Private] Remove this role from the set of defined roles. 436 * 437 * @param role Role to be removed 438 */ 439 void remove(String role) { 440 441 roles.remove(role); 442 443 } 444 445 446 /** 447 * [Package Private] Remove this user from the set of defined users. 448 * 449 * @param user User to be removed 450 */ 451 void remove(FileRealmUser user) { 452 453 users.remove(user.getName()); 454 455 } 456 457 458 /** 459 * Reset the contents of this database so that it can be reused 460 */ 461 public void reset() { 462 463 groups.clear(); 464 roles.clear(); 465 users.clear(); 466 467 } 468 469 470 /** 471 * Write the contents of this database to the specified output stream, 472 * in a format suitable for loading via the read() method. 473 * 474 * @exception IOException if an input/output error occurs 475 */ 476 public void write(OutputStream stream) throws IOException { 477 478 // XXX - Yes, this should really create a DOM tree and ask it to 479 // output itself. At this time, however, that approach would introduce 480 // another dependency on which XML parser is being used. Once 481 // a standardized XML interface is selected, this will be modified. 482 // XXX - Does not support "<anyone/>" membership in groups or roles. 483 PrintWriter writer = new PrintWriter(stream); 484 writer.println("<tomcat-users>"); 485 486 // Render user elements for all defined users 487 Enumeration users = getUsers(); 488 while (users.hasMoreElements()) { 489 FileRealmUser user = (FileRealmUser) users.nextElement(); 490 writer.println(" <user name=\"" + user.getName() + 491 "\" password=\"" + 492 HexUtils.convert(user.getPassword()) + "\" />"); 493 } 494 495 // Render group elements for all defined groups 496 Enumeration groups = getGroups(); 497 while (groups.hasMoreElements()) { 498 FileRealmGroup group = (FileRealmGroup) groups.nextElement(); 499 writer.println(" <group name=\"" + group.getName() + "\">"); 500 users = group.getUsers(); 501 while (users.hasMoreElements()) { 502 FileRealmUser user = (FileRealmUser) users.nextElement(); 503 writer.println(" <user-member name=\"" + 504 user.getName() + "\" />"); 505 } 506 writer.println(" </group>"); 507 } 508 509 // Render role elements for all defined roles 510 Enumeration roles = getRoles(); 511 while (roles.hasMoreElements()) { 512 String role = (String) roles.nextElement(); 513 writer.println(" <role name=\"" + role + "\">"); 514 users = getUsers(); 515 while (users.hasMoreElements()) { 516 FileRealmUser user = (FileRealmUser) users.nextElement(); 517 if (!user.hasRole(role)) 518 continue; 519 writer.println(" <user-member name=\"" + 520 user.getName() + "\" />"); 521 } 522 groups = getGroups(); 523 while (groups.hasMoreElements()) { 524 FileRealmGroup group = (FileRealmGroup) groups.nextElement(); 525 if (!group.hasRole(role)) 526 continue; 527 writer.println(" <group-member name=\"" + 528 group.getName() + "\" />"); 529 } 530 writer.println(" </role>"); 531 } 532 533 // Finish the output of this XML file 534 writer.println("</tomcat-users>"); 535 writer.flush(); 536 537 } 538 539 540 }