| /* |
| JSPWiki - a JSP-based WikiWiki clone. |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.auth.user; |
| |
| import java.io.*; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.Principal; |
| import java.util.*; |
| |
| import org.apache.wiki.NoRequiredPropertyException; |
| import org.apache.wiki.WikiEngine; |
| import org.apache.wiki.auth.NoSuchPrincipalException; |
| import org.apache.wiki.auth.WikiPrincipal; |
| import org.apache.wiki.auth.WikiSecurityException; |
| import org.apache.wiki.log.Logger; |
| import org.apache.wiki.log.LoggerFactory; |
| import org.apache.wiki.util.CryptoUtil; |
| import org.apache.wiki.util.HexUtils; |
| |
| |
| /** |
| * Abstract UserDatabase class that provides convenience methods for finding |
| * profiles, building Principal collections and hashing passwords. |
| * @since 2.3 |
| */ |
| public abstract class AbstractUserDatabase implements UserDatabase |
| { |
| |
| protected static final Logger log = LoggerFactory.getLogger( AbstractUserDatabase.class ); |
| protected static final String SHA_PREFIX = "{SHA}"; |
| protected static final String SSHA_PREFIX = "{SSHA}"; |
| |
| /** |
| * No-op method that in previous versions of JSPWiki was intended to |
| * atomically commit changes to the user database. Now, the {@link #rename(String, String)}, |
| * {@link #save(UserProfile)} and {@link #deleteByLoginName(String)} methods |
| * are atomic themselves. |
| * @throws WikiSecurityException |
| * @deprecated there is no need to call this method because the save, rename and |
| * delete methods contain their own commit logic |
| */ |
| public synchronized void commit() throws WikiSecurityException |
| { } |
| |
| /** |
| * Looks up and returns the first {@link UserProfile}in the user database |
| * that whose login name, full name, or wiki name matches the supplied |
| * string. This method provides a "forgiving" search algorithm for resolving |
| * principal names when the exact profile attribute that supplied the name |
| * is unknown. |
| * @param index the login name, full name, or wiki name |
| * @see org.apache.wiki.auth.user.UserDatabase#find(java.lang.String) |
| */ |
| public UserProfile find( String index ) throws NoSuchPrincipalException |
| { |
| UserProfile profile = null; |
| |
| // Try finding by full name |
| try |
| { |
| profile = findByFullName( index ); |
| } |
| catch ( NoSuchPrincipalException e ) |
| { |
| } |
| if ( profile != null ) |
| { |
| return profile; |
| } |
| |
| // Try finding by wiki name |
| try |
| { |
| profile = findByWikiName( index ); |
| } |
| catch ( NoSuchPrincipalException e ) |
| { |
| } |
| if ( profile != null ) |
| { |
| return profile; |
| } |
| |
| // Try finding by login name |
| try |
| { |
| profile = findByLoginName( index ); |
| } |
| catch ( NoSuchPrincipalException e ) |
| { |
| } |
| if ( profile != null ) |
| { |
| return profile; |
| } |
| |
| throw new NoSuchPrincipalException( "Not in database: " + index ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(java.lang.String) |
| */ |
| public abstract UserProfile findByEmail( String index ) throws NoSuchPrincipalException; |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(java.lang.String) |
| */ |
| public abstract UserProfile findByFullName( String index ) throws NoSuchPrincipalException; |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(java.lang.String) |
| */ |
| public abstract UserProfile findByLoginName( String index ) throws NoSuchPrincipalException; |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(java.lang.String) |
| */ |
| public abstract UserProfile findByWikiName( String index ) throws NoSuchPrincipalException; |
| |
| /** |
| * <p>Looks up the Principals representing a user from the user database. These |
| * are defined as a set of WikiPrincipals manufactured from the login name, |
| * full name, and wiki name. If the user database does not contain a user |
| * with the supplied identifier, throws a {@link NoSuchPrincipalException}.</p> |
| * <p>When this method creates WikiPrincipals, the Principal containing |
| * the user's full name is marked as containing the common name (see |
| * {@link org.apache.wiki.auth.WikiPrincipal#WikiPrincipal(String, String)}). |
| * @param identifier the name of the principal to retrieve; this corresponds to |
| * value returned by the user profile's |
| * {@link UserProfile#getLoginName()}method. |
| * @return the array of Principals representing the user |
| * @see org.apache.wiki.auth.user.UserDatabase#getPrincipals(java.lang.String) |
| * @throws NoSuchPrincipalException {@inheritDoc} |
| */ |
| public Principal[] getPrincipals( String identifier ) throws NoSuchPrincipalException |
| { |
| try |
| { |
| UserProfile profile = findByLoginName( identifier ); |
| ArrayList<Principal> principals = new ArrayList<Principal>(); |
| if ( profile.getLoginName() != null && profile.getLoginName().length() > 0 ) |
| { |
| principals.add( new WikiPrincipal( profile.getLoginName(), WikiPrincipal.LOGIN_NAME ) ); |
| } |
| if ( profile.getFullname() != null && profile.getFullname().length() > 0 ) |
| { |
| principals.add( new WikiPrincipal( profile.getFullname(), WikiPrincipal.FULL_NAME ) ); |
| } |
| if ( profile.getWikiName() != null && profile.getWikiName().length() > 0 ) |
| { |
| principals.add( new WikiPrincipal( profile.getWikiName(), WikiPrincipal.WIKI_NAME ) ); |
| } |
| return principals.toArray( new Principal[principals.size()] ); |
| } |
| catch( NoSuchPrincipalException e ) |
| { |
| throw e; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine, java.util.Properties) |
| */ |
| public abstract void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException; |
| |
| /** |
| * Factory method that instantiates a new DefaultUserProfile with a new, distinct |
| * unique identifier. |
| * |
| * @return A new, empty profile. |
| */ |
| public UserProfile newProfile() |
| { |
| return DefaultUserProfile.newProfile( this ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.auth.user.UserDatabase#save(org.apache.wiki.auth.user.UserProfile) |
| */ |
| public abstract void save( UserProfile profile ) throws WikiSecurityException; |
| |
| /** |
| * Validates the password for a given user. If the user does not exist in |
| * the user database, this method always returns <code>false</code>. If |
| * the user exists, the supplied password is compared to the stored |
| * password. Note that if the stored password's value starts with |
| * <code>{SHA}</code>, the supplied password is hashed prior to the |
| * comparison. |
| * @param loginName the user's login name |
| * @param password the user's password (obtained from user input, e.g., a web form) |
| * @return <code>true</code> if the supplied user password matches the |
| * stored password |
| * @throws NoSuchAlgorithmException |
| * @see org.apache.wiki.auth.user.UserDatabase#validatePassword(java.lang.String, |
| * java.lang.String) |
| */ |
| public boolean validatePassword( String loginName, String password ) |
| { |
| String hashedPassword; |
| try |
| { |
| UserProfile profile = findByLoginName( loginName ); |
| String storedPassword = profile.getPassword(); |
| |
| // Is the password stored as a salted hash (the new 2.8 format?) |
| boolean newPasswordFormat = storedPassword.startsWith( SSHA_PREFIX ); |
| |
| // If new format, verify the hash |
| if ( newPasswordFormat ) |
| { |
| hashedPassword = getHash( password ); |
| return CryptoUtil.verifySaltedPassword( password.getBytes("UTF-8"), storedPassword ); |
| } |
| |
| // If old format, verify using the old SHA verification algorithm |
| if ( storedPassword.startsWith( SHA_PREFIX ) ) |
| { |
| storedPassword = storedPassword.substring( SHA_PREFIX.length() ); |
| } |
| hashedPassword = getOldHash( password ); |
| boolean verified = hashedPassword.equals( storedPassword ); |
| |
| // If in the old format and password verified, upgrade the hash to SSHA |
| if ( verified ) |
| { |
| profile.setPassword( password ); |
| save( profile ); |
| } |
| |
| return verified; |
| } |
| catch( NoSuchPrincipalException e ) |
| { |
| } |
| catch( NoSuchAlgorithmException e ) |
| { |
| log.error( "Unsupported algorithm: " + e.getMessage() ); |
| } |
| catch( UnsupportedEncodingException e ) |
| { |
| log.error( "You do not have UTF-8!?!" ); |
| } |
| catch( WikiSecurityException e ) |
| { |
| log.error( "Could not upgrade SHA password to SSHA because profile could not be saved. Reason: " + e.getMessage() ); |
| e.printStackTrace(); |
| } |
| return false; |
| } |
| |
| /** |
| * Generates a new random user identifier (uid) that is guaranteed to be unique. |
| * |
| * @param db The database for which the UID should be generated. |
| * @return A random, unique UID. |
| */ |
| protected static String generateUid( UserDatabase db ) |
| { |
| // Keep generating UUIDs until we find one that doesn't collide |
| String uid = null; |
| boolean collision; |
| |
| do |
| { |
| uid = UUID.randomUUID().toString(); |
| collision = true; |
| try |
| { |
| db.findByUid( uid ); |
| } |
| catch ( NoSuchPrincipalException e ) |
| { |
| collision = false; |
| } |
| } |
| while ( collision || uid == null ); |
| return uid; |
| } |
| |
| /** |
| * Private method that calculates the salted SHA-1 hash of a given |
| * <code>String</code>. Note that as of JSPWiki 2.8, this method calculates |
| * a <em>salted</em> hash rather than a plain hash. |
| * @param text the text to hash |
| * @return the result hash |
| */ |
| protected String getHash( String text ) |
| { |
| String hash = null; |
| try |
| { |
| hash = CryptoUtil.getSaltedPassword( text.getBytes("UTF-8") ); |
| } |
| catch( NoSuchAlgorithmException e ) |
| { |
| log.error( "Error creating salted SHA password hash:" + e.getMessage() ); |
| hash = text; |
| } |
| catch( UnsupportedEncodingException e ) |
| { |
| log.error("You do not have UTF-8!?!"); |
| } |
| return hash; |
| } |
| |
| /** |
| * Private method that calculates the SHA-1 hash of a given |
| * <code>String</code> |
| * @param text the text to hash |
| * @return the result hash |
| * @deprecated this method is retained for backwards compatibility purposes; use {@link #getHash(String)} instead |
| */ |
| protected String getOldHash( String text ) |
| { |
| String hash = null; |
| try |
| { |
| MessageDigest md = MessageDigest.getInstance( "SHA" ); |
| md.update( text.getBytes("UTF-8") ); |
| byte[] digestedBytes = md.digest(); |
| hash = HexUtils.convert( digestedBytes ); |
| } |
| catch( NoSuchAlgorithmException e ) |
| { |
| log.error( "Error creating SHA password hash:" + e.getMessage() ); |
| hash = text; |
| } |
| catch (UnsupportedEncodingException e) |
| { |
| log.error("UTF-8 not supported!?!"); |
| } |
| return hash; |
| } |
| |
| /** |
| * Parses a long integer from a supplied string, or returns 0 if not parsable. |
| * @param value the string to parse |
| * @return the value parsed |
| */ |
| protected long parseLong( String value ) |
| { |
| if ( value == null || value.length() == 0 ) |
| { |
| return 0; |
| } |
| try |
| { |
| return Long.parseLong( value ); |
| } |
| catch ( NumberFormatException e ) |
| { |
| return 0; |
| } |
| } |
| |
| } |