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.api.core.Engine; 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.IOException; 042import java.io.OutputStreamWriter; 043import java.io.Serializable; 044import java.nio.charset.StandardCharsets; 045import java.nio.file.Files; 046import java.security.Principal; 047import java.text.DateFormat; 048import java.text.ParseException; 049import java.text.SimpleDateFormat; 050import java.util.Date; 051import java.util.Map; 052import java.util.Properties; 053import java.util.SortedSet; 054import java.util.TreeSet; 055import java.util.stream.Collectors; 056import java.util.stream.IntStream; 057 058/** 059 * <p>Manages {@link DefaultUserProfile} objects using XML files for persistence. Passwords are hashed using SHA1. User entries are simple 060 * <code><user></code> elements under the root. User profile properties are attributes of the 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 /** The jspwiki.properties property specifying the file system location of the user database. */ 077 public static final String PROP_USERDATABASE = "jspwiki.xmlUserDatabaseFile"; 078 private static final String DEFAULT_USERDATABASE = "userdatabase.xml"; 079 private static final String ATTRIBUTES_TAG = "attributes"; 080 private static final String CREATED = "created"; 081 private static final String EMAIL = "email"; 082 private static final String FULL_NAME = "fullName"; 083 private static final String LOGIN_NAME = "loginName"; 084 private static final String LAST_MODIFIED = "lastModified"; 085 private static final String LOCK_EXPIRY = "lockExpiry"; 086 private static final String PASSWORD = "password"; 087 private static final String UID = "uid"; 088 private static final String USER_TAG = "user"; 089 private static final String WIKI_NAME = "wikiName"; 090 private static final String DATE_FORMAT = "yyyy.MM.dd 'at' HH:mm:ss:SSS z"; 091 private Document c_dom; 092 private File c_file; 093 094 /** {@inheritDoc} */ 095 @Override 096 public synchronized void deleteByLoginName( final String loginName ) throws WikiSecurityException { 097 if( c_dom == null ) { 098 throw new WikiSecurityException( "FATAL: database does not exist" ); 099 } 100 101 final NodeList users = c_dom.getDocumentElement().getElementsByTagName( USER_TAG ); 102 for( int i = 0; i < users.getLength(); i++ ) { 103 final Element user = ( Element )users.item( i ); 104 if( user.getAttribute( LOGIN_NAME ).equals( loginName ) ) { 105 c_dom.getDocumentElement().removeChild( user ); 106 107 // Commit to disk 108 saveDOM(); 109 return; 110 } 111 } 112 throw new NoSuchPrincipalException( "Not in database: " + loginName ); 113 } 114 115 /** {@inheritDoc} */ 116 @Override 117 public UserProfile findByEmail( final String index ) throws NoSuchPrincipalException { 118 return findBy( EMAIL, index ); 119 } 120 121 /** {@inheritDoc} */ 122 @Override 123 public UserProfile findByFullName( final String index ) throws NoSuchPrincipalException { 124 return findBy( FULL_NAME, index ); 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 public UserProfile findByLoginName( final String index ) throws NoSuchPrincipalException { 130 return findBy( LOGIN_NAME, index ); 131 } 132 133 /** {@inheritDoc} */ 134 @Override 135 public UserProfile findByUid( final String uid ) throws NoSuchPrincipalException { 136 return findBy( UID, uid ); 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public UserProfile findByWikiName( final String index ) throws NoSuchPrincipalException { 142 return findBy( WIKI_NAME, index ); 143 } 144 145 public UserProfile findBy( final String attr, final String value ) throws NoSuchPrincipalException { 146 final UserProfile profile = findByAttribute( attr, value ); 147 if ( profile != null ) { 148 return profile; 149 } 150 throw new NoSuchPrincipalException( "Not in database: " + value ); 151 } 152 153 /** {@inheritDoc} */ 154 @Override 155 public Principal[] getWikiNames() throws WikiSecurityException { 156 if ( c_dom == null ) { 157 throw new IllegalStateException( "FATAL: database does not exist" ); 158 } 159 final SortedSet< WikiPrincipal > principals = new TreeSet<>(); 160 final NodeList users = c_dom.getElementsByTagName( USER_TAG ); 161 for( int i = 0; i < users.getLength(); i++ ) { 162 final Element user = ( Element )users.item( i ); 163 final String wikiName = user.getAttribute( WIKI_NAME ); 164 if( StringUtils.isEmpty( wikiName ) ) { 165 LOG.warn( "Detected null or empty wiki name for {} in XMLUserDataBase. Check your user database.", user.getAttribute( LOGIN_NAME ) ); 166 } else { 167 final WikiPrincipal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME ); 168 principals.add( principal ); 169 } 170 } 171 return principals.toArray( new Principal[0] ); 172 } 173 174 /** {@inheritDoc} */ 175 @Override 176 public void initialize( final Engine engine, final Properties props ) throws NoRequiredPropertyException { 177 final File defaultFile; 178 if( engine.getRootPath() == null ) { 179 LOG.warn( "Cannot identify JSPWiki root path" ); 180 defaultFile = new File( "WEB-INF/" + DEFAULT_USERDATABASE ).getAbsoluteFile(); 181 } else { 182 defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + DEFAULT_USERDATABASE ); 183 } 184 185 // Get database file location 186 final String file = TextUtil.getStringProperty( props, PROP_USERDATABASE, defaultFile.getAbsolutePath() ); 187 if( file == null ) { 188 LOG.warn( "XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile ); 189 c_file = defaultFile; 190 } else { 191 c_file = new File( file ); 192 } 193 194 LOG.info( "XML user database at " + c_file.getAbsolutePath() ); 195 196 buildDOM(); 197 sanitizeDOM(); 198 } 199 200 private void buildDOM() { 201 // Read DOM 202 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 203 factory.setValidating( false ); 204 factory.setExpandEntityReferences( false ); 205 factory.setIgnoringComments( true ); 206 factory.setNamespaceAware( false ); 207 //factory.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" ); 208 //factory.setAttribute( XMLConstants.ACCESS_EXTERNAL_SCHEMA, "" ); 209 try { 210 c_dom = factory.newDocumentBuilder().parse( c_file ); 211 LOG.debug( "Database successfully initialized" ); 212 c_lastModified = c_file.lastModified(); 213 c_lastCheck = System.currentTimeMillis(); 214 } catch( final ParserConfigurationException e ) { 215 LOG.error( "Configuration error: {}", e.getMessage() ); 216 } catch( final SAXException e ) { 217 LOG.error( "SAX error: {}", e.getMessage() ); 218 } catch( final FileNotFoundException e ) { 219 LOG.info( "User database not found; creating from scratch..." ); 220 } catch( final IOException e ) { 221 LOG.error( "IO error: {}", e.getMessage() ); 222 } 223 if( c_dom == null ) { 224 try { 225 // Create the DOM from scratch 226 c_dom = factory.newDocumentBuilder().newDocument(); 227 c_dom.appendChild( c_dom.createElement( "users" ) ); 228 } catch( final ParserConfigurationException e ) { 229 LOG.fatal( "Could not create in-memory DOM" ); 230 } 231 } 232 } 233 234 private void saveDOM() throws WikiSecurityException { 235 if( c_dom == null ) { 236 throw new IllegalStateException( "FATAL: database does not exist" ); 237 } 238 239 final File newFile = new File( c_file.getAbsolutePath() + ".new" ); 240 try( final BufferedWriter io = new BufferedWriter( new OutputStreamWriter( Files.newOutputStream( newFile.toPath() ), StandardCharsets.UTF_8 ) ) ) { 241 242 // Write the file header and document root 243 io.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); 244 io.write( "<users>\n" ); 245 246 // Write each profile as a <user> node 247 final Element root = c_dom.getDocumentElement(); 248 final NodeList nodes = root.getElementsByTagName( USER_TAG ); 249 for( int i = 0; i < nodes.getLength(); i++ ) { 250 final Element user = ( Element )nodes.item( i ); 251 io.write( " <" + USER_TAG + " " ); 252 io.write( UID ); 253 io.write( "=\"" + user.getAttribute( UID ) + "\" " ); 254 io.write( LOGIN_NAME ); 255 io.write( "=\"" + user.getAttribute( LOGIN_NAME ) + "\" " ); 256 io.write( WIKI_NAME ); 257 io.write( "=\"" + user.getAttribute( WIKI_NAME ) + "\" " ); 258 io.write( FULL_NAME ); 259 io.write( "=\"" + user.getAttribute( FULL_NAME ) + "\" " ); 260 io.write( EMAIL ); 261 io.write( "=\"" + user.getAttribute( EMAIL ) + "\" " ); 262 io.write( PASSWORD ); 263 io.write( "=\"" + user.getAttribute( PASSWORD ) + "\" " ); 264 io.write( CREATED ); 265 io.write( "=\"" + user.getAttribute( CREATED ) + "\" " ); 266 io.write( LAST_MODIFIED ); 267 io.write( "=\"" + user.getAttribute( LAST_MODIFIED ) + "\" " ); 268 io.write( LOCK_EXPIRY ); 269 io.write( "=\"" + user.getAttribute( LOCK_EXPIRY ) + "\" " ); 270 io.write( ">" ); 271 final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 272 for( int j = 0; j < attributes.getLength(); j++ ) { 273 final Element attribute = ( Element )attributes.item( j ); 274 final String value = extractText( attribute ); 275 io.write( "\n <" + ATTRIBUTES_TAG + ">" ); 276 io.write( value ); 277 io.write( "</" + ATTRIBUTES_TAG + ">" ); 278 } 279 io.write( "\n </" + USER_TAG + ">\n" ); 280 } 281 io.write( "</users>" ); 282 } catch( final IOException e ) { 283 throw new WikiSecurityException( e.getLocalizedMessage(), e ); 284 } 285 286 // Copy new file over old version 287 final File backup = new File( c_file.getAbsolutePath() + ".old" ); 288 if( backup.exists() ) { 289 if( !backup.delete() ) { 290 LOG.error( "Could not delete old user database backup: " + backup ); 291 } 292 } 293 if( !c_file.renameTo( backup ) ) { 294 LOG.error( "Could not create user database backup: " + backup ); 295 } 296 if( !newFile.renameTo( c_file ) ) { 297 LOG.error( "Could not save database: " + backup + " restoring backup." ); 298 if( !backup.renameTo( c_file ) ) { 299 LOG.error( "Restore failed. Check the file permissions." ); 300 } 301 LOG.error( "Could not save database: " + c_file + ". Check the file permissions" ); 302 } 303 } 304 305 private long c_lastCheck; 306 private long c_lastModified; 307 308 private void checkForRefresh() { 309 final long time = System.currentTimeMillis(); 310 if( time - c_lastCheck > 60 * 1000L ) { 311 final long lastModified = c_file.lastModified(); 312 313 if( lastModified > c_lastModified ) { 314 buildDOM(); 315 } 316 } 317 } 318 319 /** 320 * {@inheritDoc} 321 * 322 * @see org.apache.wiki.auth.user.UserDatabase#rename(String, String) 323 */ 324 @Override 325 public synchronized void rename( final String loginName, final String newName) throws DuplicateUserException, WikiSecurityException { 326 if( c_dom == null ) { 327 LOG.fatal( "Could not rename profile '" + loginName + "'; database does not exist" ); 328 throw new IllegalStateException( "FATAL: database does not exist" ); 329 } 330 checkForRefresh(); 331 332 // Get the existing user; if not found, throws NoSuchPrincipalException 333 final UserProfile profile = findByLoginName( loginName ); 334 335 // Get user with the proposed name; if found, it's a collision 336 try { 337 final UserProfile otherProfile = findByLoginName( newName ); 338 if( otherProfile != null ) { 339 throw new DuplicateUserException( "security.error.cannot.rename", newName ); 340 } 341 } catch( final NoSuchPrincipalException e ) { 342 // Good! That means it's safe to save using the new name 343 } 344 345 // Find the user with the old login id attribute, and change it 346 final NodeList users = c_dom.getElementsByTagName( USER_TAG ); 347 for( int i = 0; i < users.getLength(); i++ ) { 348 final Element user = ( Element )users.item( i ); 349 if( user.getAttribute( LOGIN_NAME ).equals( loginName ) ) { 350 final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 351 final Date modDate = new Date( System.currentTimeMillis() ); 352 setAttribute( user, LOGIN_NAME, newName ); 353 setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) ); 354 profile.setLoginName( newName ); 355 profile.setLastModified( modDate ); 356 break; 357 } 358 } 359 360 // Commit to disk 361 saveDOM(); 362 } 363 364 /** {@inheritDoc} */ 365 @Override 366 public synchronized void save( final UserProfile profile ) throws WikiSecurityException { 367 if ( c_dom == null ) { 368 LOG.fatal( "Could not save profile " + profile + " database does not exist" ); 369 throw new IllegalStateException( "FATAL: database does not exist" ); 370 } 371 372 checkForRefresh(); 373 374 final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 375 final String index = profile.getLoginName(); 376 final NodeList users = c_dom.getElementsByTagName( USER_TAG ); 377 Element user = IntStream.range(0, users.getLength()).mapToObj(i -> (Element) users.item(i)).filter(currentUser -> currentUser.getAttribute(LOGIN_NAME).equals(index)).findFirst().orElse(null); 378 379 boolean isNew = false; 380 381 final Date modDate = new Date( System.currentTimeMillis() ); 382 if( user == null ) { 383 // Create new user node 384 profile.setCreated( modDate ); 385 LOG.info( "Creating new user " + index ); 386 user = c_dom.createElement( USER_TAG ); 387 c_dom.getDocumentElement().appendChild( user ); 388 setAttribute( user, CREATED, c_format.format( profile.getCreated() ) ); 389 isNew = true; 390 } else { 391 // To update existing user node, delete old attributes first... 392 final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 393 for( int i = 0; i < attributes.getLength(); i++ ) { 394 user.removeChild( attributes.item( i ) ); 395 } 396 } 397 398 setAttribute( user, UID, profile.getUid() ); 399 setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) ); 400 setAttribute( user, LOGIN_NAME, profile.getLoginName() ); 401 setAttribute( user, FULL_NAME, profile.getFullname() ); 402 setAttribute( user, WIKI_NAME, profile.getWikiName() ); 403 setAttribute( user, EMAIL, profile.getEmail() ); 404 final Date lockExpiry = profile.getLockExpiry(); 405 setAttribute( user, LOCK_EXPIRY, lockExpiry == null ? "" : c_format.format( lockExpiry ) ); 406 407 // Hash and save the new password if it's different from old one 408 final String newPassword = profile.getPassword(); 409 if( newPassword != null && !newPassword.equals( "" ) ) { 410 final String oldPassword = user.getAttribute( PASSWORD ); 411 if( !oldPassword.equals( newPassword ) ) { 412 setAttribute( user, PASSWORD, getHash( newPassword ) ); 413 } 414 } 415 416 // Save the attributes as Base64 string 417 if(!profile.getAttributes().isEmpty()) { 418 try { 419 final String encodedAttributes = Serializer.serializeToBase64( profile.getAttributes() ); 420 final Element attributes = c_dom.createElement( ATTRIBUTES_TAG ); 421 user.appendChild( attributes ); 422 final Text value = c_dom.createTextNode( encodedAttributes ); 423 attributes.appendChild( value ); 424 } catch( final IOException e ) { 425 throw new WikiSecurityException( "Could not save user profile attribute. Reason: " + e.getMessage(), e ); 426 } 427 } 428 429 // Set the profile timestamps 430 if( isNew ) { 431 profile.setCreated( modDate ); 432 } 433 profile.setLastModified( modDate ); 434 435 // Commit to disk 436 saveDOM(); 437 } 438 439 /** 440 * Private method that returns the first {@link UserProfile}matching a <user> element's supplied attribute. This method will also 441 * set the UID if it has not yet been set. 442 * 443 * @param matchAttribute matching attribute 444 * @param index value to match 445 * @return the profile, or <code>null</code> if not found 446 */ 447 private UserProfile findByAttribute( final String matchAttribute, String index ) { 448 if ( c_dom == null ) { 449 throw new IllegalStateException( "FATAL: database does not exist" ); 450 } 451 452 checkForRefresh(); 453 final NodeList users = c_dom.getElementsByTagName( USER_TAG ); 454 if( users == null ) { 455 return null; 456 } 457 458 // check if we have to do a case-insensitive compare 459 final boolean caseSensitiveCompare = !matchAttribute.equals( EMAIL ); 460 461 for( int i = 0; i < users.getLength(); i++ ) { 462 final Element user = (Element) users.item( i ); 463 String userAttribute = user.getAttribute( matchAttribute ); 464 if( !caseSensitiveCompare ) { 465 userAttribute = StringUtils.lowerCase(userAttribute); 466 index = StringUtils.lowerCase(index); 467 } 468 if( userAttribute.equals( index ) ) { 469 final UserProfile profile = newProfile(); 470 471 // Parse basic attributes 472 profile.setUid( user.getAttribute( UID ) ); 473 if( profile.getUid() == null || profile.getUid().isEmpty() ) { 474 profile.setUid( generateUid( this ) ); 475 } 476 profile.setLoginName( user.getAttribute( LOGIN_NAME ) ); 477 profile.setFullname( user.getAttribute( FULL_NAME ) ); 478 profile.setPassword( user.getAttribute( PASSWORD ) ); 479 profile.setEmail( user.getAttribute( EMAIL ) ); 480 481 // Get created/modified timestamps 482 final String created = user.getAttribute( CREATED ); 483 final String modified = user.getAttribute( LAST_MODIFIED ); 484 profile.setCreated( parseDate( profile, created ) ); 485 profile.setLastModified( parseDate( profile, modified ) ); 486 487 // Is the profile locked? 488 final String lockExpiry = user.getAttribute( LOCK_EXPIRY ); 489 if( StringUtils.isEmpty( lockExpiry ) || lockExpiry.isEmpty() ) { 490 profile.setLockExpiry( null ); 491 } else { 492 profile.setLockExpiry( new Date( Long.parseLong( lockExpiry ) ) ); 493 } 494 495 // Extract all the user's attributes (should only be one attributes tag, but you never know!) 496 final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG ); 497 for( int j = 0; j < attributes.getLength(); j++ ) { 498 final Element attribute = ( Element )attributes.item( j ); 499 final String serializedMap = extractText( attribute ); 500 try { 501 final Map< String, ? extends Serializable > map = Serializer.deserializeFromBase64( serializedMap ); 502 profile.getAttributes().putAll( map ); 503 } catch( final IOException e ) { 504 LOG.error( "Could not parse user profile attributes!", e ); 505 } 506 } 507 508 return profile; 509 } 510 } 511 return null; 512 } 513 514 /** 515 * Extracts all the text nodes that are immediate children of an Element. 516 * 517 * @param element the base element 518 * @return the text nodes that are immediate children of the base element, concatenated together 519 */ 520 private String extractText( final Element element ) { 521 String text = ""; 522 if( element.getChildNodes().getLength() > 0 ) { 523 final NodeList children = element.getChildNodes(); 524 text = IntStream.range(0, children.getLength()).mapToObj(children::item).filter(child -> child.getNodeType() == Node.TEXT_NODE).map(child -> ((Text) child).getData()).collect(Collectors.joining()); 525 } 526 return text; 527 } 528 529 /** 530 * Tries to parse a date using the default format - then, for backwards compatibility reasons, tries the platform default. 531 * 532 * @param profile profile associated to the date. 533 * @param date date to be parsed. 534 * @return A parsed date, or null, if both parse attempts fail. 535 */ 536 private Date parseDate( final UserProfile profile, final String date ) { 537 try { 538 final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 539 return c_format.parse( date ); 540 } catch( final ParseException e ) { 541 try { 542 return DateFormat.getDateTimeInstance().parse( date ); 543 } catch( final ParseException e2 ) { 544 LOG.warn( "Could not parse 'created' or 'lastModified' attribute for profile '" + profile.getLoginName() + "'." + 545 " It may have been tampered with.", e2 ); 546 } 547 } 548 return null; 549 } 550 551 /** 552 * After loading the DOM, this method sanity-checks the dates in the DOM and makes sure they are formatted properly. This is sort-of 553 * hacky, but it should work. 554 */ 555 private void sanitizeDOM() { 556 if( c_dom == null ) { 557 throw new IllegalStateException( "FATAL: database does not exist" ); 558 } 559 560 final NodeList users = c_dom.getElementsByTagName( USER_TAG ); 561 for( int i = 0; i < users.getLength(); i++ ) { 562 final Element user = ( Element )users.item( i ); 563 564 // Sanitize UID (and generate a new one if one does not exist) 565 String uid = user.getAttribute( UID ).trim(); 566 if( StringUtils.isEmpty( uid ) || "-1".equals( uid ) ) { 567 uid = String.valueOf( generateUid( this ) ); 568 user.setAttribute( UID, uid ); 569 } 570 571 // Sanitize dates 572 final String loginName = user.getAttribute( LOGIN_NAME ); 573 String created = user.getAttribute( CREATED ); 574 String modified = user.getAttribute( LAST_MODIFIED ); 575 final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT ); 576 try { 577 created = c_format.format( c_format.parse( created ) ); 578 modified = c_format.format( c_format.parse( modified ) ); 579 user.setAttribute( CREATED, created ); 580 user.setAttribute( LAST_MODIFIED, modified ); 581 } catch( final ParseException e ) { 582 try { 583 created = c_format.format( DateFormat.getDateTimeInstance().parse( created ) ); 584 modified = c_format.format( DateFormat.getDateTimeInstance().parse( modified ) ); 585 user.setAttribute( CREATED, created ); 586 user.setAttribute( LAST_MODIFIED, modified ); 587 } catch( final ParseException e2 ) { 588 LOG.warn( "Could not parse 'created' or 'lastModified' attribute for profile '" + loginName + "'." 589 + " It may have been tampered with." ); 590 } 591 } 592 } 593 } 594 595 /** 596 * Private method that sets an attribute value for a supplied DOM element. 597 * 598 * @param element the element whose attribute is to be set 599 * @param attribute the name of the attribute to set 600 * @param value the desired attribute value 601 */ 602 private void setAttribute( final Element element, final String attribute, final String value ) { 603 if( value != null ) { 604 element.setAttribute( attribute, value ); 605 } 606 } 607 608}