de.knowwe.defi.usermanager.XMLUserDatabase.java Source code

Java tutorial

Introduction

Here is the source code for de.knowwe.defi.usermanager.XMLUserDatabase.java

Source

/*
 * Copyright (C) 2013 denkbares GmbH
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option) any
 * later version.
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
 * site: http://www.fsf.org.
 */
package de.knowwe.defi.usermanager;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.security.Principal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang.StringUtils;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
import org.apache.wiki.auth.NoSuchPrincipalException;
import org.apache.wiki.auth.WikiPrincipal;
import org.apache.wiki.auth.WikiSecurityException;
import org.apache.wiki.auth.user.AbstractUserDatabase;
import org.apache.wiki.auth.user.DuplicateUserException;
import org.apache.wiki.auth.user.UserProfile;
import org.apache.wiki.util.Serializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import com.denkbares.utils.Log;

/**
 * <p>Manages {@link org.apache.wiki.auth.user.DefaultUserProfile} objects using XML files for persistence.
 * Passwords are hashed using SHA1. User entries are simple <code>&lt;user&gt;</code>
 * elements under the root. User profile properties are attributes of the
 * element. For example:</p>
 * <blockquote><code>
 * &lt;users&gt;<br/>
 * &nbsp;&nbsp;&lt;user loginName="janne" fullName="Janne Jalkanen"<br/> 
 * &nbsp;&nbsp;&nbsp;&nbsp;wikiName="JanneJalkanen" email="janne@ecyrd.com"<br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;password="{SHA}457b08e825da547c3b77fbc1ff906a1d00a7daee"/&gt;<br/>
 * &lt;/users&gt;
 * </code></blockquote> 
 * <p>In this example, the un-hashed password is <code>myP@5sw0rd</code>. Passwords are hashed without salt.</p>
 * @since 2.3
 */

// FIXME: If the DB is shared across multiple systems, it's possible to lose accounts
//        if two people add new accounts right after each other from different wikis.
public class XMLUserDatabase extends AbstractUserDatabase {

    /**
     * The jspwiki.properties property specifying the file system location of
     * the user database.
     */
    public static final String PROP_USERDATABASE = "jspwiki.xmlUserDatabaseFile";

    private static final String DEFAULT_USERDATABASE = "userdatabase.xml";

    private static final String ATTRIBUTES_TAG = "attributes";

    private static final String CREATED = "created";

    private static final String EMAIL = "email";

    private static final String FULL_NAME = "fullName";

    private static final String LOGIN_NAME = "loginName";

    private static final String LAST_MODIFIED = "lastModified";

    private static final String LOCK_EXPIRY = "lockExpiry";

    private static final String PASSWORD = "password";

    private static final String UID = "uid";

    private static final String USER_TAG = "user";

    private static final String WIKI_NAME = "wikiName";

    private static final String DATE_FORMAT = "yyyy.MM.dd 'at' HH:mm:ss:SSS z";

    private Document c_dom = null;

    private File c_file = null;

    /**
     * Looks up and deletes the first {@link org.apache.wiki.auth.user.UserProfile} in the user database
     * that matches a profile having a given login name. If the user database
     * does not contain a user with a matching attribute, throws a
     * {@link org.apache.wiki.auth.NoSuchPrincipalException}.
     * @param loginName the login name of the user profile that shall be deleted
     */
    @Override
    public synchronized void deleteByLoginName(String loginName) throws WikiSecurityException {
        if (c_dom == null) {
            throw new WikiSecurityException("FATAL: database does not exist");
        }

        NodeList users = c_dom.getDocumentElement().getElementsByTagName(USER_TAG);
        for (int i = 0; i < users.getLength(); i++) {
            Element user = (Element) users.item(i);
            if (user.getAttribute(LOGIN_NAME).equals(loginName)) {
                c_dom.getDocumentElement().removeChild(user);

                // Commit to disk
                saveDOM();
                return;
            }
        }
        throw new NoSuchPrincipalException("Not in database: " + loginName);
    }

    /**
     * Looks up and returns the first {@link org.apache.wiki.auth.user.UserProfile}in the user database
     * that matches a profile having a given e-mail address. If the user
     * database does not contain a user with a matching attribute, throws a
     * {@link org.apache.wiki.auth.NoSuchPrincipalException}.
     * @param index the e-mail address of the desired user profile
     * @return the user profile
     * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(String)
     */
    @Override
    public UserProfile findByEmail(String index) throws NoSuchPrincipalException {
        UserProfile profile = findByAttribute(EMAIL, index);
        if (profile != null) {
            return profile;
        }
        throw new NoSuchPrincipalException("Not in database: " + index);
    }

    /**
     * Looks up and returns the first {@link org.apache.wiki.auth.user.UserProfile}in the user database
     * that matches a profile having a given full name. If the user database
     * does not contain a user with a matching attribute, throws a
     * {@link org.apache.wiki.auth.NoSuchPrincipalException}.
     * @param index the fill name of the desired user profile
     * @return the user profile
     * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(String)
     */
    @Override
    public UserProfile findByFullName(String index) throws NoSuchPrincipalException {
        UserProfile profile = findByAttribute(FULL_NAME, index);
        if (profile != null) {
            return profile;
        }
        throw new NoSuchPrincipalException("Not in database: " + index);
    }

    /**
     * Looks up and returns the first {@link org.apache.wiki.auth.user.UserProfile}in the user database
     * that matches a profile having a given login name. If the user database
     * does not contain a user with a matching attribute, throws a
     * {@link org.apache.wiki.auth.NoSuchPrincipalException}.
     * @param index the login name of the desired user profile
     * @return the user profile
     * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(String)
     */
    @Override
    public UserProfile findByLoginName(String index) throws NoSuchPrincipalException {
        UserProfile profile = findByAttribute(LOGIN_NAME, index);
        if (profile != null) {
            return profile;
        }
        throw new NoSuchPrincipalException("Not in database: " + index);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public UserProfile findByUid(String uid) throws NoSuchPrincipalException {
        UserProfile profile = findByAttribute(UID, uid);
        if (profile != null) {
            return profile;
        }
        throw new NoSuchPrincipalException("Not in database: " + uid);
    }

    /**
     * Looks up and returns the first {@link org.apache.wiki.auth.user.UserProfile}in the user database
     * that matches a profile having a given wiki name. If the user database
     * does not contain a user with a matching attribute, throws a
     * {@link org.apache.wiki.auth.NoSuchPrincipalException}.
     * @param index the wiki name of the desired user profile
     * @return the user profile
     * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(String)
     */
    @Override
    public UserProfile findByWikiName(String index) throws NoSuchPrincipalException {
        UserProfile profile = findByAttribute(WIKI_NAME, index);
        if (profile != null) {
            return profile;
        }
        throw new NoSuchPrincipalException("Not in database: " + index);
    }

    /**
     * Returns all WikiNames that are stored in the UserDatabase
     * as an array of WikiPrincipal objects. If the database does not
     * contain any profiles, this method will return a zero-length
     * array.
     * @return the WikiNames
     * @throws org.apache.wiki.auth.WikiSecurityException In case things fail.
     */
    @Override
    public Principal[] getWikiNames() throws WikiSecurityException {
        if (c_dom == null) {
            throw new IllegalStateException("FATAL: database does not exist");
        }
        SortedSet<Principal> principals = new TreeSet<>();
        NodeList users = c_dom.getElementsByTagName(USER_TAG);
        for (int i = 0; i < users.getLength(); i++) {
            Element user = (Element) users.item(i);
            String wikiName = user.getAttribute(WIKI_NAME);
            if (wikiName == null) {
                Log.warning("Detected null wiki name in XMLUserDataBase. Check your user database.");
            } else {
                Principal principal = new WikiPrincipal(wikiName, WikiPrincipal.WIKI_NAME);
                principals.add(principal);
            }
        }
        return principals.toArray(new Principal[principals.size()]);
    }

    /**
     * Initializes the user database based on values from a Properties object.
     * The properties object must contain a file path to the XML database file
     * whose key is {@link #PROP_USERDATABASE}.
     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine,
     *      java.util.Properties)
     * @throws org.apache.wiki.api.exceptions.NoRequiredPropertyException if the user database cannot be located, parsed, or opened
     */
    @Override
    public void initialize(WikiEngine engine, Properties props) throws NoRequiredPropertyException {
        File defaultFile = null;
        if (engine.getRootPath() == null) {
            Log.warning("Cannot identify JSPWiki root path");
            defaultFile = new File("WEB-INF/" + DEFAULT_USERDATABASE).getAbsoluteFile();
        } else {
            defaultFile = new File(engine.getRootPath() + "/WEB-INF/" + DEFAULT_USERDATABASE);
        }

        // Get database file location
        String file = props.getProperty(PROP_USERDATABASE);
        if (file == null) {
            Log.warning("XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile);
            c_file = defaultFile;
        } else {
            c_file = new File(file);
        }

        Log.info("XML user database at " + c_file.getAbsolutePath());

        buildDOM();
        sanitizeDOM();
    }

    private void buildDOM() {
        // Read DOM
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);
        factory.setExpandEntityReferences(false);
        factory.setIgnoringComments(true);
        factory.setNamespaceAware(false);
        try {
            c_dom = factory.newDocumentBuilder().parse(c_file);
            Log.fine("Database successfully initialized");
            c_lastModified = c_file.lastModified();
            c_lastCheck = System.currentTimeMillis();
        } catch (ParserConfigurationException e) {
            Log.severe("Configuration error: " + e.getMessage());
        } catch (SAXException e) {
            Log.severe("SAX error: " + e.getMessage());
        } catch (FileNotFoundException e) {
            Log.info("User database not found; creating from scratch...");
        } catch (IOException e) {
            Log.severe("IO error: " + e.getMessage());
        }
        if (c_dom == null) {
            try {
                //
                //  Create the DOM from scratch
                //
                c_dom = factory.newDocumentBuilder().newDocument();
                c_dom.appendChild(c_dom.createElement("users"));
            } catch (ParserConfigurationException e) {
                Log.severe("Could not create in-memory DOM");
            }
        }
    }

    private void saveDOM() throws WikiSecurityException {
        if (c_dom == null) {
            Log.severe("User database doesn't exist in memory.");
        }

        File newFile = new File(c_file.getAbsolutePath() + ".new");
        try {
            BufferedWriter io = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF-8"));

            // Write the file header and document root
            io.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            io.write("<users>\n");

            // Write each profile as a <user> node
            Element root = c_dom.getDocumentElement();
            NodeList nodes = root.getElementsByTagName(USER_TAG);
            for (int i = 0; i < nodes.getLength(); i++) {
                Element user = (Element) nodes.item(i);
                io.write("    <" + USER_TAG + " ");
                io.write(UID);
                io.write("=\"" + user.getAttribute(UID) + "\" ");
                io.write(LOGIN_NAME);
                io.write("=\"" + user.getAttribute(LOGIN_NAME) + "\" ");
                io.write(WIKI_NAME);
                io.write("=\"" + user.getAttribute(WIKI_NAME) + "\" ");
                io.write(FULL_NAME);
                io.write("=\"" + user.getAttribute(FULL_NAME) + "\" ");
                io.write(EMAIL);
                io.write("=\"" + user.getAttribute(EMAIL) + "\" ");
                io.write(PASSWORD);
                io.write("=\"" + user.getAttribute(PASSWORD) + "\" ");
                io.write(CREATED);
                io.write("=\"" + user.getAttribute(CREATED) + "\" ");
                io.write(LAST_MODIFIED);
                io.write("=\"" + user.getAttribute(LAST_MODIFIED) + "\" ");
                io.write(LOCK_EXPIRY);
                io.write("=\"" + user.getAttribute(LOCK_EXPIRY) + "\" ");
                io.write(">");
                NodeList attributes = user.getElementsByTagName(ATTRIBUTES_TAG);
                for (int j = 0; j < attributes.getLength(); j++) {
                    Element attribute = (Element) attributes.item(j);
                    String value = extractText(attribute);
                    io.write("\n        <" + ATTRIBUTES_TAG + ">");
                    io.write(value);
                    io.write("</" + ATTRIBUTES_TAG + ">");
                }
                io.write("\n    </" + USER_TAG + ">\n");
            }
            io.write("</users>");
            io.close();
        } catch (IOException e) {
            throw new WikiSecurityException(e.getLocalizedMessage(), e);
        }

        // Copy new file over old version
        File backup = new File(c_file.getAbsolutePath() + ".old");
        if (backup.exists()) {
            if (!backup.delete()) {
                Log.severe("Could not delete old user database backup: " + backup);
            }
        }
        if (!c_file.renameTo(backup)) {
            Log.severe("Could not create user database backup: " + backup);
        }
        if (!newFile.renameTo(c_file)) {
            Log.severe("Could not save database: " + backup + " restoring backup.");
            if (!backup.renameTo(c_file)) {
                Log.severe("Restore failed. Check the file permissions.");
            }
            Log.severe("Could not save database: " + c_file + ". Check the file permissions");
        }
    }

    private long c_lastCheck = 0;
    private long c_lastModified = 0;

    private void checkForRefresh() {
        long time = System.currentTimeMillis();

        if (time - c_lastCheck > 60 * 1000L) {
            long lastModified = c_file.lastModified();

            if (lastModified > c_lastModified) {
                buildDOM();
            }
        }
    }

    /**
     * @see org.apache.wiki.auth.user.UserDatabase#rename(String, String)
     */
    @Override
    public synchronized void rename(String loginName, String newName)
            throws DuplicateUserException, WikiSecurityException {
        if (c_dom == null) {
            Log.severe("Could not rename profile '" + loginName + "'; database does not exist");
            throw new IllegalStateException("FATAL: database does not exist");
        }
        checkForRefresh();

        // Get the existing user; if not found, throws NoSuchPrincipalException
        UserProfile profile = findByLoginName(loginName);

        // Get user with the proposed name; if found, it's a collision
        try {
            UserProfile otherProfile = findByLoginName(newName);
            if (otherProfile != null) {
                throw new DuplicateUserException("security.error.cannot.rename", newName);
            }
        } catch (NoSuchPrincipalException e) {
            // Good! That means it's safe to save using the new name
        }

        // Find the user with the old login id attribute, and change it
        NodeList users = c_dom.getElementsByTagName(USER_TAG);
        for (int i = 0; i < users.getLength(); i++) {
            Element user = (Element) users.item(i);
            if (user.getAttribute(LOGIN_NAME).equals(loginName)) {
                DateFormat c_format = new SimpleDateFormat(DATE_FORMAT);
                Date modDate = new Date(System.currentTimeMillis());
                setAttribute(user, LOGIN_NAME, newName);
                setAttribute(user, LAST_MODIFIED, c_format.format(modDate));
                profile.setLoginName(newName);
                profile.setLastModified(modDate);
                break;
            }
        }

        // Commit to disk
        saveDOM();
    }

    /**
     * Saves a {@link org.apache.wiki.auth.user.UserProfile}to the user database, overwriting the
     * existing profile if it exists. The user name under which the profile
     * should be saved is returned by the supplied profile's
     * {@link org.apache.wiki.auth.user.UserProfile#getLoginName()}method.
     * @param profile the user profile to save
     * @throws org.apache.wiki.auth.WikiSecurityException if the profile cannot be saved
     */
    @Override
    public synchronized void save(UserProfile profile) throws WikiSecurityException {
        if (c_dom == null) {
            Log.severe("Could not save profile " + profile + " database does not exist");
            throw new IllegalStateException("FATAL: database does not exist");
        }

        checkForRefresh();

        DateFormat c_format = new SimpleDateFormat(DATE_FORMAT);
        String index = profile.getLoginName();
        NodeList users = c_dom.getElementsByTagName(USER_TAG);
        Element user = null;
        for (int i = 0; i < users.getLength(); i++) {
            Element currentUser = (Element) users.item(i);
            if (currentUser.getAttribute(LOGIN_NAME).equals(index)) {
                user = currentUser;
                break;
            }
        }

        boolean isNew = false;

        Date modDate = new Date(System.currentTimeMillis());
        if (user == null) {
            // Create new user node
            profile.setCreated(modDate);
            Log.info("Creating new user " + index);
            user = c_dom.createElement(USER_TAG);
            c_dom.getDocumentElement().appendChild(user);
            setAttribute(user, CREATED, c_format.format(profile.getCreated()));
            isNew = true;
        } else {
            // To update existing user node, delete old attributes first...
            NodeList attributes = user.getElementsByTagName(ATTRIBUTES_TAG);
            for (int i = 0; i < attributes.getLength(); i++) {
                user.removeChild(attributes.item(i));
            }
        }

        setAttribute(user, UID, profile.getUid());
        setAttribute(user, LAST_MODIFIED, c_format.format(modDate));
        setAttribute(user, LOGIN_NAME, profile.getLoginName());
        setAttribute(user, FULL_NAME, profile.getFullname());
        setAttribute(user, WIKI_NAME, profile.getWikiName());
        setAttribute(user, EMAIL, profile.getEmail());
        Date lockExpiry = profile.getLockExpiry();
        setAttribute(user, LOCK_EXPIRY, lockExpiry == null ? "" : c_format.format(lockExpiry));

        // Hash and save the new password if it's different from old one
        String newPassword = profile.getPassword();
        if (newPassword != null && !newPassword.equals("")) {
            String oldPassword = user.getAttribute(PASSWORD);
            if (!oldPassword.equals(newPassword)) {
                setAttribute(user, PASSWORD, getHash(newPassword));
            }
        }

        // Save the attributes as as Base64 string
        if (!profile.getAttributes().isEmpty()) {
            try {
                String encodedAttributes = Serializer.serializeToBase64(profile.getAttributes());
                Element attributes = c_dom.createElement(ATTRIBUTES_TAG);
                user.appendChild(attributes);
                Text value = c_dom.createTextNode(encodedAttributes);
                attributes.appendChild(value);
            } catch (IOException e) {
                throw new WikiSecurityException("Could not save user profile attribute. Reason: " + e.getMessage(),
                        e);
            }
        }

        // Set the profile timestamps
        if (isNew) {
            profile.setCreated(modDate);
        }
        profile.setLastModified(modDate);

        // Commit to disk
        saveDOM();
    }

    /**
     * Private method that returns the first {@link org.apache.wiki.auth.user.UserProfile}matching a
     * &lt;user&gt; element's supplied attribute. This method will also
     * set the UID if it has not yet been set.
     * @param matchAttribute
     * @param index
     * @return the profile, or <code>null</code> if not found
     */
    private UserProfile findByAttribute(String matchAttribute, String index) {
        if (c_dom == null) {
            throw new IllegalStateException("FATAL: database does not exist");
        }

        checkForRefresh();

        NodeList users = c_dom.getElementsByTagName(USER_TAG);

        if (users == null)
            return null;

        // check if we have to do a case insensitive compare
        boolean caseSensitiveCompare = true;
        if (matchAttribute.equals(EMAIL)) {
            caseSensitiveCompare = false;
        }

        for (int i = 0; i < users.getLength(); i++) {
            Element user = (Element) users.item(i);
            String userAttribute = user.getAttribute(matchAttribute);
            if (!caseSensitiveCompare) {
                userAttribute = StringUtils.lowerCase(userAttribute);
                index = StringUtils.lowerCase(index);
            }
            if (userAttribute.equals(index)) {
                UserProfile profile = newProfile();

                // Parse basic attributes
                profile.setUid(user.getAttribute(UID));
                if (profile.getUid() == null || profile.getUid().isEmpty()) {
                    profile.setUid(generateUid(this));
                }
                profile.setLoginName(user.getAttribute(LOGIN_NAME));
                profile.setFullname(user.getAttribute(FULL_NAME));
                profile.setPassword(user.getAttribute(PASSWORD));
                profile.setEmail(user.getAttribute(EMAIL));

                // Get created/modified timestamps
                String created = user.getAttribute(CREATED);
                String modified = user.getAttribute(LAST_MODIFIED);
                profile.setCreated(parseDate(profile, created));
                profile.setLastModified(parseDate(profile, modified));

                // Is the profile locked?
                String lockExpiry = user.getAttribute(LOCK_EXPIRY);
                if (lockExpiry == null || lockExpiry.isEmpty()) {
                    profile.setLockExpiry(null);
                } else {
                    profile.setLockExpiry(parseDate(profile, lockExpiry));
                }

                // Extract all of the user's attributes (should only be one attributes tag, but you never know!)
                NodeList attributes = user.getElementsByTagName(ATTRIBUTES_TAG);
                for (int j = 0; j < attributes.getLength(); j++) {
                    Element attribute = (Element) attributes.item(j);
                    String serializedMap = extractText(attribute);
                    try {
                        Map<String, ? extends Serializable> map = Serializer.deserializeFromBase64(serializedMap);
                        profile.getAttributes().putAll(map);
                    } catch (IOException e) {
                        Log.severe("Could not parse user profile attributes!", e);
                    }
                }

                return profile;
            }
        }
        return null;
    }

    /**
     * Extracts all of the text nodes that are immediate children of an Element.
     * @param element the base element
     * @return the text nodes that are immediate children of the base element, concatenated together
     */
    private String extractText(Element element) {
        String text = "";
        if (element.getChildNodes().getLength() > 0) {
            NodeList children = element.getChildNodes();
            for (int k = 0; k < children.getLength(); k++) {
                Node child = children.item(k);
                if (child.getNodeType() == Node.TEXT_NODE) {
                    text = text + ((Text) child).getData();
                }
            }
        }
        return text;
    }

    /**
     *  Tries to parse a date using the default format - then, for backwards
     *  compatibility reasons, tries the platform default.
     *  
     *  @param profile
     *  @param date
     *  @return A parsed date, or null, if both parse attempts fail.
     */
    private Date parseDate(UserProfile profile, String date) {
        try {
            DateFormat c_format = new SimpleDateFormat(DATE_FORMAT);
            return c_format.parse(date);
        } catch (ParseException e) {
            try {
                return DateFormat.getDateTimeInstance().parse(date);
            } catch (ParseException e2) {
                Log.warning("Could not parse 'created' or 'lastModified' " + "attribute for " + " profile '"
                        + profile.getLoginName() + "'." + " It may have been tampered with.");
            }
        }
        return null;
    }

    /**
     * After loading the DOM, this method sanity-checks the dates in the DOM and makes
     * sure they are formatted properly. This is sort-of hacky, but it should work.
     */
    private void sanitizeDOM() {
        if (c_dom == null) {
            throw new IllegalStateException("FATAL: database does not exist");
        }

        NodeList users = c_dom.getElementsByTagName(USER_TAG);
        for (int i = 0; i < users.getLength(); i++) {
            Element user = (Element) users.item(i);

            // Sanitize UID (and generate a new one if one does not exist)
            String uid = user.getAttribute(UID).trim();
            if (uid == null || uid.isEmpty() || "-1".equals(uid)) {
                uid = String.valueOf(generateUid(this));
                user.setAttribute(UID, uid);
            }

            // Sanitize dates
            String loginName = user.getAttribute(LOGIN_NAME);
            String created = user.getAttribute(CREATED);
            String modified = user.getAttribute(LAST_MODIFIED);
            DateFormat c_format = new SimpleDateFormat(DATE_FORMAT);
            try {
                created = c_format.format(c_format.parse(created));
                modified = c_format.format(c_format.parse(modified));
                user.setAttribute(CREATED, created);
                user.setAttribute(LAST_MODIFIED, modified);
            } catch (ParseException e) {
                try {
                    created = c_format.format(DateFormat.getDateTimeInstance().parse(created));
                    modified = c_format.format(DateFormat.getDateTimeInstance().parse(modified));
                    user.setAttribute(CREATED, created);
                    user.setAttribute(LAST_MODIFIED, modified);
                } catch (ParseException e2) {
                    Log.warning("Could not parse 'created' or 'lastModified' attribute for profile '" + loginName
                            + "'." + " It may have been tampered with.");
                }
            }
        }
    }

    /**
     * Private method that sets an attribute value for a supplied DOM element.
     * @param element the element whose attribute is to be set
     * @param attribute the name of the attribute to set
     * @param value the desired attribute value
     */
    private void setAttribute(Element element, String attribute, String value) {
        if (value != null) {
            element.setAttribute(attribute, value);
        }
    }
}