001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.auth.user;
021:
022: import java.io.UnsupportedEncodingException;
023: import java.security.MessageDigest;
024: import java.security.NoSuchAlgorithmException;
025: import java.security.Principal;
026: import java.util.ArrayList;
027: import java.util.Properties;
028:
029: import org.apache.catalina.util.HexUtils;
030: import org.apache.log4j.Logger;
031:
032: import com.ecyrd.jspwiki.NoRequiredPropertyException;
033: import com.ecyrd.jspwiki.WikiEngine;
034: import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
035: import com.ecyrd.jspwiki.auth.WikiPrincipal;
036: import com.ecyrd.jspwiki.auth.WikiSecurityException;
037:
038: /**
039: * Abstract UserDatabase class that provides convenience methods for finding
040: * profiles, building Principal collections and hashing passwords.
041: * @author Andrew R. Jaquith
042: * @since 2.3
043: */
044: public abstract class AbstractUserDatabase implements UserDatabase {
045:
046: protected static final Logger log = Logger
047: .getLogger(AbstractUserDatabase.class);
048: protected static final String SHA_PREFIX = "{SHA}";
049: protected static final String PROP_SHARED_WITH_CONTAINER = "jspwiki.userdatabase.isSharedWithContainer";
050:
051: /**
052: * No-op method that in previous versions of JSPWiki was intended to
053: * atomically commit changes to the user database. Now, the {@link #rename(String, String)},
054: * {@link #save(UserProfile)} and {@link #deleteByLoginName(String)} methods
055: * are atomic themselves.
056: * @throws WikiSecurityException
057: * @deprecated there is no need to call this method because the save, rename and
058: * delete methods contain their own commit logic
059: */
060: public synchronized void commit() throws WikiSecurityException {
061: }
062:
063: /**
064: * Looks up and returns the first {@link UserProfile}in the user database
065: * that whose login name, full name, or wiki name matches the supplied
066: * string. This method provides a "forgiving" search algorithm for resolving
067: * principal names when the exact profile attribute that supplied the name
068: * is unknown.
069: * @param index the login name, full name, or wiki name
070: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#find(java.lang.String)
071: */
072: public UserProfile find(String index)
073: throws NoSuchPrincipalException {
074: UserProfile profile = null;
075:
076: // Try finding by full name
077: try {
078: profile = findByFullName(index);
079: } catch (NoSuchPrincipalException e) {
080: }
081: if (profile != null) {
082: return profile;
083: }
084:
085: // Try finding by wiki name
086: try {
087: profile = findByWikiName(index);
088: } catch (NoSuchPrincipalException e) {
089: }
090: if (profile != null) {
091: return profile;
092: }
093:
094: // Try finding by login name
095: try {
096: profile = findByLoginName(index);
097: } catch (NoSuchPrincipalException e) {
098: }
099: if (profile != null) {
100: return profile;
101: }
102:
103: throw new NoSuchPrincipalException("Not in database: " + index);
104: }
105:
106: /**
107: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByEmail(java.lang.String)
108: */
109: public abstract UserProfile findByEmail(String index)
110: throws NoSuchPrincipalException;
111:
112: /**
113: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByFullName(java.lang.String)
114: */
115: public abstract UserProfile findByFullName(String index)
116: throws NoSuchPrincipalException;
117:
118: /**
119: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
120: */
121: public abstract UserProfile findByLoginName(String index)
122: throws NoSuchPrincipalException;
123:
124: /**
125: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(java.lang.String)
126: */
127: public abstract UserProfile findByWikiName(String index)
128: throws NoSuchPrincipalException;
129:
130: /**
131: * <p>Looks up the Principals representing a user from the user database. These
132: * are defined as a set of WikiPrincipals manufactured from the login name,
133: * full name, and wiki name. If the user database does not contain a user
134: * with the supplied identifier, throws a {@link NoSuchPrincipalException}.</p>
135: * <p>When this method creates WikiPrincipals, the Principal containing
136: * the user's full name is marked as containing the common name (see
137: * {@link com.ecyrd.jspwiki.auth.WikiPrincipal#WikiPrincipal(String, String)}).
138: * @param identifier the name of the principal to retrieve; this corresponds to
139: * value returned by the user profile's
140: * {@link UserProfile#getLoginName()}method.
141: * @return the array of Principals representing the user
142: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#getPrincipals(java.lang.String)
143: */
144: public Principal[] getPrincipals(String identifier)
145: throws NoSuchPrincipalException {
146: try {
147: UserProfile profile = findByLoginName(identifier);
148: ArrayList principals = new ArrayList();
149: if (profile.getLoginName() != null
150: && profile.getLoginName().length() > 0) {
151: principals.add(new WikiPrincipal(
152: profile.getLoginName(),
153: WikiPrincipal.LOGIN_NAME));
154: }
155: if (profile.getFullname() != null
156: && profile.getFullname().length() > 0) {
157: principals.add(new WikiPrincipal(profile.getFullname(),
158: WikiPrincipal.FULL_NAME));
159: }
160: if (profile.getWikiName() != null
161: && profile.getWikiName().length() > 0) {
162: principals.add(new WikiPrincipal(profile.getWikiName(),
163: WikiPrincipal.WIKI_NAME));
164: }
165: return (Principal[]) principals
166: .toArray(new Principal[principals.size()]);
167: } catch (NoSuchPrincipalException e) {
168: throw e;
169: }
170: }
171:
172: /**
173: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#initialize(com.ecyrd.jspwiki.WikiEngine, java.util.Properties)
174: */
175: public abstract void initialize(WikiEngine engine, Properties props)
176: throws NoRequiredPropertyException;
177:
178: /**
179: * Factory method that instantiates a new DefaultUserProfile.
180: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#newProfile()
181: */
182: public UserProfile newProfile() {
183: return new DefaultUserProfile();
184: }
185:
186: /**
187: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#save(com.ecyrd.jspwiki.auth.user.UserProfile)
188: */
189: public abstract void save(UserProfile profile)
190: throws WikiSecurityException;
191:
192: /**
193: * Validates the password for a given user. If the user does not exist in
194: * the user database, this method always returns <code>false</code>. If
195: * the user exists, the supplied password is compared to the stored
196: * password. Note that if the stored password's value starts with
197: * <code>{SHA}</code>, the supplied password is hashed prior to the
198: * comparison.
199: * @param loginName the user's login name
200: * @param password the user's password (obtained from user input, e.g., a web form)
201: * @return <code>true</code> if the supplied user password matches the
202: * stored password
203: * @see com.ecyrd.jspwiki.auth.user.UserDatabase#validatePassword(java.lang.String,
204: * java.lang.String)
205: */
206: public boolean validatePassword(String loginName, String password) {
207: String hashedPassword = getHash(password);
208: try {
209: UserProfile profile = findByLoginName(loginName);
210: String storedPassword = profile.getPassword();
211: if (storedPassword.startsWith(SHA_PREFIX)) {
212: storedPassword = storedPassword.substring(SHA_PREFIX
213: .length());
214: }
215: return hashedPassword.equals(storedPassword);
216: } catch (NoSuchPrincipalException e) {
217: return false;
218: }
219: }
220:
221: /**
222: * Private method that calculates the SHA-1 hash of a given
223: * <code>String</code>
224: * @param text the text to hash
225: * @return the result hash
226: */
227: protected String getHash(String text) {
228: String hash = null;
229: try {
230: MessageDigest md = MessageDigest.getInstance("SHA");
231: md.update(text.getBytes("UTF-8"));
232: byte[] digestedBytes = md.digest();
233: hash = HexUtils.convert(digestedBytes);
234: } catch (NoSuchAlgorithmException e) {
235: log.error("Error creating SHA password hash:"
236: + e.getMessage());
237: hash = text;
238: } catch (UnsupportedEncodingException e) {
239: log.fatal("UTF-8 not supported!?!");
240: }
241: return hash;
242: }
243:
244: }
|