-
Notifications
You must be signed in to change notification settings - Fork 130
[NC-2046] JWT authentication for JSON-RPC #815
Changes from 24 commits
d32d4a5
f8b861a
253aea4
b285a4d
6178576
98c8bbb
76d3c2b
a58f9bb
b6d66bd
48bb6ba
f9be563
02e17d6
94285b3
bcf2eba
daf80c3
d39091a
ccd333d
6982558
a933608
6249104
8167fe8
e5fc663
94cde24
cb5bbef
27788d2
4ea975b
df6bf62
43eca89
cea1ea4
8b2ed76
9296f26
c5918b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -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; | ||
private String token = null; | ||
|
||
public PantheonNode( | ||
final String name, | ||
|
@@ -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()), | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This caused one AT to fail so putting it back in There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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"] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍