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