Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to authenticated JSON-RPC permissions checking #1144

Merged
merged 6 commits into from
Jun 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,31 @@ public static boolean isPermitted(

AtomicBoolean foundMatchingPermission = new AtomicBoolean();

if (authenticationService.isPresent()) {
if (optionalUser.isPresent()) {
User user = optionalUser.get();
for (String perm : jsonRpcMethod.getPermissions()) {
user.isAuthorized(
perm,
(authed) -> {
if (authed.result()) {
LOG.trace(
"user {} authorized : {} via permission {}",
user,
jsonRpcMethod.getName(),
perm);
foundMatchingPermission.set(true);
}
});
if (authenticationService.isEmpty()) {
// no auth provider configured thus anything is permitted
return true;
}

if (optionalUser.isPresent()) {
User user = optionalUser.get();
for (String perm : jsonRpcMethod.getPermissions()) {
user.isAuthorized(
perm,
(authed) -> {
if (authed.result()) {
LOG.trace(
"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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public interface JsonRpcMethod {

/**
* Standardised JSON-RPC method name.
* Standardized JSON-RPC method name.
*
* @return identification of the JSON-RPC method.
*/
Expand All @@ -38,15 +38,17 @@ public interface JsonRpcMethod {
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.
*/
default List<String> getPermissions() {
List<String> permissions = new ArrayList<>();
permissions.add("*:*");
permissions.add(this.getName().replace('_', ':'));
permissions.add(this.getName().substring(0, this.getName().indexOf('_')) + ":*");
permissions.add(this.getName().replace('_', ':'));
return permissions;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethBlockNumber))
.isTrue();
// eth/accounts not permitted
// eth/accounts NOT permitted
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), ethAccounts))
Expand All @@ -407,7 +407,7 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), web3Sha3))
.isTrue();
// no net permissions
// NO net permissions
assertThat(
AuthenticationUtils.isPermitted(
service.authenticationService, Optional.of(user), netVersion))
Expand All @@ -416,6 +416,66 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro
}
}

@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
public void checkPermissionsWithEmptyUser() {
final JsonRpcMethod ethAccounts = new EthAccounts();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void shouldReturnFalseWhenNetworkIsNotListening() {
@Test
public void getPermissions() {
List<String> permissions = method.getPermissions();
assertThat(permissions).containsExactlyInAnyOrder("net:*", "net:listening", "*:*");
assertThat(permissions).containsExactly("*:*", "net:*", "net:listening");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it just made sense to have the items in order of granularity from widest to narrowest

Copy link
Contributor Author

@macfarla macfarla Jun 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also if the permissions are checked in that order then the log.debug messages will make sense

}

private JsonRpcRequestContext netListeningRequest() {
Expand Down
4 changes: 4 additions & 0 deletions ethereum/api/src/test/resources/JsonRpcHttpService/auth.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC"
permissions = ["fakePermission","eth:blockNumber","eth:subscribe","web3:*"]
privacyPublicKey = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="
[Users.adminuser]
password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC"
permissions = ["*:*"]
privacyPublicKey = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="