Package org.fao.geonet.util

Source Code of org.fao.geonet.util.PasswordUtil

package org.fao.geonet.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;

import javax.servlet.ServletContext;

import org.fao.geonet.Constants;
import org.fao.geonet.exceptions.UserNotFoundEx;
import jeeves.server.context.ServiceContext;

import org.fao.geonet.domain.User;
import org.fao.geonet.domain.UserSecurityNotification;
import org.fao.geonet.repository.UserRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
* Geonetwork has had several password storing mechanisms in the past and upgrading is a tricky issue. 
* As of version 2.9.0 the password hashes have been moved to the secure spring security hashes which are
* SHA-256 based with a system-wide salt and a key specific salt.  However a migration path is needed.
* A column "security" has been added to the users table which contains security related flags.  One such flag
* is update_hash_required which indicates the password has an old password and needs to be updated.  When a user
* logs in the new hash will be calculated.
*
* @author jeichar
*/
public class PasswordUtil {

  public static final String ENCODER_ID = "geonetworkEncoder";
  public static final String PASSWORD_COLUMN = "password";

  /**
   * Check the security column for the {@value org.fao.geonet.domain.UserSecurityNotification.UPDATE_HASH_REQUIRED} tag.
   *
   * @param securityField The securityField the
   * @return true if the user needs its hash updated
   */
  public static boolean hasOldHash(User user) {
    return user.getSecurity().getSecurityNotifications().contains(UserSecurityNotification.UPDATE_HASH_REQUIRED);
  }
  /**
   * Compare the hash (read from database) to *all* type of hashes used by geonetwork.  This should not be used
   * for logging in, only for upgrading passwords.
   *
   * @param encoder encoder to use
   * @param hash the hash used for comparison
   * @param password the unhashed password to compare to hash with the different hashing techniques
   *
   * @return true is password matches hash
   */
  public static boolean matchesAnyHash(PasswordEncoder encoder, String hash, String password) {
    return matchesOldHash(hash, password) || encoder.matches(password, hash);
  }
  /**
   * Check if the password matches is one of the old outdated hashes
   *
   * @param hash the hash used for comparison
   * @param password the unhashed password to compare to hash with the different hashing techniques
   *
   * @return true if password matches one of the hashes
   */
  public static boolean matchesOldHash(String hash, String password) {
    return unsaltedScramble(password).equals(hash) || oldScramble(password).equals(hash);
  }

  /**
   * SHA-1 Cryptographic hash algorithm
   * See #191
   *
   * @param text  password to digest
   * @return  the hexadecimal encoded string
   * @deprecated
   */
  private static String unsaltedScramble(String text)
  {
    try {
      MessageDigest md = MessageDigest.getInstance("SHA-1") ;
      md.update(text.getBytes(Constants.ENCODING));
      byte[] raw = md.digest();
      if (raw == null) {
        return null;
      }
      final StringBuilder hex = new StringBuilder(2 * raw.length);
      for (final byte b : raw) {
        hex.append("0123456789abcdef".charAt((b & 0xF0) >> 4)).append(
            "0123456789abcdef".charAt((b & 0x0F)));
      }
      return hex.toString();
    }
    catch (UnsupportedEncodingException e) { return null; }
    catch (NoSuchAlgorithmException e)     { return null; }
  }
  /**
   * Old Jeeves scramble method which lost leading 0
   * during byte to hexadecimal string conversion.
   *
   * @param text
   * @return  the hexadecimal encoded string with missing leading 0
   *  @deprecated
   */
  private static String oldScramble(String text)
  {
    try
    {
      MessageDigest md = MessageDigest.getInstance("SHA-1") ;
 
      md.update(text.getBytes(Constants.ENCODING));
 
      StringBuffer sb = new StringBuffer();
 
      for (byte b : md.digest())
        sb.append(Integer.toString(b & 0xFF, 16))// #191 : here leading 0 are removed
 
      return sb.toString();
    }
    catch (UnsupportedEncodingException e) { return null; }
    catch (NoSuchAlgorithmException e)     { return null; }
  }
  /**
   * Obtain the password encoder from the spring application context.
   * @param servletContext
   *
   * @return the pasword encoder from the spring application context.
   */
  public static PasswordEncoder encoder(ServletContext servletContext) {
    WebApplicationContext appcontext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
    return (PasswordEncoder) appcontext.getBean(ENCODER_ID);
  }
  /**
   * Obtain the password encoder from the spring application context.
   * @param servletContext
   *
   * @return the pasword encoder from the spring application context.
   */
  public static PasswordEncoder encoder(ApplicationContext appContext) {
      return (PasswordEncoder) appContext.getBean(ENCODER_ID);
  }
  /**
   * Updates database with new password if passwords match
   *
   * @param matchOldPassword if false and oldPassword is null then password
   *                will be updated without checking old password
   * @param oldPassword the old password (obtained from user. not a hash)
   * @param newPassword the new password
   * @param iUserId the user id
   * @param servletContext the servlet context, used to obtain the password encoder
   * @return the xml from the database query containing the new password hash
   *
   * @throws SQLException if an error occurred during a database access
   * @throws UserNotFoundEx  if the id does not reference a user
   */
  public static User updatePasswordWithNew(boolean matchOldPassword, String oldPassword,
      String newPassword, Integer iUserId, ApplicationContext appContext) throws SQLException, UserNotFoundEx {
      UserRepository repo = appContext.getBean(UserRepository.class);
        User user = repo.findOne(iUserId);
        if (user == null) {
            throw new UserNotFoundEx(""+iUserId);
        }

        PasswordEncoder encoder = encoder(appContext);
    return updatePasswordWithNew(matchOldPassword, oldPassword, newPassword, user, encoder, repo);
  }
  /**
   * Updates database with new password if passwords match
   *
   * @param matchOldPassword if false and oldPassword is null then password
   *                will be updated without checking old password
   * @param oldPassword the old password (obtained from user. not a hash)
   * @param newPassword the new password
   * @param user the user to update
   * @param encoder the Password encoder
   * @return the xml from the database query containing the new password hash
   *
   * @throws SQLException if an error occurred during a database access
   * @throws UserNotFoundEx  if the id does not reference a user
   */
  public static User updatePasswordWithNew(boolean matchOldPassword, String oldPassword,
      String newPassword, User user, PasswordEncoder encoder, UserRepository repository) throws SQLException, UserNotFoundEx {
    String hash = user.getPassword();
    if (hasOldHash(user)) {
      if ((matchOldPassword || newPassword != null) && !matchesOldHash(hash , newPassword)) {
        throw new IllegalArgumentException("Password is not correct. It does not match old hash.");
      }
    } else {
      if ((matchOldPassword || oldPassword != null) && !encoder.matches(oldPassword, hash)) {
        throw new IllegalArgumentException("Old password is not correct");
      }
    }
   
    user.getSecurity().getSecurityNotifications().remove(UserSecurityNotification.UPDATE_HASH_REQUIRED);
   
    String newPasswordHash = encoder.encode(newPassword);
    user.getSecurity().setPassword(newPasswordHash.toCharArray());
   
    repository.save(user);
    return user;
  }
  public static String encode(ServiceContext context, String password) {
    return encoder(context.getApplicationContext()).encode(password);
  }
}
TOP

Related Classes of org.fao.geonet.util.PasswordUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.