package io.vertx.ext.auth.mongo.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.HashingStrategy;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.CredentialValidationException;
import io.vertx.ext.auth.authentication.Credentials;
import io.vertx.ext.auth.authentication.UsernamePasswordCredentials;
import io.vertx.ext.auth.impl.UserImpl;
import io.vertx.ext.auth.mongo.*;
import io.vertx.ext.mongo.MongoClient;
import java.util.List;
import java.util.Map;
public class MongoAuthenticationImpl implements MongoAuthentication {
private static final Logger log = LoggerFactory.getLogger(MongoAuthenticationImpl.class);
private final HashingStrategy strategy = HashingStrategy.load();
private MongoClient mongoClient;
private MongoAuthenticationOptions options;
private HashStrategy legacyStrategy;
private String hashField;
public MongoAuthenticationImpl(MongoClient mongoClient, MongoAuthenticationOptions options) {
this.mongoClient = mongoClient;
this.options = options;
}
public MongoAuthenticationImpl(MongoClient mongoClient, HashStrategy legacyStrategy, String hashField, MongoAuthenticationOptions options) {
this.mongoClient = mongoClient;
this.options = options;
this.legacyStrategy = legacyStrategy;
this.hashField = hashField;
}
@Override
public void authenticate(JsonObject credentials, Handler<AsyncResult<User>> resultHandler) {
authenticate(new UsernamePasswordCredentials(credentials), resultHandler);
}
@Override
public void authenticate(Credentials credentials, Handler<AsyncResult<User>> resultHandler) {
try {
if (credentials == null) {
resultHandler.handle((Future.failedFuture("Credentials must be set for authentication.")));
return;
}
UsernamePasswordCredentials authInfo = (UsernamePasswordCredentials) credentials;
authInfo.checkValid(null);
AuthToken token = new AuthToken(authInfo.getUsername(), authInfo.getPassword());
JsonObject query = createQuery(authInfo.getUsername());
mongoClient.find(options.getCollectionName(), query, res -> {
try {
if (res.succeeded()) {
User user = handleSelection(res, token);
resultHandler.handle(Future.succeededFuture(user));
} else {
resultHandler.handle(Future.failedFuture(res.cause()));
}
} catch (Exception e) {
log.warn(e);
resultHandler.handle(Future.failedFuture(e));
}
});
} catch (ClassCastException | CredentialValidationException e) {
resultHandler.handle(Future.failedFuture(e));
}
}
protected JsonObject createQuery(String username) {
return new JsonObject().put(options.getUsernameField(), username);
}
private User handleSelection(AsyncResult<List<JsonObject>> resultList, AuthToken authToken)
throws Exception {
switch (resultList.result().size()) {
case 0: {
String message = "No account found for user [" + authToken.username + "]";
throw new Exception(message);
}
case 1: {
JsonObject json = resultList.result().get(0);
User user = createUser(json);
if (examinePassword(user, json.getString(options.getPasswordCredentialField()), authToken.password))
return user;
else {
String message = "Invalid username/password [" + authToken.username + "]";
throw new Exception(message);
}
}
default: {
String message = "More than one user row found for user [" + authToken.username + "( "
+ resultList.result().size() + " )]. Usernames must be unique.";
throw new Exception(message);
}
}
}
private User createUser(JsonObject json) {
User user = new UserImpl(json);
if (legacyStrategy != null) {
json.put(MongoAuthImpl.PROPERTY_FIELD_SALT, hashField);
json.put(MongoAuthImpl.PROPERTY_FIELD_PASSWORD, options.getPasswordField());
}
return user;
}
private boolean examinePassword(User user, String hash, String password) {
if (hash.charAt(0) != '$') {
if (legacyStrategy == null) {
throw new IllegalStateException("Mongo Authentication cannot handle legacy hashes without a HashStrategy");
}
String givenPassword = this.legacyStrategy.computeHash(password, user);
return hash.equals(givenPassword);
} else {
return strategy.verify(hash, password);
}
}
@Override
public String hash(String id, Map<String, String> params, String salt, String password) {
return strategy.hash(id, params, salt, password);
}
static class AuthToken {
String username;
String password;
AuthToken(String username, String password) {
this.username = username;
this.password = password;
}
}
}