org.liquidsite.core.content.User.java Source code

Java tutorial

Introduction

Here is the source code for org.liquidsite.core.content.User.java

Source

/*
 * User.java
 *
 * This work is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This work 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * Copyright (c) 2004-2005 Per Cederberg. All rights reserved.
 */

package org.liquidsite.core.content;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.TimeZone;

import org.apache.commons.codec.binary.Base64;

import org.liquidsite.core.data.DataObjectException;
import org.liquidsite.core.data.DataSource;
import org.liquidsite.core.data.UserData;
import org.liquidsite.core.data.UserGroupData;
import org.liquidsite.core.data.UserGroupPeer;
import org.liquidsite.core.data.UserPeer;
import org.liquidsite.util.log.Log;

/**
 * A system user.
 *
 * @author   Per Cederberg, <per at percederberg dot net>
 * @version  1.0
 */
public class User extends PersistentObject {

    /**
     * The class logger.
     */
    private static final Log LOG = new Log(User.class);

    /**
     * The permitted user name characters.
     */
    public static final String NAME_CHARS = UPPER_CASE + LOWER_CASE + NUMBERS + BINDERS;

    /**
     * The string containing suitable password characters.
     */
    private static final String PASSWORD_CHARS = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXYZ2346789#%=+";

    /**
     * The user data.
     */
    private UserData data;

    /**
     * The user groups found in the database.
     */
    private Group[] groups = null;

    /**
     * The list of group names added since the object was saved.
     */
    private ArrayList groupsAdded = null;

    /**
     * The list of group names removed since the object was saved.
     */
    private ArrayList groupsRemoved = null;

    /**
     * Generates a password suggestion that should be sufficiently
     * hard to crack.
     *
     * @return the generated password
     */
    public static String generatePassword() {
        StringBuffer result = new StringBuffer();
        int length = PASSWORD_CHARS.length();
        char c;

        while (result.length() < 8) {
            c = PASSWORD_CHARS.charAt((int) (Math.random() * length));
            if (result.length() > 0 || Character.isLetter(c)) {
                result.append(c);
            }
        }
        return result.toString();
    }

    /**
     * Returns the number of users in a specified domain. Only users
     * with matching names will be counted.
     *
     * @param manager        the content manager to use
     * @param domain         the domain, or null for superusers
     * @param filter         the search filter (empty for all)
     *
     * @return the number of matching users in the domain
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static int countByDomain(ContentManager manager, Domain domain, String filter) throws ContentException {

        DataSource src = getDataSource(manager);
        String domainName = "";

        try {
            if (domain != null) {
                domainName = domain.getName();
            }
            return UserPeer.doCountByDomain(src, domainName, filter);
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
    }

    /**
     * Returns the number of users in a specified group.
     *
     * @param manager        the content manager to use
     * @param group          the group
     *
     * @return the number of users in the group
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static int countByGroup(ContentManager manager, Group group) throws ContentException {

        DataSource src = getDataSource(manager);

        try {
            return UserGroupPeer.doCountByGroup(src, group.getDomainName(), group.getName());
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
    }

    /**
     * Returns an array of users in a specified domain. Only users
     * with matching names will be returned. Also, only a limited
     * interval of the matching users will be returned.
     *
     * @param manager        the content manager to use
     * @param domain         the domain, or null for superusers
     * @param filter         the search filter (empty for all)
     * @param startPos       the list interval start position
     * @param maxLength      the list interval maximum length
     *
     * @return an array of matching users in the domain
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static User[] findByDomain(ContentManager manager, Domain domain, String filter, int startPos, int maxLength)
            throws ContentException {

        DataSource src = getDataSource(manager);
        ArrayList list;
        User[] res;
        String domainName = "";

        try {
            if (domain != null) {
                domainName = domain.getName();
            }
            list = UserPeer.doSelectByDomain(src, domainName, filter, startPos, maxLength);
            res = new User[list.size()];
            for (int i = 0; i < list.size(); i++) {
                res[i] = new User(manager, (UserData) list.get(i));
            }
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
        return res;
    }

    /**
     * Returns a user with a specified name.
     *
     * @param manager        the content manager to use
     * @param domain         the domain, or null for superusers
     * @param name           the user name
     *
     * @return the user found, or
     *         null if no matching user existed
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static User findByName(ContentManager manager, Domain domain, String name) throws ContentException {

        DataSource src = getDataSource(manager);
        UserData data;
        String domainName = "";

        try {
            if (domain != null) {
                domainName = domain.getName();
            }
            data = UserPeer.doSelectByName(src, domainName, name);
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
        if (data == null) {
            return null;
        } else {
            return new User(manager, data);
        }
    }

    /**
     * Returns a user with a specified email address.
     *
     * @param manager        the content manager to use
     * @param domain         the domain, or null for superusers
     * @param email          the user email address
     *
     * @return the user found, or
     *         null if no matching user existed
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static User findByEmail(ContentManager manager, Domain domain, String email) throws ContentException {

        DataSource src = getDataSource(manager);
        UserData data;
        String domainName = "";

        try {
            if (domain != null) {
                domainName = domain.getName();
            }
            data = UserPeer.doSelectByEmail(src, domainName, email);
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
        if (data == null) {
            return null;
        } else {
            return new User(manager, data);
        }
    }

    /**
     * Returns an array of all users in a certain group. Only a
     * limited interval of the matching users will be returned.
     *
     * @param manager        the content manager to use
     * @param group          the group
     * @param startPos       the list interval start position
     * @param maxLength      the list interval maximum length
     *
     * @return an array of all users in the group
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    static User[] findByGroup(ContentManager manager, Group group, int startPos, int maxLength)
            throws ContentException {

        DataSource src = getDataSource(manager);
        ArrayList list;
        User[] res;
        UserGroupData data;
        UserData user;
        String name;

        try {
            list = UserGroupPeer.doSelectByGroup(src, group.getDomainName(), group.getName(), startPos, maxLength);
            res = new User[list.size()];
            for (int i = 0; i < list.size(); i++) {
                data = (UserGroupData) list.get(i);
                name = data.getString(UserGroupData.USER);
                user = UserPeer.doSelectByName(src, group.getDomainName(), name);
                res[i] = new User(manager, user);
            }
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        } finally {
            src.close();
        }
        return res;
    }

    /**
     * Creates a new user with default values.
     *
     * @param manager        the content manager to use
     * @param domain         the domain, or null for a superuser
     * @param name           the user name
     */
    public User(ContentManager manager, Domain domain, String name) {
        super(manager, false);
        this.data = new UserData();
        if (domain == null) {
            this.data.setString(UserData.DOMAIN, "");
        } else {
            this.data.setString(UserData.DOMAIN, domain.getName());
        }
        this.data.setString(UserData.NAME, name);
    }

    /**
     * Creates a new user from a data object.
     *
     * @param manager        the content manager to use
     * @param data           the user data object
     */
    private User(ContentManager manager, UserData data) {
        super(manager, true);
        this.data = data;
    }

    /**
     * Checks if this user equals another object. This method will
     * only return true if the other object is a user with the same
     * domain and user name.
     *
     * @param obj            the object to compare with
     *
     * @return true if the other object is an identical user, or
     *         false otherwise
     */
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return equals((User) obj);
        } else {
            return false;
        }
    }

    /**
     * Checks if this user equals another user. This method will
     * only return true if the other object is a user with the same
     * domain and user name.
     *
     * @param obj            the object to compare with
     *
     * @return true if the other object is an identical user, or
     *         false otherwise
     */
    public boolean equals(User obj) {
        return getDomainName().equals(obj.getDomainName()) && getName().equals(obj.getName());
    }

    /**
     * Returns a string representation of this object.
     *
     * @return a string representation of this object
     */
    public String toString() {
        return getName();
    }

    /**
     * Checks if this user is a super user. A super user should have
     * access to all objects in the system, and does not belong to
     * any domain.
     *
     * @return true if the user represents a super user, or
     *         false otherwise
     */
    public boolean isSuperUser() {
        return getDomainName().equals("");
    }

    /**
     * Returns the user domain.
     *
     * @return the user domain, or null if the user is a superuser
     *
     * @throws ContentException if no content manager is available
     */
    public Domain getDomain() throws ContentException {
        if (getDomainName().equals("")) {
            return null;
        } else {
            return getContentManager().getDomain(getDomainName());
        }
    }

    /**
     * Returns the user domain name
     *
     * @return the user domain name
     */
    public String getDomainName() {
        return data.getString(UserData.DOMAIN);
    }

    /**
     * Returns the user name.
     *
     * @return the user name
     */
    public String getName() {
        return data.getString(UserData.NAME);
    }

    /**
     * Returns the encoded user password.
     *
     * @return the encoded user password
     */
    public String getPassword() {
        return data.getString(UserData.PASSWORD);
    }

    /**
     * Sets the user password. This method will hash and encode the
     * specified password, which is an irreversible process.
     *
     * @param password       the new user password
     */
    public void setPassword(String password) {
        setPasswordEncoded(createHash(getName() + password));
    }

    /**
     * Sets the encoded user password. This method assumes that the
     * specified password has already been hashed and encoded and
     * should only be used when restoring user passwords from backups. 
     *
     * @param password       the new encoded user password
     */
    public void setPasswordEncoded(String password) {
        data.setString(UserData.PASSWORD, password);
    }

    /**
     * Returns the enabled flag.
     *
     * @return true if the user is enabled, or
     *         false otherwise
     */
    public boolean getEnabled() {
        return data.getBoolean(UserData.ENABLED);
    }

    /**
     * Sets the enabled flag.
     *
     * @param enabled         the new enabled flag
     */
    public void setEnabled(boolean enabled) {
        data.setBoolean(UserData.ENABLED, enabled);
    }

    /**
     * Verifies the user password. This method will hash and encode
     * the specified password, and compare the result with the real
     * password. This method can be used to verify user logins and
     * will return false if the enabled flag isn't set.
     *
     * @param password       the user password
     *
     * @return true if the passwords are identical, or
     *         false otherwise
     */
    public boolean verifyPassword(String password) {
        // TODO: remove hack that allows empty passwords
        if (!getEnabled()) {
            return false;
        } else if (getPassword().equals("")) {
            return true;
        } else {
            return getPassword().equals(createHash(getName() + password));
        }
    }

    /**
     * Returns the real user name.
     *
     * @return the real user name
     */
    public String getRealName() {
        return data.getString(UserData.REAL_NAME);
    }

    /**
     * Sets the real user name.
     *
     * @param realName       the new real user name
     */
    public void setRealName(String realName) {
        data.setString(UserData.REAL_NAME, realName);
    }

    /**
     * Returns the user e-mail address.
     *
     * @return the user e-mail address
     */
    public String getEmail() {
        return data.getString(UserData.EMAIL);
    }

    /**
     * Sets the user e-mail address.
     *
     * @param email          the new user e-mail address
     */
    public void setEmail(String email) {
        data.setString(UserData.EMAIL, email);
    }

    /**
     * Returns the user time zone.
     *
     * @return the user time zone
     */
    public TimeZone getTimeZone() {
        // TODO: the timezone should be stored in the database
        return TimeZone.getTimeZone("Europe/Stockholm");
    }

    /**
     * Returns the user comment.
     *
     * @return the user comment
     */
    public String getComment() {
        return data.getString(UserData.COMMENT);
    }

    /**
     * Sets the user comment.
     *
     * @param comment        the new user comment
     */
    public void setComment(String comment) {
        data.setString(UserData.COMMENT, comment);
    }

    /**
     * Returns the groups that this user belongs to. This method will
     * only return the groups registered to this user in the database.
     * The results will also be cached to return the same list every
     * time, until this object is written to the database.
     *
     * @return an array of groups this user belongs to
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    public Group[] getGroups() throws ContentException {
        if (groups == null) {
            groups = Group.findByUser(getContentManager(), this);
        }
        return groups;
    }

    /**
     * Adds this user to the specified group. This action will not
     * take effect until this object is saved.
     *
     * @param group          the group object
     */
    public void addToGroup(Group group) {
        if (getDomainName().equals(group.getDomainName())) {
            if (groupsAdded == null) {
                groupsAdded = new ArrayList();
            }
            groupsAdded.add(group.getName());
        }
    }

    /**
     * Removes this user from the specified group. This action will
     * not take effect until this object is saved.
     *
     * @param group          the group object
     */
    public void removeFromGroup(Group group) {
        if (getDomainName().equals(group.getDomainName())) {
            if (groupsRemoved == null) {
                groupsRemoved = new ArrayList();
            }
            groupsRemoved.add(group.getName());
        }
    }

    /**
     * Validates the object data before writing to the database.
     *
     * @throws ContentException if the object data wasn't valid
     */
    protected void doValidate() throws ContentException {
        ContentManager manager = getContentManager();

        if (!isPersistent()) {
            if (!getDomainName().equals("") && getDomain() == null) {
                throw new ContentException("domain '" + getDomainName() + "' does not exist");
            }
            validateSize("user name", getName(), 1, 30);
            validateChars("user name", getName(), NAME_CHARS);
            if (manager.getUser(getDomain(), getName()) != null) {
                throw new ContentException("user '" + getName() + "' already exists");
            }
        }
        validateSize("user password", getPassword(), 1, 30);
        validateSize("user real name", getRealName(), 0, 100);
        validateSize("user email", getEmail(), 0, 100);
        validateSize("user comment", getComment(), 0, 200);
    }

    /**
     * Inserts the object data into the database. If the restore flag
     * is set, no automatic changes should be made to the data before
     * writing to the database.
     *
     * @param src            the data source to use
     * @param user           the user performing the operation
     * @param restore        the restore flag
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    protected void doInsert(DataSource src, User user, boolean restore) throws ContentException {

        try {
            UserPeer.doInsert(src, data);
            doUserGroups(src);
            groups = null;
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        }
    }

    /**
     * Updates the object data in the database.
     *
     * @param src            the data source to use
     * @param user           the user performing the operation
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    protected void doUpdate(DataSource src, User user) throws ContentException {

        try {
            UserPeer.doUpdate(src, data);
            doUserGroups(src);
            groups = null;
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        }
    }

    /**
     * Deletes the object data from the database.
     *
     * @param src            the data source to use
     * @param user           the user performing the operation
     *
     * @throws ContentException if the database couldn't be accessed
     *             properly
     */
    protected void doDelete(DataSource src, User user) throws ContentException {

        try {
            UserPeer.doDelete(src, data);
            groups = null;
        } catch (DataObjectException e) {
            LOG.error(e.getMessage());
            throw new ContentException(e);
        }
    }

    /**
     * Adds and removes user groups from the database.
     *
     * @param src            the data source to use
     *
     * @throws DataObjectException if the data source couldn't be
     *             accessed properly
     */
    private void doUserGroups(DataSource src) throws DataObjectException {
        UserGroupData groupData;

        // Handle added groups
        if (groupsAdded != null) {
            for (int i = 0; i < groupsAdded.size(); i++) {
                groupData = new UserGroupData();
                groupData.setString(UserGroupData.DOMAIN, getDomainName());
                groupData.setString(UserGroupData.USER, getName());
                groupData.setString(UserGroupData.GROUP, groupsAdded.get(i).toString());
                UserGroupPeer.doInsert(src, groupData);
            }
            groupsAdded = null;
        }

        // Handle removed groups
        if (groupsRemoved != null) {
            for (int i = 0; i < groupsRemoved.size(); i++) {
                groupData = new UserGroupData();
                groupData.setString(UserGroupData.DOMAIN, getDomainName());
                groupData.setString(UserGroupData.USER, getName());
                groupData.setString(UserGroupData.GROUP, groupsRemoved.get(i).toString());
                UserGroupPeer.doDelete(src, groupData);
            }
            groupsRemoved = null;
        }
    }

    /**
     * Creates an ASCII hash value for a string. The hash value
     * calculation is irreversible, and is calculated with the MD5
     * algorithm and encoded with base-64.
     *
     * @param input           the input string data
     *
     * @return the encoded hash value
     */
    private String createHash(String input) {
        MessageDigest digest;
        byte bytes[];

        // Compute MD5 digest
        try {
            digest = MessageDigest.getInstance("MD5");
            digest.reset();
            digest.update(input.getBytes());
            bytes = digest.digest();
        } catch (NoSuchAlgorithmException e) {
            LOG.error(e.getMessage());
            return "";
        }

        // Base-64 encode digest
        return new String(Base64.encodeBase64(bytes));
    }
}