Improvements to authenticated JSON-RPC permissions checking (#1144)

* exit early if matching permission found; add test for *:* permission

Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
pull/1155/head
Sally MacFarlane 4 years ago committed by GitHub
parent a9bdd29128
commit 06e35a58c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java
  2. 8
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/JsonRpcMethod.java
  3. 64
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java
  4. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/NetListeningTest.java
  5. 4
      ethereum/api/src/test/resources/JsonRpcHttpService/auth.toml

@ -37,27 +37,31 @@ public class AuthenticationUtils {
AtomicBoolean foundMatchingPermission = new AtomicBoolean(); AtomicBoolean foundMatchingPermission = new AtomicBoolean();
if (authenticationService.isPresent()) { if (authenticationService.isEmpty()) {
if (optionalUser.isPresent()) { // no auth provider configured thus anything is permitted
User user = optionalUser.get(); return true;
for (String perm : jsonRpcMethod.getPermissions()) { }
user.isAuthorized(
perm, if (optionalUser.isPresent()) {
(authed) -> { User user = optionalUser.get();
if (authed.result()) { for (String perm : jsonRpcMethod.getPermissions()) {
LOG.trace( user.isAuthorized(
"user {} authorized : {} via permission {}", perm,
user, (authed) -> {
jsonRpcMethod.getName(), if (authed.result()) {
perm); LOG.trace(
foundMatchingPermission.set(true); "user {} authorized : {} via permission {}",
} user,
}); jsonRpcMethod.getName(),
perm);
foundMatchingPermission.set(true);
}
});
// exit if a matching permission was found, no need to keep checking
if (foundMatchingPermission.get()) {
return foundMatchingPermission.get();
} }
} }
} else {
// no auth provider configured thus anything is permitted
foundMatchingPermission.set(true);
} }
if (!foundMatchingPermission.get()) { if (!foundMatchingPermission.get()) {

@ -23,7 +23,7 @@ import java.util.List;
public interface JsonRpcMethod { public interface JsonRpcMethod {
/** /**
* Standardised JSON-RPC method name. * Standardized JSON-RPC method name.
* *
* @return identification of the JSON-RPC method. * @return identification of the JSON-RPC method.
*/ */
@ -38,15 +38,17 @@ public interface JsonRpcMethod {
JsonRpcResponse response(JsonRpcRequestContext request); JsonRpcResponse response(JsonRpcRequestContext request);
/** /**
* The list of Permissions that correspond to this JSON-RPC method. e.g. [net/*, net/listening] * The list of Permissions that correspond to this JSON-RPC method.
*
* <p>e.g. [*:*, net:*, net:listening]
* *
* @return list of permissions that match this method. * @return list of permissions that match this method.
*/ */
default List<String> getPermissions() { default List<String> getPermissions() {
List<String> permissions = new ArrayList<>(); List<String> permissions = new ArrayList<>();
permissions.add("*:*"); permissions.add("*:*");
permissions.add(this.getName().replace('_', ':'));
permissions.add(this.getName().substring(0, this.getName().indexOf('_')) + ":*"); permissions.add(this.getName().substring(0, this.getName().indexOf('_')) + ":*");
permissions.add(this.getName().replace('_', ':'));
return permissions; return permissions;
}; };
} }

@ -393,7 +393,7 @@ public class JsonRpcHttpServiceLoginTest {
AuthenticationUtils.isPermitted( AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethBlockNumber)) service.authenticationService, Optional.of(user), ethBlockNumber))
.isTrue(); .isTrue();
// eth/accounts not permitted // eth/accounts NOT permitted
assertThat( assertThat(
AuthenticationUtils.isPermitted( AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethAccounts)) service.authenticationService, Optional.of(user), ethAccounts))
@ -407,7 +407,7 @@ public class JsonRpcHttpServiceLoginTest {
AuthenticationUtils.isPermitted( AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), web3Sha3)) service.authenticationService, Optional.of(user), web3Sha3))
.isTrue(); .isTrue();
// no net permissions // NO net permissions
assertThat( assertThat(
AuthenticationUtils.isPermitted( AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), netVersion)) service.authenticationService, Optional.of(user), netVersion))
@ -416,6 +416,66 @@ public class JsonRpcHttpServiceLoginTest {
} }
} }
@Test
public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndAllPermissions()
throws IOException {
final RequestBody body =
RequestBody.create(JSON, "{\"username\":\"adminuser\",\"password\":\"pegasys\"}");
final Request request = new Request.Builder().post(body).url(baseUrl + "/login").build();
try (final Response resp = client.newCall(request).execute()) {
assertThat(resp.code()).isEqualTo(200);
assertThat(resp.message()).isEqualTo("OK");
assertThat(resp.body().contentType()).isNotNull();
assertThat(resp.body().contentType().type()).isEqualTo("application");
assertThat(resp.body().contentType().subtype()).isEqualTo("json");
final String bodyString = resp.body().string();
assertThat(bodyString).isNotNull();
assertThat(bodyString).isNotBlank();
final JsonObject respBody = new JsonObject(bodyString);
final String token = respBody.getString("token");
assertThat(token).isNotNull();
final JsonRpcMethod ethAccounts = new EthAccounts();
final JsonRpcMethod netVersion = new NetVersion(Optional.of(BigInteger.valueOf(123)));
final JsonRpcMethod ethBlockNumber = new EthBlockNumber(blockchainQueries);
final JsonRpcMethod web3Sha3 = new Web3Sha3();
final JsonRpcMethod web3ClientVersion = new Web3ClientVersion("777");
// adminuser has *:* permissions so everything should be allowed
jwtAuth.authenticate(
new JsonObject().put("jwt", token),
(r) -> {
assertThat(r.succeeded()).isTrue();
final User user = r.result();
// single eth/blockNumber method permitted
Assertions.assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethBlockNumber))
.isTrue();
// eth/accounts IS permitted
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethAccounts))
.isTrue();
// allowed by *:*
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), web3ClientVersion))
.isTrue();
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), web3Sha3))
.isTrue();
// YES net permissions
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), netVersion))
.isTrue();
});
}
}
@Test @Test
public void checkPermissionsWithEmptyUser() { public void checkPermissionsWithEmptyUser() {
final JsonRpcMethod ethAccounts = new EthAccounts(); final JsonRpcMethod ethAccounts = new EthAccounts();

@ -69,7 +69,7 @@ public class NetListeningTest {
@Test @Test
public void getPermissions() { public void getPermissions() {
List<String> permissions = method.getPermissions(); List<String> permissions = method.getPermissions();
assertThat(permissions).containsExactlyInAnyOrder("net:*", "net:listening", "*:*"); assertThat(permissions).containsExactly("*:*", "net:*", "net:listening");
} }
private JsonRpcRequestContext netListeningRequest() { private JsonRpcRequestContext netListeningRequest() {

@ -2,3 +2,7 @@
password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC" password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC"
permissions = ["fakePermission","eth:blockNumber","eth:subscribe","web3:*"] permissions = ["fakePermission","eth:blockNumber","eth:subscribe","web3:*"]
privacyPublicKey = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=" privacyPublicKey = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="
[Users.adminuser]
password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC"
permissions = ["*:*"]
privacyPublicKey = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="

Loading…
Cancel
Save