/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.util;
import java.security.SecureRandom;
import java.util.Calendar;
import java.util.Map;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.model.property.PropertyInfoRestRep;
import com.emc.storageos.security.password.Constants;
import com.emc.storageos.security.password.PasswordUtils;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import org.apache.commons.codec.digest.Crypt;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.systemservices.exceptions.CoordinatorClientException;
import com.emc.storageos.systemservices.exceptions.LocalRepositoryException;
import com.emc.storageos.systemservices.impl.resource.ConfigService;
import com.emc.storageos.model.property.PropertyInfoUpdate;
import com.emc.storageos.model.property.PropertyInfo.PropCategory;
import com.emc.storageos.db.client.model.EncryptionProvider;
public class LocalPasswordHandler {
private static final Logger _log = LoggerFactory.getLogger(LocalPasswordHandler.class);
private static final String SYSTEM_ENCPASSWORD_FORMAT = "system_%s_encpassword"; // NOSONAR
// ("squid:S2068 Suppressing sonar violation of hard-coded password")
private static final String SYSTEM_AUTHORIZEDKEY2_FORMAT = "system_%s_authorizedkeys2";
private static final String CRYPT_SHA_512 = "$6$";
private Map<String, StorageOSUser> _localUsers;
private ConfigService _cfg;
private EncryptionProvider _encryptionProvider;
public void setEncryptionProvider(EncryptionProvider encryptionProvider) {
_encryptionProvider = encryptionProvider;
}
public void setConfigService(ConfigService cfg) {
_cfg = cfg;
}
public void setLocalUsers(Map<String, StorageOSUser> localUsers) {
_localUsers = localUsers;
}
private DbClient _dbClient;
public void setDbClient(DbClient dbClient) {
_dbClient = dbClient;
}
private PasswordUtils _passwordUtils;
public void setPasswordUtils(PasswordUtils passwordUtils) {
_passwordUtils = passwordUtils;
}
public PasswordUtils getPasswordUtils() {
return _passwordUtils;
}
/**
* Check whether the local user name exists in configuration.
*
* @param username the local user name
* @return true if user exists, else false
*/
public boolean checkUserExists(String username) {
boolean ret = false;
if (_localUsers.get(username) != null) {
_log.debug("user {} exists locally, roles={}", username,
_localUsers.get(username).getRoles());
ret = true;
} else {
_log.debug("user {} does not exist locally", username);
}
return ret;
}
/**
* Get the local user's hashed password
*
* @param username the local user name
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
public String getUserPassword(String username)
throws CoordinatorClientException, LocalRepositoryException {
return getPassword(username);
}
private String getPassword(String username)
throws CoordinatorClientException, LocalRepositoryException {
PropertyInfoRestRep props = null;
try {
props = _cfg.getProperties(PropCategory.CONFIG.toString());
} catch (Exception e) {
throw APIException.internalServerErrors.getObjectFromError("password", "coordinator", e);
}
String propertyKey = String.format(SYSTEM_ENCPASSWORD_FORMAT, username);
String oldPassword = props.getProperty(propertyKey);
if (oldPassword == null) {
_log.error("password not found for " + username);
return "";
}
return oldPassword;
}
/**
* Set the local user's password
*
* @param username the local user name
* @param clearTextPassword the local user's password in clear text
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
public void setUserPassword(String username, String clearTextPassword, boolean bReset)
throws CoordinatorClientException, LocalRepositoryException {
if (clearTextPassword == null || clearTextPassword.isEmpty()) {
throw APIException.badRequests.parameterIsNullOrEmpty("Password");
}
String salt = generateSalt();
String hashed = Crypt.crypt(clearTextPassword, CRYPT_SHA_512 + salt);
_log.debug("New hashedSaltedPassword: {}", hashed);
setUserHashedPassword(username, hashed, bReset);
}
/**
* Calls EncryptionProvider to encrypt a string and returns a Base64 encoded string representing the encrypted data.
*
* @param input the string to encrypt and get Base64 . encoded string
* @return the encrypted (and Base64 encoded) string.
*/
public String getEncryptedString(String input) {
return _encryptionProvider.getEncryptedString(input);
}
/**
* Set the local user's encrypted password
*
* @param username the local user name
* @param password the local user's password already encrypted by caller
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
public void setUserEncryptedPassword(String username, String password, boolean bReset)
throws CoordinatorClientException, LocalRepositoryException {
if (password == null || password.isEmpty()) {
throw APIException.badRequests.parameterIsNullOrEmpty("password");
}
updateUserPasswordProperty(username, password, bReset);
}
/**
* Set the local user's hashed password
*
* @param username the local user name
* @param hashedPassword the local user's password already hashed by caller
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
public void setUserHashedPassword(String username, String hashedPassword, boolean bReset)
throws CoordinatorClientException, LocalRepositoryException {
if (hashedPassword == null || hashedPassword.isEmpty()) {
throw APIException.badRequests.parameterIsNullOrEmpty("Password");
}
updateUserPasswordProperty(username, hashedPassword, bReset);
}
/**
* Set the local user's authorizedkey2
*
* @param username the local user name
* @param authorizedkey2 the local user's SSH authorizedkey2
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
public void setUserAuthorizedkey2(String username, String authorizedkey2)
throws CoordinatorClientException, LocalRepositoryException {
updateProperty(String.format(SYSTEM_AUTHORIZEDKEY2_FORMAT, username),
authorizedkey2);
}
public void updateProperty(String key, String value)
throws CoordinatorClientException, LocalRepositoryException {
PropertyInfoUpdate props = new PropertyInfoUpdate();
props.addProperty(key, value);
_log.info("Calling ConfigService to update property: ", key);
try {
_cfg.setProperties(props);
} catch (Exception e) {
throw APIException.internalServerErrors.updateObjectError("properties", e);
}
}
/**
* when updating localuser's encpassword property, this method should be call instead of
* updateProperty method.
*
* it will update encpassword property, it also update user's expiry_date property and
* user's password history.
*
* expiry_date system properties is for generate /etc/shadow file to block ssh login after
* user's password expired.
*
* @param username
* @param value
* @throws CoordinatorClientException
* @throws LocalRepositoryException
*/
private void updateUserPasswordProperty(String username, String value, boolean bReset)
throws CoordinatorClientException, LocalRepositoryException {
String encpasswordProperty = String.format(SYSTEM_ENCPASSWORD_FORMAT, username);
PropertyInfoUpdate props = new PropertyInfoUpdate();
props.addProperty(encpasswordProperty, value);
Calendar newExpireTime = getExpireTimeFromNow();
if (username.equals("root") || username.equals("svcuser")) {
// add expiry_date system property
String configExpireDays = getPasswordUtils().getConfigProperty(Constants.PASSWORD_EXPIRE_DAYS);
int intConfigExpireDays = NumberUtils.toInt(configExpireDays);
int daysAfterEpoch = 0;
if (intConfigExpireDays != 0) {
daysAfterEpoch = PasswordUtils.getDaysAfterEpoch(newExpireTime);
}
String expirydaysProperty = String.format(Constants.SYSTEM_PASSWORD_EXPIRY_FORMAT, username);
_log.info("updating " + expirydaysProperty + " to " + daysAfterEpoch);
props.addProperty(expirydaysProperty, String.valueOf(daysAfterEpoch));
}
try {
_cfg.setProperties(props);
if (username.equals("proxyuser")) {
value = _passwordUtils.getEncryptedString(value);
}
_passwordUtils.updatePasswordHistory(username, value, newExpireTime, bReset);
} catch (Exception e) {
throw APIException.internalServerErrors.updateObjectError("properties", e);
}
}
private Calendar getExpireTimeFromNow() {
String configExpireDays = _passwordUtils.getConfigProperty(Constants.PASSWORD_EXPIRE_DAYS);
int intConfigExpireDays = NumberUtils.toInt(configExpireDays);
Calendar expireTime = Calendar.getInstance();
expireTime.add(Calendar.DATE, intConfigExpireDays);
return expireTime;
}
/**
* Generate a random salt
*/
static private String generateSalt() {
// number of Base64 characters for salt is dependent on the number of salt bytes
final int SALT_LENGTH = 16;
// valid chars as part of salt acceptable by commons-codec
final String SALT_BASE_CHARS =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
// create the salt of random bytes
SecureRandom random = new SecureRandom();
StringBuilder salt = new StringBuilder(SALT_LENGTH);
for (int i = 0; i < SALT_LENGTH; i++) {
salt.append(SALT_BASE_CHARS.charAt(
random.nextInt(SALT_BASE_CHARS.length())));
}
return salt.toString();
}
}