Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[NC-2046] JWT authentication for JSON-RPC #815

Merged
merged 32 commits into from
Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d32d4a5
added JsonRpcHttpService.isPermitted(User, JsonRpcMethod) and JsonRpc…
macfarla Feb 7, 2019
f8b861a
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 7, 2019
253aea4
logging
macfarla Feb 7, 2019
b285a4d
added getUser handler
macfarla Feb 8, 2019
6178576
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 8, 2019
98c8bbb
401 if no auth token
macfarla Feb 8, 2019
76d3c2b
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 8, 2019
a58f9bb
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 8, 2019
b6d66bd
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 9, 2019
48bb6ba
changes to tests
macfarla Feb 10, 2019
f9be563
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 10, 2019
02e17d6
acceptance test for node with authentication turned on needs to not a…
macfarla Feb 11, 2019
94285b3
merged master
macfarla Feb 11, 2019
bcf2eba
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 11, 2019
daf80c3
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 11, 2019
d39091a
fixed dodgy if statement
macfarla Feb 12, 2019
ccd333d
use HTTP unauthorized status code
macfarla Feb 12, 2019
6982558
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 12, 2019
a933608
spots
macfarla Feb 12, 2019
6249104
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 12, 2019
8167fe8
added acceptance test for permissioned JSON RPC
macfarla Feb 12, 2019
e5fc663
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 12, 2019
94cde24
added DSL condition for NetVersion to fail because Unauthorized
macfarla Feb 12, 2019
cb5bbef
Added condition to do login and set auth token
macfarla Feb 12, 2019
27788d2
typos
macfarla Feb 12, 2019
4ea975b
PR comments
macfarla Feb 12, 2019
df6bf62
don't set factories to null
macfarla Feb 12, 2019
43eca89
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 12, 2019
cea1ea4
added */* permission
macfarla Feb 12, 2019
8b2ed76
added AwaitLoginResponse
macfarla Feb 12, 2019
9296f26
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 12, 2019
c5918b9
Merge branch 'master' of github.com:PegaSysEng/pantheon into nc-2046-…
macfarla Feb 13, 2019
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
@@ -0,0 +1,44 @@
/*
* Copyright 2018 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.
*/
package tech.pegasys.pantheon.tests.acceptance.dsl.condition.net;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;

import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetVersionTransaction;

import org.web3j.protocol.exceptions.ClientConnectionException;

public class ExpectNetVersionPermissionException implements Condition {

private final NetVersionTransaction transaction;
private final String expectedMessage;

public ExpectNetVersionPermissionException(
final NetVersionTransaction transaction, final String expectedMessage) {
this.transaction = transaction;
this.expectedMessage = expectedMessage;
}

@Override
public void verify(final Node node) {
final Throwable thrown = catchThrowable(() -> node.execute(transaction));
assertThat(thrown).isInstanceOf(RuntimeException.class);

final Throwable cause = thrown.getCause();
assertThat(cause).isInstanceOf(ClientConnectionException.class);
assertThat(cause.getMessage()).contains(expectedMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginUnauthorizedTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;

public class Login {

Expand All @@ -32,4 +33,14 @@ public Condition loginFails(final String username, final String password) {
n.executeHttpTransaction(new LoginUnauthorizedTransaction(username, password));
};
}

public Condition loginSucceedsAndSetsAuthenticationToken(
final String username, final String password) {

return (n) -> {
final String token = n.executeHttpTransaction(new LoginTransaction(username, password));
assertThat(token).isNotBlank();
((PantheonNode) n).useAuthenticationTokenInHeaderForJsonRpc(token);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionConnectionException;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionConnectionExceptionWithCause;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionIsNotBlank;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionPermissionException;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetTransactions;

import java.math.BigInteger;
Expand Down Expand Up @@ -46,6 +47,10 @@ public Condition netVersionExceptional(final Class<? extends Throwable> cause) {
return new ExpectNetVersionConnectionExceptionWithCause(transactions.netVersion(), cause);
}

public Condition netVersionUnauthorizedExceptional(final String expectedMessage) {
return new ExpectNetVersionPermissionException(transactions.netVersion(), expectedMessage);
}

public Condition awaitPeerCountExceptional() {
return new AwaitNetPeerCountException(transactions.peerCount());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public interface NodeConfiguration {

void useWebSocketsForJsonRpc();

void useAuthenticationTokenInHeaderForJsonRpc(String token);

Optional<Integer> jsonRpcWebSocketPort();

String hostName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
private HttpRequestFactory httpRequestFactory;
private Optional<EthNetworkConfig> ethNetworkConfig = Optional.empty();
private boolean useWsForJsonRpc = false;
private boolean useAuthenticationTokenInHeaderForJsonRpc = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can probably simplify this boolean out by either making token an Optional or checking token != null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

private String token = null;

public PantheonNode(
final String name,
Expand Down Expand Up @@ -201,6 +203,10 @@ private JsonRequestFactories jsonRequestFactories() {
.map(url -> new HttpService(url))
.orElse(new HttpService("http://" + LOCALHOST + ":" + port));

if (useAuthenticationTokenInHeaderForJsonRpc) {
((HttpService) web3jService).addHeader("Bearer", token);
}

jsonRequestFactories =
new JsonRequestFactories(
new JsonRpc2_0Web3j(web3jService, 2000, Async.defaultExecutorService()),
Expand Down Expand Up @@ -255,6 +261,22 @@ public void useWebSocketsForJsonRpc() {
useWsForJsonRpc = true;
}

/** All future JSON-RPC calls will include the authentication token. */
@Override
public void useAuthenticationTokenInHeaderForJsonRpc(final String token) {

if (jsonRequestFactories != null) {
jsonRequestFactories.shutdown();
Copy link
Contributor

Choose a reason for hiding this comment

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

need to add jsonRequestFactoies = null; here, it's also wrong in useWebSocketsForJsonRpc but we haven't managed to race condition it yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This caused one AT to fail so putting it back in
java.lang.RuntimeException: org.web3j.protocol.exceptions.ClientConnectionException: Invalid response received: 400; Websocket endpoint can't handle HTTP requests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RpcApisTogglesAcceptanceTest.shouldSucceedConnectingToNodeWithWsRpcEnabled

}

if (httpRequestFactory != null) {
httpRequestFactory = null;
}

useAuthenticationTokenInHeaderForJsonRpc = true;
this.token = token;
}

private void checkIfWebSocketEndpointIsAvailable(final String url) {
final WebSocketClient webSocketClient = new WebSocketClient(URI.create(url));
// Web3j implementation always invoke the listener (even when one hasn't been set). We are using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;

import java.io.IOException;

import org.web3j.protocol.core.methods.response.NetVersion;

public class NetVersionTransaction implements Transaction<String> {
Expand All @@ -32,7 +30,7 @@ public String execute(final JsonRequestFactories node) {
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getNetVersion();
} catch (final IOException e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.Cluster;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.ClusterConfiguration;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder;

import java.io.IOException;
import java.net.URISyntaxException;
Expand All @@ -22,12 +25,17 @@
import org.junit.Test;

public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase {
private Cluster authenticatedCluster;
private Node node;

@Before
public void setUp() throws IOException, URISyntaxException {
final ClusterConfiguration clusterConfiguration =
new ClusterConfigurationBuilder().setAwaitPeerDiscovery(false).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

With the await peer discovery being off is there still something that is awaiting the node being ready? Want to avoid the login call further down occurring before the service has finished starting in the node.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is nothing awaiting the node being ready. If you can't do any JSON RPC calls, is there another way to ask the node if it's ready?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

^ if this is a problem, it's a problem for a few tests

Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to test that the services are up (JSON-RPC service), can't we send a request to the 'login' route and expect any response?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's exactly what the test does. I think Chris is asking is there a chance that the login call happens before the node has started?

Copy link
Contributor

Choose a reason for hiding this comment

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

What I meant is that we could have a check (prior to the test) if the JSON-RPC service is responding. Currently, we use the admin_peers I guess, we could have an alternative (for when we disable the discovery check) that would send a request to the login route and expect it to be handled (it doesn't need to be a successful login, just need the request to be "received" by the node).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gotcha. awaitLogin()


authenticatedCluster = new Cluster(clusterConfiguration, net);
node = pantheon.createArchiveNodeWithAuthentication("node1");
cluster.start(node);
authenticatedCluster.start(node);
}

@Test
Expand All @@ -39,4 +47,17 @@ public void shouldFailLoginWithWrongCredentials() {
public void shouldSucceedLoginWithCorrectCredentials() {
node.verify(login.loginSucceeds("user", "pegasys"));
}

@Test
public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() {
node.verify(login.loginSucceedsAndSetsAuthenticationToken("user", "pegasys"));
node.verify(net.awaitPeerCount(0));
node.verify(net.netVersionUnauthorizedExceptional("Unauthorized"));
}

@Override
public void tearDownAcceptanceTestBase() {
authenticatedCluster.stop();
super.tearDownAcceptanceTestBase();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[Users.user]
password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC"
permissions = ["fakePermission"]
permissions = ["fakePermission", "net/peerCount"]
Loading