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 }