001 /* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 package org.apache.wiki.auth.user; 020 021 import java.io.BufferedWriter; 022 import java.io.File; 023 import java.io.FileNotFoundException; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.OutputStreamWriter; 027 import java.io.Serializable; 028 import java.security.Principal; 029 import java.text.DateFormat; 030 import java.text.ParseException; 031 import java.text.SimpleDateFormat; 032 import java.util.Date; 033 import java.util.Map; 034 import java.util.Properties; 035 import java.util.SortedSet; 036 import java.util.TreeSet; 037 038 import javax.xml.parsers.DocumentBuilderFactory; 039 import javax.xml.parsers.ParserConfigurationException; 040 041 import org.apache.wiki.WikiEngine; 042 import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 043 import org.apache.wiki.auth.NoSuchPrincipalException; 044 import org.apache.wiki.auth.WikiPrincipal; 045 import org.apache.wiki.auth.WikiSecurityException; 046 import org.apache.wiki.util.Serializer; 047 import org.w3c.dom.Document; 048 import org.w3c.dom.Element; 049 import org.w3c.dom.Node; 050 import org.w3c.dom.NodeList; 051 import org.w3c.dom.Text; 052 import org.xml.sax.SAXException; 053 054 /** 055 * <p>Manages {@link DefaultUserProfile} objects using XML files for persistence. 056 * Passwords are hashed using SHA1. User entries are simple <code><user></code> 057 * elements under the root. User profile properties are attributes of the 058 * element. For example:</p> 059 * <blockquote><code> 060 * <users><br/> 061 * <user loginName="janne" fullName="Janne Jalkanen"<br/> 062 * wikiName="JanneJalkanen" email="janne@ecyrd.com"<br/> 063 * password="{SHA}457b08e825da547c3b77fbc1ff906a1d00a7daee"/><br/> 064 * </users> 065 * </code></blockquote> 066 * <p>In this example, the un-hashed password is <code>myP@5sw0rd</code>. Passwords are hashed without salt.</p> 067 * @since 2.3 068 */ 069 070 // FIXME: If the DB is shared across multiple systems, it's possible to lose accounts 071 // if two people add new accounts right after each other from different wikis. 072 public class XMLUserDatabase extends AbstractUserDatabase { 073 074 /** 075 * The jspwiki.properties property specifying the file system location of 076 * the user database. 077 */ 078 public static final String PROP_USERDATABASE = "jspwiki.xmlUserDatabaseFile"; 079 080 private static final String DEFAULT_USERDATABASE = "userdatabase.xml"; 081 082 private static final String ATTRIBUTES_TAG = "attributes"; 083 084 private static final String CREATED = "created"; 085 086 private static final String EMAIL = "email"; 087 088 private static final String FULL_NAME = "fullName"; 089 090 private static final String LOGIN_NAME = "loginName"; 091 092 private static final String LAST_MODIFIED = "lastModified"; 093 094 private static final String LOCK_EXPIRY = "lockExpiry"; 095 096 private static final String PASSWORD = "password"; 097 098 private static final String UID = "uid"; 099 100 private static final String USER_TAG = "user"; 101 102 private static final String WIKI_NAME = "wikiName"; 103 104 private static final String DATE_FORMAT = "yyyy.MM.dd 'at' HH:mm:ss:SSS z"; 105 106 private Document c_dom = null; 107 108 private File c_file = null; 109 110 /** 111 * Looks up and deletes the first {@link UserProfile} in the user database 112 * that matches a profile having a given login name. If the user database 113 * does not contain a user with a matching attribute, throws a 114 * {@link NoSuchPrincipalException}. 115 * @param loginName the login name of the user profile that shall be deleted 116 */ 117 public synchronized void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException 118 { 119 if ( c_dom == null ) 120 { 121 throw new WikiSecurityException( "FATAL: database does not exist" ); 122 } 123 124 NodeList users = c_dom.getDocumentElement().getElementsByTagName( USER_TAG ); 125 for( int i = 0; i < users.getLength(); i++ ) 126 { 127 Element user = (Element) users.item( i ); 128 if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) ) 129 { 130 c_dom.getDocumentElement().removeChild(user); 131 132 // Commit to disk 133 saveDOM(); 134 return; 135 } 136 } 137 throw new NoSuchPrincipalException( "Not in database: " + loginName ); 138 } 139 140 /** 141 * Looks up and returns the first {@link UserProfile}in the user database 142 * that matches a profile having a given e-mail address. If the user 143 * database does not contain a user with a matching attribute, throws a 144 * {@link NoSuchPrincipalException}. 145 * @param index the e-mail address of the desired user profile 146 * @return the user profile 147 * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(String) 148 */ 149 public UserProfile findByEmail( String index ) throws NoSuchPrincipalException 150 { 151 UserProfile profile = findByAttribute( EMAIL, index ); 152 if ( profile != null ) 153 { 154 return profile; 155 } 156 throw new NoSuchPrincipalException( "Not in database: " + index ); 157 } 158 159 /** 160 * Looks up and returns the first {@link UserProfile}in the user database 161 * that matches a profile having a given full name. If the user database 162 * does not contain a user with a matching attribute, throws a 163 * {@link NoSuchPrincipalException}. 164 * @param index the fill name of the desired user profile 165 * @return the user profile 166 * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(java.lang.String) 167 */ 168 public UserProfile findByFullName( String index ) throws NoSuchPrincipalException 169 { 170 UserProfile profile = findByAttribute( FULL_NAME, index ); 171 if ( profile != null ) 172 { 173 return profile; 174 } 175 throw new NoSuchPrincipalException( "Not in database: " + index ); 176 } 177 178 /** 179 * Looks up and returns the first {@link UserProfile}in the user database 180 * that matches a profile having a given login name. If the user database 181 * does not contain a user with a matching attribute, throws a 182 * {@link NoSuchPrincipalException}. 183 * @param index the login name of the desired user profile 184 * @return the user profile 185 * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(java.lang.String) 186 */ 187 public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException 188 { 189 UserProfile profile = findByAttribute( LOGIN_NAME, index ); 190 if ( profile != null ) 191 { 192 return profile; 193 } 194 throw new NoSuchPrincipalException( "Not in database: " + index ); 195 } 196 197 /** 198 * {@inheritDoc} 199 */ 200 public UserProfile findByUid( String uid ) throws NoSuchPrincipalException 201 { 202 UserProfile profile = findByAttribute( UID, uid ); 203 if ( profile != null ) 204 { 205 return profile; 206 } 207 throw new NoSuchPrincipalException( "Not in database: " + uid ); 208 } 209 210 /** 211 * Looks up and returns the first {@link UserProfile}in the user database 212 * that matches a profile having a given wiki name. If the user database 213 * does not contain a user with a matching attribute, throws a 214 * {@link NoSuchPrincipalException}. 215 * @param index the wiki name of the desired user profile 216 * @return the user profile 217 * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(java.lang.String) 218 */ 219 public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException 220 { 221 UserProfile profile = findByAttribute( WIKI_NAME, index ); 222 if ( profile != null ) 223 { 224 return profile; 225 } 226 throw new NoSuchPrincipalException( "Not in database: " + index ); 227 } 228 229 /** 230 * Returns all WikiNames that are stored in the UserDatabase 231 * as an array of WikiPrincipal objects. If the database does not 232 * contain any profiles, this method will return a zero-length 233 * array. 234 * @return the WikiNames 235 * @throws WikiSecurityException In case things fail. 236 */ 237 public Principal[] getWikiNames() throws WikiSecurityException 238 { 239 if ( c_dom == null ) 240 { 241 throw new IllegalStateException( "FATAL: database does not exist" ); 242 } 243 SortedSet<Principal> principals = new TreeSet<Principal>(); 244 NodeList users = c_dom.getElementsByTagName( USER_TAG ); 245 for( int i = 0; i < users.getLength(); i++ ) 246 { 247 Element user = (Element) users.item( i ); 248 String wikiName = user.getAttribute( WIKI_NAME ); 249 if ( wikiName == null ) 250 { 251 log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." ); 252 } 253 else 254 { 255 Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME ); 256 principals.add( principal ); 257 } 258 } 259 return principals.toArray( new Principal[principals.size()] ); 260 } 261 262 /** 263 * Initializes the user database based on values from a Properties object. 264 * The properties object must contain a file path to the XML database file 265 * whose key is {@link #PROP_USERDATABASE}. 266 * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine, 267 * java.util.Properties) 268 * @throws NoRequiredPropertyException if the user database cannot be located, parsed, or opened 269 */ 270 public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException 271 { 272 File defaultFile = null; 273 if( engine.getRootPath() == null ) 274 { 275 log.warn( "Cannot identify JSPWiki root path" ); 276 defaultFile = new File( "WEB-INF/" + DEFAULT_USERDATABASE ).getAbsoluteFile(); 277 } 278 else 279 { 280 defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + DEFAULT_USERDATABASE ); 281 } 282 283 // Get database file location 284 String file = props.getProperty( PROP_USERDATABASE ); 285 if( file == null ) 286 { 287 log.warn( "XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile ); 288 c_file = defaultFile; 289 } 290 else 291 { 292 c_file = new File( file ); 293 } 294 295 log.info("XML user database at "+c_file.getAbsolutePath()); 296 297 buildDOM(); 298 sanitizeDOM(); 299 } 300 301 private void buildDOM() 302 { 303 // Read DOM 304 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 305 factory.setValidating( false ); 306 factory.setExpandEntityReferences( false ); 307 factory.setIgnoringComments( true ); 308 factory.setNamespaceAware( false ); 309 try 310 { 311 c_dom = factory.newDocumentBuilder().parse( c_file ); 312 log.debug( "Database successfully initialized" ); 313 c_lastModified = c_file.lastModified(); 314 c_lastCheck = System.currentTimeMillis(); 315 } 316 catch( ParserConfigurationException e ) 317 { 318 log.error( "Configuration error: " + e.getMessage() ); 319 } 320 catch( SAXException e ) 321 { 322 log.error( "SAX error: " + e.getMessage() ); 323 } 324 catch( FileNotFoundException e ) 325 { 326 log.info("User database not found; creating from scratch..."); 327 } 328 catch( IOException e ) 329 { 330 log.error( "IO error: " + e.getMessage() ); 331 } 332 if ( c_dom == null ) 333 { 334 try 335 { 336 // 337 // Create the DOM from scratch 338 // 339 c_dom = factory.newDocumentBuilder().newDocument(); 340 c_dom.appendChild( c_dom.createElement( "users") ); 341 } 342 catch( ParserConfigurationException e ) 343 { 344 log.fatal( "Could not create in-memory DOM" ); 345 } 346 } 347 } 348 349 private void saveDOM() throws WikiSecurityException 350 { 351 if ( c_dom == null ) 352 { 353 log.fatal( "User database doesn't exist in memory." ); 354 } 355 356 File newFile = new File( c_file.getAbsolutePath() + ".new" ); 357 try 358 { 359 BufferedWriter io = new BufferedWriter( new OutputStreamWriter ( 360 new FileOutputStream( newFile ), "UTF-8" ) ); 361 362 // Write the file header and document root 363 io.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 364 io.write("<users>\n"); 365 366 // Write each profile as a <user> node 367 Element root = c_dom.getDocumentElement(); 368 NodeList nodes = root.getElementsByTagName( USER_TAG ); 369 for( int i = 0; i < nodes.getLength(); i++ ) 370 { 371 Element user = (Element)nodes.item( i ); 372 io.write( " <" + USER_TAG + " "); 373 io.write( UID ); 374 io.write( "=\"" + user.getAttribute( UID ) + "\" " ); 375 io.write( LOGIN_NAME ); 376 io.write( "=\"" + user.getAttribute( LOGIN_NAME ) + "\" " ); 377 io.write( WIKI_NAME ); 378 io.write( "=\"" + user.getAttribute( WIKI_NAME ) + "\" " ); 379 io.write( FULL_NAME ); 380 io.write( "=\"" + user.getAttribute( FULL_NAME ) + "\" " ); 381 io.write( EMAIL ); 382 io.write( "=\"" + user.getAttribute( EMAIL ) + "\" " ); 383 io.write( PASSWORD ); 384 io.write( "=\"" + user.getAttribute( PASSWORD ) + "\" " ); 385 io.write( CREATED ); 386 io.write( "=\"" + user.getAttribute( CREATED ) + "\" " ); 387 io.write( LAST_MODIFIED ); 388 io.write( "=\"" + user.getAttribute( LAST_MODIFIED ) + "\" " ); 389 io.write( LOCK_EXPIRY ); 390 io.write( "=\"" + user.getAttribute( LOCK_EXPIRY ) + "\" " ); 391 io.write( ">" ); 392 NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 393 for ( int j = 0; j < attributes.getLength(); j++ ) 394 { 395 Element attribute = (Element)attributes.item( j ); 396 String value = extractText( attribute ); 397 io.write( "\n <" + ATTRIBUTES_TAG + ">" ); 398 io.write( value ); 399 io.write( "</" + ATTRIBUTES_TAG + ">" ); 400 } 401 io.write("\n </" +USER_TAG + ">\n"); 402 } 403 io.write("</users>"); 404 io.close(); 405 } 406 catch ( IOException e ) 407 { 408 throw new WikiSecurityException( e.getLocalizedMessage(), e ); 409 } 410 411 // Copy new file over old version 412 File backup = new File( c_file.getAbsolutePath() + ".old" ); 413 if ( backup.exists() ) 414 { 415 if ( !backup.delete() ) 416 { 417 log.error( "Could not delete old user database backup: " + backup ); 418 } 419 } 420 if ( !c_file.renameTo( backup ) ) 421 { 422 log.error( "Could not create user database backup: " + backup ); 423 } 424 if ( !newFile.renameTo( c_file ) ) 425 { 426 log.error( "Could not save database: " + backup + " restoring backup." ); 427 if ( !backup.renameTo( c_file ) ) 428 { 429 log.error( "Restore failed. Check the file permissions." ); 430 } 431 log.error( "Could not save database: " + c_file + ". Check the file permissions" ); 432 } 433 } 434 435 private long c_lastCheck = 0; 436 private long c_lastModified = 0; 437 438 private void checkForRefresh() 439 { 440 long time = System.currentTimeMillis(); 441 442 if( time - c_lastCheck > 60*1000L ) 443 { 444 long lastModified = c_file.lastModified(); 445 446 if( lastModified > c_lastModified ) 447 { 448 buildDOM(); 449 } 450 } 451 } 452 453 /** 454 * @see org.apache.wiki.auth.user.UserDatabase#rename(String, String) 455 */ 456 public synchronized void rename(String loginName, String newName) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException 457 { 458 if ( c_dom == null ) 459 { 460 log.fatal( "Could not rename profile '" + loginName + "'; database does not exist" ); 461 throw new IllegalStateException( "FATAL: database does not exist" ); 462 } 463 checkForRefresh(); 464 465 // Get the existing user; if not found, throws NoSuchPrincipalException 466 UserProfile profile = findByLoginName( loginName ); 467 468 // Get user with the proposed name; if found, it's a collision 469 try 470 { 471 UserProfile otherProfile = findByLoginName( newName ); 472 if ( otherProfile != null ) 473 { 474 throw new DuplicateUserException( "security.error.cannot.rename", newName ); 475 } 476 } 477 catch ( NoSuchPrincipalException e ) 478 { 479 // Good! That means it's safe to save using the new name 480 } 481 482 // Find the user with the old login id attribute, and change it 483 NodeList users = c_dom.getElementsByTagName( USER_TAG ); 484 for( int i = 0; i < users.getLength(); i++ ) 485 { 486 Element user = (Element) users.item( i ); 487 if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) ) 488 { 489 DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 490 Date modDate = new Date( System.currentTimeMillis() ); 491 setAttribute( user, LOGIN_NAME, newName ); 492 setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) ); 493 profile.setLoginName( newName ); 494 profile.setLastModified( modDate ); 495 break; 496 } 497 } 498 499 // Commit to disk 500 saveDOM(); 501 } 502 503 /** 504 * Saves a {@link UserProfile}to the user database, overwriting the 505 * existing profile if it exists. The user name under which the profile 506 * should be saved is returned by the supplied profile's 507 * {@link UserProfile#getLoginName()}method. 508 * @param profile the user profile to save 509 * @throws WikiSecurityException if the profile cannot be saved 510 */ 511 public synchronized void save( UserProfile profile ) throws WikiSecurityException 512 { 513 if ( c_dom == null ) 514 { 515 log.fatal( "Could not save profile " + profile + " database does not exist" ); 516 throw new IllegalStateException( "FATAL: database does not exist" ); 517 } 518 519 checkForRefresh(); 520 521 DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 522 String index = profile.getLoginName(); 523 NodeList users = c_dom.getElementsByTagName( USER_TAG ); 524 Element user = null; 525 for( int i = 0; i < users.getLength(); i++ ) 526 { 527 Element currentUser = (Element) users.item( i ); 528 if ( currentUser.getAttribute( LOGIN_NAME ).equals( index ) ) 529 { 530 user = currentUser; 531 break; 532 } 533 } 534 535 boolean isNew = false; 536 537 Date modDate = new Date( System.currentTimeMillis() ); 538 if( user == null ) 539 { 540 // Create new user node 541 profile.setCreated( modDate ); 542 log.info( "Creating new user " + index ); 543 user = c_dom.createElement( USER_TAG ); 544 c_dom.getDocumentElement().appendChild( user ); 545 setAttribute( user, CREATED, c_format.format( profile.getCreated() ) ); 546 isNew = true; 547 } 548 else 549 { 550 // To update existing user node, delete old attributes first... 551 NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 552 for ( int i = 0; i < attributes.getLength(); i++ ) 553 { 554 user.removeChild( attributes.item( i ) ); 555 } 556 } 557 558 setAttribute( user, UID, profile.getUid() ); 559 setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) ); 560 setAttribute( user, LOGIN_NAME, profile.getLoginName() ); 561 setAttribute( user, FULL_NAME, profile.getFullname() ); 562 setAttribute( user, WIKI_NAME, profile.getWikiName() ); 563 setAttribute( user, EMAIL, profile.getEmail() ); 564 Date lockExpiry = profile.getLockExpiry(); 565 setAttribute( user, LOCK_EXPIRY, lockExpiry == null ? "" : c_format.format( lockExpiry ) ); 566 567 // Hash and save the new password if it's different from old one 568 String newPassword = profile.getPassword(); 569 if ( newPassword != null && !newPassword.equals( "" ) ) 570 { 571 String oldPassword = user.getAttribute( PASSWORD ); 572 if ( !oldPassword.equals( newPassword ) ) 573 { 574 setAttribute( user, PASSWORD, getHash( newPassword ) ); 575 } 576 } 577 578 // Save the attributes as as Base64 string 579 if ( profile.getAttributes().size() > 0 ) 580 { 581 try 582 { 583 String encodedAttributes = Serializer.serializeToBase64( profile.getAttributes() ); 584 Element attributes = c_dom.createElement( ATTRIBUTES_TAG ); 585 user.appendChild( attributes ); 586 Text value = c_dom.createTextNode( encodedAttributes ); 587 attributes.appendChild( value ); 588 } 589 catch ( IOException e ) 590 { 591 throw new WikiSecurityException( "Could not save user profile attribute. Reason: " + e.getMessage(), e ); 592 } 593 } 594 595 // Set the profile timestamps 596 if ( isNew ) 597 { 598 profile.setCreated( modDate ); 599 } 600 profile.setLastModified( modDate ); 601 602 // Commit to disk 603 saveDOM(); 604 } 605 606 /** 607 * Private method that returns the first {@link UserProfile}matching a 608 * <user> element's supplied attribute. This method will also 609 * set the UID if it has not yet been set. 610 * @param matchAttribute 611 * @param index 612 * @return the profile, or <code>null</code> if not found 613 */ 614 private UserProfile findByAttribute( String matchAttribute, String index ) 615 { 616 if ( c_dom == null ) 617 { 618 throw new IllegalStateException( "FATAL: database does not exist" ); 619 } 620 621 checkForRefresh(); 622 623 NodeList users = c_dom.getElementsByTagName( USER_TAG ); 624 625 if( users == null ) return null; 626 627 for( int i = 0; i < users.getLength(); i++ ) 628 { 629 Element user = (Element) users.item( i ); 630 if ( user.getAttribute( matchAttribute ).equals( index ) ) 631 { 632 UserProfile profile = newProfile(); 633 634 // Parse basic attributes 635 profile.setUid( user.getAttribute( UID ) ); 636 if ( profile.getUid() == null || profile.getUid().length() == 0 ) 637 { 638 profile.setUid( generateUid( this ) ); 639 } 640 profile.setLoginName( user.getAttribute( LOGIN_NAME ) ); 641 profile.setFullname( user.getAttribute( FULL_NAME ) ); 642 profile.setPassword( user.getAttribute( PASSWORD ) ); 643 profile.setEmail( user.getAttribute( EMAIL ) ); 644 645 // Get created/modified timestamps 646 String created = user.getAttribute( CREATED ); 647 String modified = user.getAttribute( LAST_MODIFIED ); 648 profile.setCreated( parseDate( profile, created ) ); 649 profile.setLastModified( parseDate( profile, modified ) ); 650 651 // Is the profile locked? 652 String lockExpiry = user.getAttribute( LOCK_EXPIRY ); 653 if ( lockExpiry == null || lockExpiry.length() == 0 ) 654 { 655 profile.setLockExpiry( null ); 656 } 657 else 658 { 659 profile.setLockExpiry( new Date( Long.parseLong( lockExpiry ) ) ); 660 } 661 662 // Extract all of the user's attributes (should only be one attributes tag, but you never know!) 663 NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 664 for ( int j = 0; j < attributes.getLength(); j++ ) 665 { 666 Element attribute = (Element)attributes.item( j ); 667 String serializedMap = extractText( attribute ); 668 try 669 { 670 Map<String,? extends Serializable> map = Serializer.deserializeFromBase64( serializedMap ); 671 profile.getAttributes().putAll( map ); 672 } 673 catch ( IOException e ) 674 { 675 log.error( "Could not parse user profile attributes!", e ); 676 } 677 } 678 679 return profile; 680 } 681 } 682 return null; 683 } 684 685 /** 686 * Extracts all of the text nodes that are immediate children of an Element. 687 * @param element the base element 688 * @return the text nodes that are immediate children of the base element, concatenated together 689 */ 690 private String extractText( Element element ) 691 { 692 String text = ""; 693 if ( element.getChildNodes().getLength() > 0 ) 694 { 695 NodeList children = element.getChildNodes(); 696 for ( int k = 0; k < children.getLength(); k++ ) 697 { 698 Node child = children.item( k ); 699 if ( child.getNodeType() == Node.TEXT_NODE ) 700 { 701 text = text + ((Text)child).getData(); 702 } 703 } 704 } 705 return text; 706 } 707 708 /** 709 * Tries to parse a date using the default format - then, for backwards 710 * compatibility reasons, tries the platform default. 711 * 712 * @param profile 713 * @param date 714 * @return A parsed date, or null, if both parse attempts fail. 715 */ 716 private Date parseDate( UserProfile profile, String date ) 717 { 718 try 719 { 720 DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 721 return c_format.parse( date ); 722 } 723 catch( ParseException e ) 724 { 725 try 726 { 727 return DateFormat.getDateTimeInstance().parse( date ); 728 } 729 catch ( ParseException e2) 730 { 731 log.warn("Could not parse 'created' or 'lastModified' " 732 + "attribute for " 733 + " profile '" + profile.getLoginName() + "'." 734 + " It may have been tampered with." ); 735 } 736 } 737 return null; 738 } 739 740 /** 741 * After loading the DOM, this method sanity-checks the dates in the DOM and makes 742 * sure they are formatted properly. This is sort-of hacky, but it should work. 743 */ 744 private void sanitizeDOM() 745 { 746 if ( c_dom == null ) 747 { 748 throw new IllegalStateException( "FATAL: database does not exist" ); 749 } 750 751 NodeList users = c_dom.getElementsByTagName( USER_TAG ); 752 for( int i = 0; i < users.getLength(); i++ ) 753 { 754 Element user = (Element) users.item( i ); 755 756 // Sanitize UID (and generate a new one if one does not exist) 757 String uid = user.getAttribute( UID ).trim(); 758 if ( uid == null || uid.length() == 0 || "-1".equals( uid ) ) 759 { 760 uid = String.valueOf( generateUid( this ) ); 761 user.setAttribute( UID, uid ); 762 } 763 764 // Sanitize dates 765 String loginName = user.getAttribute( LOGIN_NAME ); 766 String created = user.getAttribute( CREATED ); 767 String modified = user.getAttribute( LAST_MODIFIED ); 768 DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 769 try 770 { 771 created = c_format.format( c_format.parse( created ) ); 772 modified = c_format.format( c_format.parse( modified ) ); 773 user.setAttribute( CREATED, created ); 774 user.setAttribute( LAST_MODIFIED, modified ); 775 } 776 catch( ParseException e ) 777 { 778 try 779 { 780 created = c_format.format( DateFormat.getDateTimeInstance().parse( created ) ); 781 modified = c_format.format( DateFormat.getDateTimeInstance().parse( modified ) ); 782 user.setAttribute( CREATED, created ); 783 user.setAttribute( LAST_MODIFIED, modified ); 784 } 785 catch ( ParseException e2 ) 786 { 787 log.warn( "Could not parse 'created' or 'lastModified' attribute for profile '" + loginName + "'." 788 + " It may have been tampered with." ); 789 } 790 } 791 } 792 } 793 794 /** 795 * Private method that sets an attribute value for a supplied DOM element. 796 * @param element the element whose attribute is to be set 797 * @param attribute the name of the attribute to set 798 * @param value the desired attribute value 799 */ 800 private void setAttribute( Element element, String attribute, String value ) { 801 if( value != null ) { 802 element.setAttribute( attribute, value ); 803 } 804 } 805 }