Java tutorial
/* * 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)); } }