TomlAuth.java
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.authentication;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
import org.apache.tuweni.toml.Toml;
import org.apache.tuweni.toml.TomlParseResult;
import org.apache.tuweni.toml.TomlTable;
import org.springframework.security.crypto.bcrypt.BCrypt;
public class TomlAuth implements AuthenticationProvider {
public static final String PRIVACY_PUBLIC_KEY = "privacyPublicKey";
private final Vertx vertx;
private final TomlAuthOptions options;
public TomlAuth(final Vertx vertx, final TomlAuthOptions options) {
this.vertx = vertx;
this.options = options;
}
@Override
public void authenticate(
final JsonObject authInfo, final Handler<AsyncResult<User>> resultHandler) {
final String username = authInfo.getString("username");
if (username == null) {
resultHandler.handle(Future.failedFuture("No username provided"));
return;
}
final String password = authInfo.getString("password");
if (password == null) {
resultHandler.handle(Future.failedFuture("No password provided"));
return;
}
vertx.executeBlocking(
f -> {
TomlParseResult parseResult;
try {
parseResult = Toml.parse(options.getTomlPath());
} catch (IOException e) {
f.fail(e);
return;
}
final TomlTable userData = parseResult.getTableOrEmpty("Users." + username);
if (userData.isEmpty()) {
f.fail("User not found");
return;
}
final TomlUser tomlUser = readTomlUserFromTable(username, userData);
if ("".equals(tomlUser.getPassword())) {
f.fail("No password set for user");
return;
}
checkPasswordHash(
password,
tomlUser.getPassword(),
rs -> {
if (rs.succeeded()) {
f.complete(tomlUser);
} else {
f.fail(rs.cause());
}
});
},
false,
res -> {
if (res.succeeded()) {
resultHandler.handle(Future.succeededFuture((User) res.result()));
} else {
resultHandler.handle(Future.failedFuture(res.cause()));
}
});
}
private TomlUser readTomlUserFromTable(final String username, final TomlTable userData) {
final String saltedAndHashedPassword = userData.getString("password", () -> "");
final List<String> groups =
userData.getArrayOrEmpty("groups").toList().stream()
.map(Object::toString)
.collect(Collectors.toList());
final List<String> permissions =
userData.getArrayOrEmpty("permissions").toList().stream()
.map(Object::toString)
.collect(Collectors.toList());
final List<String> roles =
userData.getArrayOrEmpty("roles").toList().stream()
.map(Object::toString)
.collect(Collectors.toList());
final Optional<String> privacyPublicKey =
Optional.ofNullable(userData.getString(PRIVACY_PUBLIC_KEY));
return new TomlUser(
username, saltedAndHashedPassword, groups, permissions, roles, privacyPublicKey);
}
private void checkPasswordHash(
final String password,
final String passwordHash,
final Handler<AsyncResult<Void>> resultHandler) {
boolean passwordMatches = BCrypt.checkpw(password, passwordHash);
if (passwordMatches) {
resultHandler.handle(Future.succeededFuture());
} else {
resultHandler.handle(Future.failedFuture("Invalid password"));
}
}
}