/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed 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.
*
* Initial developer(s): Ludovic Launer
* Contributor(s):
*/
package com.continuent.tungsten.common.security;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import com.continuent.tungsten.common.config.TungstenProperties;
import com.continuent.tungsten.common.config.cluster.ConfigurationException;
import com.continuent.tungsten.common.jmx.ServerRuntimeException;
import com.continuent.tungsten.common.security.SecurityHelper.TUNGSTEN_APPLICATION_NAME;
/**
* Class managing passwords in a file. Retrieves, Creates, deletes, updates
*
* @author <a href="mailto:ludovic.launer@continuent.com">Ludovic Launer</a>
* @version 1.0
*/
public class PasswordManager
{
private static Logger logger = Logger.getLogger(PasswordManager.class);
/*
* Type of Client application. This allows for application specific
* management of passwords
*/
public enum ClientApplicationType
{
UNKNOWN, RMI_JMX, // Any application: generic RMI+JMX
REST_API, // REST http API
CONNECTOR; // The Tungsten Connector
public static ClientApplicationType fromString(String x)
throws IllegalArgumentException
{
if (x == null)
return null;
for (ClientApplicationType currentType : ClientApplicationType
.values())
{
if (x.equalsIgnoreCase(currentType.toString()))
{
return currentType;
}
}
throw new IllegalArgumentException(
"Cannot cast to PasswordManager.ClientApplicationType: "
+ x);
}
};
// Authentication and Encryption information
private AuthenticationInfo authenticationInfo = null;
private TungstenProperties passwordsProperties = null;
private ClientApplicationType clientApplicationType = null;
/**
* Creates a new <code>PasswordManager</code> object
*
* @param securityPropertiesFileLocation location of the security.properties
* file. If set to null will look for the default file.
* @throws ConfigurationException
*/
public PasswordManager(String securityPropertiesFileLocation)
throws ConfigurationException
{
this(securityPropertiesFileLocation, ClientApplicationType.UNKNOWN);
}
/**
* Creates a new <code>PasswordManager</code> object Loads Security related
* properties from a file. File location =
* {clusterhome}/conf/security.properties
*
* @param securityPropertiesFileLocation location of the security.properties
* file. If set to null will look for the default file.
* @param clientApplicationType Type of client application. Used to retrieve
* application specific information (password, ...)
* @throws ConfigurationException
*/
public PasswordManager(String securityPropertiesFileLocation,
ClientApplicationType clientApplicationType)
throws ConfigurationException
{
this.setClientApplicationType(clientApplicationType);
// --- Try to get Security information from properties file ---
// If securityPropertiesFileLocation==null will try to locate
// default file
try
{
this.authenticationInfo = SecurityHelper
.loadAuthenticationInformation(
securityPropertiesFileLocation, false,
TUNGSTEN_APPLICATION_NAME.ANY);
}
catch (ConfigurationException ce)
{
logger.debug(MessageFormat.format("Configuration error: {0}",
ce.getMessage()));
throw ce;
}
catch (ServerRuntimeException sre)
{
logger.debug(MessageFormat.format(
"Could not get authentication information : {0}",
sre.getMessage()));
}
}
/**
* Creates a new <code>PasswordManager</code> object
*
* @param authenticationInfo the <code>AuthenticationInfo</code> object from
* which to retrieve properties
* @param clientApplicationType Type of client application. Used to retrieve
* application specific information (password, ...)
*/
public PasswordManager(AuthenticationInfo authenticationInfo,
ClientApplicationType clientApplicationType)
{
this.authenticationInfo = authenticationInfo;
this.setClientApplicationType(clientApplicationType);
}
/**
* Passwords loaded from file as TungstenProperties. Example:
* getPasswordsAsTungstenProperties.get(username);
*
* @return TungstenProperties class containing the passwords
*/
public TungstenProperties loadPasswordsAsTungstenProperties()
throws ServerRuntimeException
{
this.passwordsProperties = SecurityHelper
.loadPasswordsFromAuthenticationInfo(this.authenticationInfo);
return passwordsProperties;
}
/**
* Get clear text password for a username: decrypts password if needed
*
* @param username the username for which to get the password
* @throws ConfigurationException
*/
public String getClearTextPasswordForUser(String username)
throws ConfigurationException
{
return this.getPasswordForUser(username, true);
}
/**
* Get Encrypted (or "as it is") password for a user
*
* @param username the username for which to get the password
* @throws ConfigurationException
*/
public String getEncryptedPasswordForUser(String username)
throws ConfigurationException
{
return this.getPasswordForUser(username, false);
}
/**
* list users defined in the password file
*
* @return
*/
public List<String> listUsers(ClientApplicationType clientApplicationType)
{
List<String> listUsers = new ArrayList<String>();
// --- Load passwords from file if necessary
if (this.passwordsProperties == null)
this.loadPasswordsAsTungstenProperties();
Set<String> setUsernames = this.passwordsProperties.keyNames();
for (String entry : setUsernames)
{
String username = this.getUsernameFromEntry(entry);
ClientApplicationType applicationType = this
.getClientApplicationTypeFromEntry(entry);
if (applicationType == clientApplicationType)
listUsers.add(username);
}
Collections.sort(listUsers);
return listUsers;
}
/**
* Tries to authenticate a user with a given password
*
* @param username
* @param candidatePassword
* @return true if the user was successfully authenticated, false otherwise.
* @throws ConfigurationException
*/
public boolean authenticateUser(String username, String candidatePassword)
throws ConfigurationException
{
boolean authOK = false;
String goodPassword = this.getClearTextPasswordForUser(username);
if (goodPassword == null)
throw new ServerRuntimeException(MessageFormat
.format("Cannot find password for user= {0}", username));
if (goodPassword.equals(candidatePassword))
authOK = true;
return authOK;
}
/**
* Get clear text password for a username: decrypts password if needed The
* list of passwords is loaded from the file if it hasn't been done before
*
* @param username the username for which to get the password
* @param decryptPassword true of the password needs to be decrypted
* @return String containing the password corresponding to the username
* @throws ConfigurationException
*/
private String getPasswordForUser(String username, boolean decryptPassword)
throws ConfigurationException
{
String userPassword = null;
String _username = this.getApplicationSpecificUsername(username); // Take
// application
// specific
// information
// into
// account
// --- Load passwords from file if necessary
if (this.passwordsProperties == null)
this.loadPasswordsAsTungstenProperties();
userPassword = this.passwordsProperties.get(_username);
// --- Decrypt password if asked for ---
if (decryptPassword)
{
this.authenticationInfo.setUsername(_username);
this.authenticationInfo.setPassword(userPassword);
userPassword = this.authenticationInfo.getDecryptedPassword();
}
return userPassword;
}
/**
* Set and store the password for a given user. The password is encryted if
* authenticationInfo requires so
*
* @param username
* @param password
*/
public void setPasswordForUser(String username, String password)
throws ServerRuntimeException
{
String _password = this.createPasswordForUser(password);
String _username = this.getApplicationSpecificUsername(username); // Take
// application
// specific
// information
// into
// account
this.authenticationInfo.setUsername(_username);
this.authenticationInfo.setPassword(_password);
// Load passwords and add / update the new or existing one
SecurityHelper
.saveCredentialsFromAuthenticationInfo(this.authenticationInfo);
}
/**
* Delete a user from the password file
*
* @param username the username to be deleted from the password file
* @throws ServerRuntimeException
*/
public void deleteUser(String username) throws ServerRuntimeException
{
String _username = this.getApplicationSpecificUsername(username);
this.authenticationInfo.setUsername(_username);
// Delete user from password file
SecurityHelper
.deleteUserFromAuthenticationInfo(this.authenticationInfo);
}
/**
* Refactor a username prior to adding it into the list. Takes into account
* application specific configuration and add prefix to link username to an
* application
*
* @param username the username to refactor for the current application
* @return username with the application specific suffix
*/
public String getApplicationSpecificUsername(String username)
{
String _username = username;
// --- Take application specific information into account ---
// Appends application name before the property
if (this.clientApplicationType != null
&& this.clientApplicationType != ClientApplicationType.UNKNOWN)
switch (this.clientApplicationType)
{
case RMI_JMX :
_username = SecurityConf.SECURITY_APPLICATION_RMI_JMX + "."
+ username;
break;
case CONNECTOR :
_username = SecurityConf.SECURITY_APPLICATION_CONNECTOR
+ "." + username;
break;
case REST_API :
_username = SecurityConf.SECURITY_APPLICATION_REST_API + "."
+ username;
break;
}
return _username;
}
/**
* Retrieve the user name from a password file entry
*
* @param passwordFileEntry the entry (line) in the password file
* @return
*/
public String getUsernameFromEntry(String passwordFileEntry)
{
// Get username
int startApplicationDelim = passwordFileEntry.lastIndexOf(".");
String username = (startApplicationDelim == -1)
? passwordFileEntry
: passwordFileEntry.substring(startApplicationDelim + 1,
passwordFileEntry.length());
return username;
}
/**
* Retrieve the application type from a password file entry.
*
* @param passwordFileEntry the entry (line) in the password file
* @return
*/
public ClientApplicationType getClientApplicationTypeFromEntry(
String passwordFileEntry)
{
ClientApplicationType clientApplicationType = ClientApplicationType.UNKNOWN;
// Get application type
int startApplicationDelim = passwordFileEntry.lastIndexOf(".");
if (startApplicationDelim != -1)
{
String _applicationType = passwordFileEntry.substring(0,
startApplicationDelim);
// This will raise an exception if it cannot cast
clientApplicationType = ClientApplicationType
.fromString(_applicationType);
}
return clientApplicationType;
}
/**
* create an encoded or clear text password (depending on settings) for the
* given user.
*
* @param clearTextPassword the clear text password to encrypt or not.
* @return a clear text or encoder password
*/
private String createPasswordForUser(String clearTextPassword)
{
String encryptedPassword = clearTextPassword;
if (this.authenticationInfo.isUseEncryptedPasswords())
{
Encryptor encryptor = null;
;
try
{
encryptor = new Encryptor(this.authenticationInfo);
}
catch (ConfigurationException e)
{
throw new ServerRuntimeException(e.getMessage());
}
if (encryptor != null)
encryptedPassword = encryptor.encrypt(clearTextPassword);
}
return encryptedPassword;
}
/**
* When possible, tries to create files needed by the Password Manager. Best
* effort: not successs guaranted
*
* @throws ConfigurationException
*/
public void try_createAuthenticationInfoFiles()
throws ConfigurationException
{
// --- Try to create password_file.location ---
File f = null;
try
{
f = new File(this.authenticationInfo.getPasswordFileLocation()); // Throws
// a
// NullPointerException
// if
// parameter
// is
// null
if (!f.isFile() || !f.canRead())
{
logger.warn(
MessageFormat.format("Creating non existing file: {0}",
f.getAbsolutePath()));
String parentPath = f.getParent();
if (parentPath != null)
{
File pathToTarget = new File(parentPath); // Create parent
// directories
if (!pathToTarget.isDirectory())
pathToTarget.mkdirs();
}
f.createNewFile(); // Create file
}
}
catch (IOException e)
{
logger.warn(MessageFormat.format("Could not create file: {0}",
f.getAbsolutePath()));
}
catch (NullPointerException npe)
{
// This is thrown when
// this.authenticationInfo.getPasswordFileLocation()==null
throw new ConfigurationException("password_file.location is null");
}
}
public AuthenticationInfo getAuthenticationInfo()
{
return authenticationInfo;
}
public ClientApplicationType getClientApplicationType()
{
return clientApplicationType;
}
public void setClientApplicationType(
ClientApplicationType clientApplicationType)
{
if (clientApplicationType == null)
this.clientApplicationType = ClientApplicationType.UNKNOWN;
else
this.clientApplicationType = clientApplicationType;
}
}