package test
import java.io.Closeable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import com.whatever.Wallet;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoCredential;
/*****************************************************************************
* Class that implements the MongoClientURI class with specific requirements
* for the XXX system, specifically supporting in-memory decryption of the
* user name and password. Note that this class will decrypt passwords and
* store them in memory and {@link #close()} should be called to wipe those
* passwords.
* @author Philip J. Dicke
*****************************************************************************/
public class MyMongoDbClientUri extends MongoClientURI implements Closeable {
private String myPassEncrypted;
private String myUserEncrypted;
private volatile char[] myDecryptedPass; // cached, leave as volatile so that
// shredding the array is not optimized out
/**
* Creates a MyMongDbClientUri from system properties. The following properties are read:
* <ul>
* <li>(db_prefix)_jdbc - property contains the (required) MongoDB URI, for example:
* <tt>mongodb://localhost/magedb</tt></li>
* <li>(db_prefix)_username - property contains encrypted user name (default null)</li>
* <li>(db_prefix)_password - property contains encrypted password (default null)</li>
* </ul>
* <b>IMPORTANT:</b> The caller of this function is now responsible for calling TapsMongoDbClientUri#close()
* which will clear the decrypted passwords from memory.
*/
public static MyMongoDbClientUri getMongoClientUri(String uriStr, String name) {
MyMongoDbClientUri clientUri = new MyMongoDbClientUri(uriStr);
String userEncrypted = Wallet.getEncryptedUser(name);
clientUri.setUserEncrypted(userEncrypted);
String passEncrypted = Wallet.getEncryptedPassword(name);
clientUri.setPasswordEncrypted(passEncrypted);
return clientUri;
}
/**
* Constructor
* @param uri MongoDB client connection URI. For example: mongdb://localhost/blah
* @throws NullPointerException from MongoClientUri(String) if uri is null
*/
public MyMongoDbClientUri(String uri) {
super(uri);
}
/** Returns the decrypted password. The reference is to the internally cached
* password char[] so that users of this class don't have to worry about clearing
* the array, only calling {@link #close()}
*/
@Override
public char[] getPassword() {
// These functions are never called from the MongoClient code, so need to implement getCredentials
if(myDecryptedPass != null) {
return myDecryptedPass; //we specifically want to return the internal array reference
//because that is the one that will be cleared in the close method
}
myDecryptedPass = Wallet.decrypt(myPassEncrypted);
return myDecryptedPass;
}
@Override
public String getUsername() {
if(myUserEncrypted == null) {
return myUserEncrypted;
}
// stuck as a String in memory, so might as well not cache it
return Wallet.decrypt(myUserEncrypted);
}
public void setPasswordEncrypted(String passEncrypted) {
myPassEncrypted = passEncrypted;
}
public void setUserEncrypted(String userEncrypted) {
myUserEncrypted = userEncrypted;
}
@Override
public MongoCredential getCredentials() {
MongoCredential existing = super.getCredentials();
// MongoCredential is a final class, so, I can't do what I want here is derive from (or proxy) MongoCredential
// and simply replace the username/password with what is in this class, that way all the
// mechanism properties are properly copied. Also there is no mechanism to get a list of the
// specific authentication mechanism parameters to be able to copy them.
// The getUserName() and getPassword() functions are
// never called from this class, so I have to implement this to replace them.
// IMPORTANT: LOTS OF Copied code (what I could and what I needed from) from ConnectionString#createCredentials()
// MISSING: authentication mechanism specific options - needs more copied code
Map<String, String> params = null;
try {
params = parseQueryString(getURI(), "UTF-8");
} catch (UnsupportedEncodingException e) {
// should never happen
return null;
}
MongoCredential newCred = null;
String authSource = (getDatabase() == null) ? "admin" : getDatabase();
String authSourceParam = params.get("authSource");
if(authSourceParam != null) {
authSource = authSourceParam;
}
// TODO parsing other parameters - needs more copied code
if(existing.getAuthenticationMechanism() == null) {
return MongoCredential.createCredential(getUsername(), authSource, getPassword());
}
switch(existing.getAuthenticationMechanism()) {
case GSSAPI:
newCred = MongoCredential.createGSSAPICredential(getUsername());
break;
case MONGODB_CR:
newCred = MongoCredential.createMongoCRCredential(getUsername(), authSource, getPassword());
break;
case MONGODB_X509:
newCred = MongoCredential.createMongoX509Credential(getUsername());
break;
case PLAIN:
newCred = MongoCredential.createPlainCredential(getUsername(), authSource, getPassword());
break;
case SCRAM_SHA_1:
newCred = MongoCredential.createScramSha1Credential(getUsername(), authSource, getPassword());
break;
default:
// this should never happen, but new versions might bring new methods
throw new UnsupportedOperationException(
String.format("The connection string contains an invalid authentication mechanism'. "
+ "'%s' is not a supported authentication mechanism", existing.getMechanism()));
}
// currently there isn't any way to copy the
return newCred;
}
/** Call in a finally block, wipes the plain text passwords from memory */
@Override
public void close() {
if(myDecryptedPass != null) {
Arrays.fill(myDecryptedPass, (char) 0x0);
}
}
private static Map<String, String> parseQueryString (final String uriString, String charSet)
throws UnsupportedEncodingException {
URI uri;
try {
uri = new URI(uriString);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid URI " + uriString ,e);
}
final Map <String, String> qps = new HashMap<String, String> ();
final StringTokenizer pairs = new StringTokenizer (uri.getQuery (), "&");
while (pairs.hasMoreTokens ()) {
final String pair = pairs.nextToken ();
final StringTokenizer parts = new StringTokenizer (pair, "=");
final String name = URLDecoder.decode (parts.nextToken (), charSet);
final String value = URLDecoder.decode (parts.nextToken (), charSet);
qps.put (name, value);
}
return qps;
}
}
MongoClient(final ServerAddress addr, final List<MongoCredential> credentialsList, final MongoClientOptions options)
to create the mongo client with the provided mongo server addresses and still use the MongoCredentials to create the credentials with the provided username and decrypted password.