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