From d32d4a5bf43e11a22a50ee8951e77b3c5f686813 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 7 Feb 2019 14:11:51 +1000 Subject: [PATCH 01/17] added JsonRpcHttpService.isPermitted(User, JsonRpcMethod) and JsonRpcMethod.getPermissions() and unit tests --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 22 +++++++ .../internal/methods/JsonRpcMethod.java | 15 +++++ .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 62 +++++++++++++++++++ .../internal/methods/NetListeningTest.java | 8 +++ 4 files changed, 107 insertions(+) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 6c6b27991b..f8e02b98ae 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -41,6 +41,7 @@ import java.util.Optional; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; @@ -67,6 +68,7 @@ import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.CorsHandler; +import io.vertx.ext.web.handler.JWTAuthHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -204,6 +206,8 @@ public CompletableFuture start() { .produces(APPLICATION_JSON) .handler(this::handleLogin); + jwtAuthProvider.ifPresent(jwtAuth -> router.route("/").handler(JWTAuthHandler.create(jwtAuth))); + final CompletableFuture resultFuture = new CompletableFuture<>(); httpServer .requestHandler(router) @@ -249,6 +253,24 @@ private Handler checkWhitelistHostHeader() { }; } + @VisibleForTesting + public boolean isPermitted(final User user, final JsonRpcMethod jsonRpcMethod) { + + AtomicBoolean foundMatchingPermission = new AtomicBoolean(); + + for (String perm : jsonRpcMethod.getPermissions()) { + user.isAuthorized( + perm, + (authed) -> { + if (authed.result()) { + LOG.trace("user {} has permission to do method {}", user, perm); + foundMatchingPermission.set(true); + } + }); + } + return foundMatchingPermission.get(); + } + private Optional getAndValidateHostHeader(final RoutingContext event) { final Iterable splitHostHeader = Splitter.on(':').split(event.request().host()); final long hostPieces = stream(splitHostHeader).count(); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java index 11b7b776c4..4f2992e2d7 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java @@ -15,6 +15,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import java.util.ArrayList; +import java.util.List; + public interface JsonRpcMethod { /** @@ -31,4 +34,16 @@ public interface JsonRpcMethod { * @return output from applying the JSON-RPC method to the input. */ JsonRpcResponse response(JsonRpcRequest request); + + /** + * The list of Permissions that correspond to this JSON-RPC method. e.g. [net/*, net/listening] + * + * @return list of permissions that match this method. + */ + default List getPermissions() { + List permissions = new ArrayList<>(); + permissions.add(this.getName().replace('_', '/')); + permissions.add(this.getName().substring(0, this.getName().indexOf('_')) + "/*"); + return permissions; + }; } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index 2fafe09c9d..f86440a10a 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -24,7 +24,12 @@ import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.eth.EthProtocol; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthAccounts; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthBlockNumber; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.NetVersion; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.Web3ClientVersion; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.Web3Sha3; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; @@ -81,6 +86,7 @@ public class JsonRpcHttpServiceLoginTest { protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; protected static Synchronizer synchronizer; + private final JsonRpcTestHelper testHelper = new JsonRpcTestHelper(); protected static final Collection JSON_RPC_APIS = Arrays.asList(RpcApis.ETH, RpcApis.NET, RpcApis.WEB3, RpcApis.ADMIN); private static StubAuthProvider stubCredentialProvider; @@ -283,4 +289,60 @@ public void loginWithGoodCredentialsAndPermissions() throws IOException { }); } } + + @Test + public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermission() throws IOException { + final User mockUser = mock(User.class); + stubCredentialProvider.setRespondUser(mockUser); + when(mockUser.principal()) + .thenReturn( + new JsonObject() + .put("permissions", new JsonArray(Arrays.asList("eth/blockNumber", "web3/*")))); + + final RequestBody body = + RequestBody.create(JSON, "{\"username\":\"user\",\"password\":\"pass\"}"); + 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(); + + JsonRpcMethod ethAccounts = new EthAccounts(); + JsonRpcMethod netVersion = new NetVersion(123); + JsonRpcMethod ethBlockNumber = new EthBlockNumber(blockchainQueries); + JsonRpcMethod web3Sha3 = new Web3Sha3(); + JsonRpcMethod web3ClientVersion = new Web3ClientVersion("777"); + + final JWTAuth auth = JWTAuth.create(vertx, jwtOptions); + + auth.authenticate( + new JsonObject().put("jwt", token), + (r) -> { + assertThat(r.succeeded()).isTrue(); + final User user = r.result(); + // single eth/blockNumber method permitted + assertThat(service.isPermitted(user, ethBlockNumber)).isTrue(); + // eth/accounts not permitted + assertThat(service.isPermitted(user, ethAccounts)).isFalse(); + // allowed by web3/* + assertThat(service.isPermitted(user, web3ClientVersion)).isTrue(); + assertThat(service.isPermitted(user, web3Sha3)).isTrue(); + // no net permissions + assertThat(service.isPermitted(user, netVersion)).isFalse(); + }); + } + } + + private Request buildRequest(final RequestBody body) { + return new Request.Builder().post(body).url(baseUrl).build(); + } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java index d41d526401..f19d8f4cc4 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java @@ -20,6 +20,8 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +60,12 @@ public void shouldReturnFalseWhenNetworkIsNotListening() { assertThat(method.response(request)).isEqualToComparingFieldByField(expectedResponse); } + @Test + public void getPermissions() { + List permissions = method.getPermissions(); + assertThat(permissions).containsExactlyInAnyOrder("net/*", "net/listening"); + } + private JsonRpcRequest netListeningRequest() { return new JsonRpcRequest("2.0", "net_listening", new Object[] {}); } From 253aea4cf3e326d4e80ffd7e718aa6f63c74518d Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 7 Feb 2019 14:29:41 +1000 Subject: [PATCH 02/17] logging --- .../pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 1071d769e7..45cbda17fa 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -263,11 +263,12 @@ public boolean isPermitted(final User user, final JsonRpcMethod jsonRpcMethod) { perm, (authed) -> { if (authed.result()) { - LOG.trace("user {} has permission to do method {}", user, perm); + LOG.trace("user {} authorized : {} via permission {}", user, jsonRpcMethod.getName(), perm); foundMatchingPermission.set(true); } }); } + LOG.trace("user {} NOT authorized : {}", user, jsonRpcMethod.getName()); return foundMatchingPermission.get(); } From b285a4dc3e531c0f7d836bc7d3ac56cc99696089 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Fri, 8 Feb 2019 12:21:31 +1000 Subject: [PATCH 03/17] added getUser handler --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 97 ++++++++++++++----- .../internal/response/JsonRpcError.java | 7 +- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 20 ++-- 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 45cbda17fa..534eeb60e8 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -254,24 +254,61 @@ private Handler checkWhitelistHostHeader() { } @VisibleForTesting - public boolean isPermitted(final User user, final JsonRpcMethod jsonRpcMethod) { + public boolean isPermitted(final Optional optionalUser, final JsonRpcMethod jsonRpcMethod) { AtomicBoolean foundMatchingPermission = new AtomicBoolean(); - 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 (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); + } + }); + } + } else { + // no user means auth provider configured thus anything is permitted + foundMatchingPermission.set(true); + } + + if (!foundMatchingPermission.get()) { + LOG.trace("user NOT authorized : {}", jsonRpcMethod.getName()); } - LOG.trace("user {} NOT authorized : {}", user, jsonRpcMethod.getName()); return foundMatchingPermission.get(); } + private void getUser(final RoutingContext routingContext, final Handler> handler) { + + if (!jwtAuthProvider.isPresent()) { + handler.handle(Optional.empty()); + } + + String token = routingContext.request().getHeader("Bearer"); + if (token == null) { + // TODO + // if null future failed + } + + if (jwtAuthProvider.isPresent()) { + jwtAuthProvider + .get() + .authenticate( + new JsonObject().put("jwt", token), + (r) -> { + final User user = r.result(); + handler.handle(Optional.of(user)); + }); + } + } + private Optional getAndValidateHostHeader(final RoutingContext event) { final Iterable splitHostHeader = Splitter.on(':').split(event.request().host()); final long hostPieces = stream(splitHostHeader).count(); @@ -327,14 +364,22 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { try { final String json = routingContext.getBodyAsString().trim(); if (!json.isEmpty() && json.charAt(0) == '{') { - handleJsonSingleRequest(routingContext, new JsonObject(json)); + getUser( + routingContext, + user -> { + handleJsonSingleRequest(routingContext, new JsonObject(json), user); + }); } else { final JsonArray array = new JsonArray(json); if (array.size() < 1) { handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); return; } - handleJsonBatchRequest(routingContext, array); + getUser( + routingContext, + user -> { + handleJsonBatchRequest(routingContext, array, user); + }); } } catch (final DecodeException ex) { handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); @@ -347,11 +392,11 @@ private void handleEmptyRequest(final RoutingContext routingContext) { } private void handleJsonSingleRequest( - final RoutingContext routingContext, final JsonObject request) { + final RoutingContext routingContext, final JsonObject request, final Optional user) { final HttpServerResponse response = routingContext.response(); vertx.executeBlocking( future -> { - final JsonRpcResponse jsonRpcResponse = process(request); + final JsonRpcResponse jsonRpcResponse = process(request, user); future.complete(jsonRpcResponse); }, false, @@ -442,7 +487,7 @@ private String serialise(final JsonRpcResponse response) { @SuppressWarnings("rawtypes") private void handleJsonBatchRequest( - final RoutingContext routingContext, final JsonArray jsonArray) { + final RoutingContext routingContext, final JsonArray jsonArray, final Optional user) { // Interpret json as rpc request final List responses = jsonArray.stream() @@ -456,7 +501,7 @@ private void handleJsonBatchRequest( final JsonObject req = (JsonObject) obj; final Future fut = Future.future(); vertx.executeBlocking( - future -> future.complete(process(req)), + future -> future.complete(process(req, user)), false, ar -> { if (ar.failed()) { @@ -493,7 +538,7 @@ private boolean isNonEmptyResponses(final JsonRpcResponse result) { return result.getType() != JsonRpcResponseType.NONE; } - private JsonRpcResponse process(final JsonObject requestJson) { + private JsonRpcResponse process(final JsonObject requestJson, final Optional user) { final JsonRpcRequest request; Object id = null; try { @@ -515,12 +560,16 @@ private JsonRpcResponse process(final JsonObject requestJson) { return errorResponse(id, JsonRpcError.METHOD_NOT_FOUND); } - // Generate response - try (final TimingContext ignored = requestTimer.labels(request.getMethod()).startTimer()) { - return method.response(request); - } catch (final InvalidJsonRpcParameters e) { - LOG.debug(e); - return errorResponse(id, JsonRpcError.INVALID_PARAMS); + if (isPermitted(user, method)) { + // Generate response + try (final TimingContext ignored = requestTimer.labels(request.getMethod()).startTimer()) { + return method.response(request); + } catch (final InvalidJsonRpcParameters e) { + LOG.debug(e); + return errorResponse(id, JsonRpcError.INVALID_PARAMS); + } + } else { + return errorResponse(id, JsonRpcError.UNAUTHORIZED); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 21bd0ac88f..9691ca6c01 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -49,7 +49,7 @@ public enum JsonRpcError { // Wallet errors COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"), - // Permissioning errors + // Permissioning/Account whitelist errors ACCOUNT_WHITELIST_NOT_ENABLED(-32000, "Account whitelisting has not been enabled"), ACCOUNT_WHITELIST_EMPTY_ENTRY(-32000, "Request contains an empty list of accounts"), ACCOUNT_WHITELIST_INVALID_ENTRY(-32000, "Request contains an invalid account"), @@ -57,7 +57,7 @@ public enum JsonRpcError { ACCOUNT_WHITELIST_EXISTING_ENTRY(-32000, "Cannot add an existing account to whitelist"), ACCOUNT_WHITELIST_ABSENT_ENTRY(-32000, "Cannot remove an absent account from whitelist"), - // Node whitelist errors + // Permissioning/Node whitelist errors NODE_WHITELIST_NOT_ENABLED(-32000, "Node whitelisting has not been enabled"), NODE_WHITELIST_EMPTY_ENTRY(-32000, "Request contains an empty list of nodes"), NODE_WHITELIST_INVALID_ENTRY(-32000, "Request contains an invalid node"), @@ -65,6 +65,9 @@ public enum JsonRpcError { NODE_WHITELIST_EXISTING_ENTRY(-32000, "Cannot add an existing node to whitelist"), NODE_WHITELIST_MISSING_ENTRY(-32000, "Cannot remove an absent node from whitelist"), + // Permissioning/Authorization errors + UNAUTHORIZED(-40100, "Unauthorized"), + ENCLAVE_IS_DOWN(-32000, "Enclave is down"); private final int code; diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index f86440a10a..69ac43c655 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -86,7 +86,6 @@ public class JsonRpcHttpServiceLoginTest { protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; protected static Synchronizer synchronizer; - private final JsonRpcTestHelper testHelper = new JsonRpcTestHelper(); protected static final Collection JSON_RPC_APIS = Arrays.asList(RpcApis.ETH, RpcApis.NET, RpcApis.WEB3, RpcApis.ADMIN); private static StubAuthProvider stubCredentialProvider; @@ -291,7 +290,7 @@ public void loginWithGoodCredentialsAndPermissions() throws IOException { } @Test - public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermission() throws IOException { + public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() throws IOException { final User mockUser = mock(User.class); stubCredentialProvider.setRespondUser(mockUser); when(mockUser.principal()) @@ -330,18 +329,25 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermission() throw assertThat(r.succeeded()).isTrue(); final User user = r.result(); // single eth/blockNumber method permitted - assertThat(service.isPermitted(user, ethBlockNumber)).isTrue(); + assertThat(service.isPermitted(Optional.of(user), ethBlockNumber)).isTrue(); // eth/accounts not permitted - assertThat(service.isPermitted(user, ethAccounts)).isFalse(); + assertThat(service.isPermitted(Optional.of(user), ethAccounts)).isFalse(); // allowed by web3/* - assertThat(service.isPermitted(user, web3ClientVersion)).isTrue(); - assertThat(service.isPermitted(user, web3Sha3)).isTrue(); + assertThat(service.isPermitted(Optional.of(user), web3ClientVersion)).isTrue(); + assertThat(service.isPermitted(Optional.of(user), web3Sha3)).isTrue(); // no net permissions - assertThat(service.isPermitted(user, netVersion)).isFalse(); + assertThat(service.isPermitted(Optional.of(user), netVersion)).isFalse(); }); } } + @Test + public void checkPermissionsWithEmptyUser() { + JsonRpcMethod ethAccounts = new EthAccounts(); + + assertThat(service.isPermitted(Optional.empty(), ethAccounts)).isTrue(); + } + private Request buildRequest(final RequestBody body) { return new Request.Builder().post(body).url(baseUrl).build(); } From 98c8bbbba05d4417d979c3aef6318f2368e2b18a Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Fri, 8 Feb 2019 13:46:36 +1000 Subject: [PATCH 04/17] 401 if no auth token --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 534eeb60e8..b91c130f5c 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -285,18 +285,16 @@ public boolean isPermitted(final Optional optionalUser, final JsonRpcMetho return foundMatchingPermission.get(); } - private void getUser(final RoutingContext routingContext, final Handler> handler) { + private String getToken(final RoutingContext routingContext) { + return routingContext.request().getHeader("Bearer"); + } + + private void getUser(final String token, final Handler> handler) { if (!jwtAuthProvider.isPresent()) { handler.handle(Optional.empty()); } - String token = routingContext.request().getHeader("Bearer"); - if (token == null) { - // TODO - // if null future failed - } - if (jwtAuthProvider.isPresent()) { jwtAuthProvider .get() @@ -360,12 +358,18 @@ public String url() { } private void handleJsonRPCRequest(final RoutingContext routingContext) { + // first check token if authentication is enabled + String token = getToken(routingContext); + if (jwtAuthProvider.isPresent() && token == null) { + handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); + } + // Parse json try { final String json = routingContext.getBodyAsString().trim(); if (!json.isEmpty() && json.charAt(0) == '{') { getUser( - routingContext, + token, user -> { handleJsonSingleRequest(routingContext, new JsonObject(json), user); }); @@ -376,7 +380,7 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { return; } getUser( - routingContext, + token, user -> { handleJsonBatchRequest(routingContext, array, user); }); From 48bb6ba6552192dc1ec09e55523f0405c0e956d8 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Mon, 11 Feb 2019 05:48:08 +1000 Subject: [PATCH 05/17] changes to tests --- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 21 +++++-------------- .../resources/JsonRpcHttpService/auth.toml | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index f673f904c1..0d2c53c3de 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -81,7 +81,8 @@ public class JsonRpcHttpServiceLoginTest { protected static Synchronizer synchronizer; protected static final Collection JSON_RPC_APIS = Arrays.asList(RpcApis.ETH, RpcApis.NET, RpcApis.WEB3, RpcApis.ADMIN); - private static JWTAuth jwtAuth; + protected static JWTAuth jwtAuth; + protected static String authPermissionsConfigFilePath = "JsonRpcHttpService/auth.toml"; @BeforeClass public static void initServerAndClient() throws Exception { @@ -123,7 +124,7 @@ public static void initServerAndClient() throws Exception { private static JsonRpcHttpService createJsonRpcHttpService() throws Exception { final String authTomlPath = - Paths.get(ClassLoader.getSystemResource("JsonRpcHttpService/auth.toml").toURI()) + Paths.get(ClassLoader.getSystemResource(authPermissionsConfigFilePath).toURI()) .toAbsolutePath() .toString(); @@ -235,15 +236,9 @@ public void loginWithGoodCredentialsAndPermissions() throws IOException { @Test public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() throws IOException { - final User mockUser = mock(User.class); - stubCredentialProvider.setRespondUser(mockUser); - when(mockUser.principal()) - .thenReturn( - new JsonObject() - .put("permissions", new JsonArray(Arrays.asList("eth/blockNumber", "web3/*")))); final RequestBody body = - RequestBody.create(JSON, "{\"username\":\"user\",\"password\":\"pass\"}"); + RequestBody.create(JSON, "{\"username\":\"user\",\"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); @@ -265,9 +260,7 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro JsonRpcMethod web3Sha3 = new Web3Sha3(); JsonRpcMethod web3ClientVersion = new Web3ClientVersion("777"); - final JWTAuth auth = JWTAuth.create(vertx, jwtOptions); - - auth.authenticate( + jwtAuth.authenticate( new JsonObject().put("jwt", token), (r) -> { assertThat(r.succeeded()).isTrue(); @@ -291,8 +284,4 @@ public void checkPermissionsWithEmptyUser() { assertThat(service.isPermitted(Optional.empty(), ethAccounts)).isTrue(); } - - private Request buildRequest(final RequestBody body) { - return new Request.Builder().post(body).url(baseUrl).build(); - } } diff --git a/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml b/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml index f36672b9ba..f654161945 100644 --- a/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml +++ b/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml @@ -1,3 +1,3 @@ [Users.user] password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC" -permissions = ["fakePermission"] +permissions = ["fakePermission","eth/blockNumber", "web3/*"] From 02e17d623f60b68648f524eeef5730793ac76754 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Mon, 11 Feb 2019 13:31:05 +1000 Subject: [PATCH 06/17] acceptance test for node with authentication turned on needs to not await discovery --- .../HttpServiceLoginAcceptanceTest.java | 16 +++- .../ethereum/jsonrpc/JsonRpcHttpService.java | 84 ++++++++++--------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 01170f4317..784230263e 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -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(); + + authenticatedCluster = new Cluster(clusterConfiguration, net); node = pantheon.createArchiveNodeWithAuthentication("node1"); - cluster.start(node); + authenticatedCluster.start(node); } @Test @@ -39,4 +47,10 @@ public void shouldFailLoginWithWrongCredentials() { public void shouldSucceedLoginWithCorrectCredentials() { node.verify(login.loginSucceeds("user", "pegasys")); } + + @Override + public void tearDownAcceptanceTestBase() { + authenticatedCluster.stop(); + super.tearDownAcceptanceTestBase(); + } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 8795b0b314..ebdf228219 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -308,6 +308,10 @@ private Handler checkWhitelistHostHeader() { }; } + private boolean requiresAuthentication(final RoutingContext routingContext) { + return (jwtAuthProvider.isPresent()); + } + @VisibleForTesting public boolean isPermitted(final Optional optionalUser, final JsonRpcMethod jsonRpcMethod) { @@ -345,21 +349,22 @@ private String getToken(final RoutingContext routingContext) { } private void getUser(final String token, final Handler> handler) { - - if (!jwtAuthProvider.isPresent()) { + try { + if (!jwtAuthProvider.isPresent()) { + handler.handle(Optional.empty()); + } else { + jwtAuthProvider + .get() + .authenticate( + new JsonObject().put("jwt", token), + (r) -> { + final User user = r.result(); + handler.handle(Optional.of(user)); + }); + } + } catch (Exception e) { handler.handle(Optional.empty()); } - - if (jwtAuthProvider.isPresent()) { - jwtAuthProvider - .get() - .authenticate( - new JsonObject().put("jwt", token), - (r) -> { - final User user = r.result(); - handler.handle(Optional.of(user)); - }); - } } private Optional getAndValidateHostHeader(final RoutingContext event) { @@ -413,35 +418,36 @@ public String url() { } private void handleJsonRPCRequest(final RoutingContext routingContext) { - // first check token if authentication is enabled + // first check token if authentication is required String token = getToken(routingContext); - if (jwtAuthProvider.isPresent() && token == null) { - handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); - } - - // Parse json - try { - final String json = routingContext.getBodyAsString().trim(); - if (!json.isEmpty() && json.charAt(0) == '{') { - getUser( - token, - user -> { - handleJsonSingleRequest(routingContext, new JsonObject(json), user); - }); - } else { - final JsonArray array = new JsonArray(json); - if (array.size() < 1) { - handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); - return; + if (requiresAuthentication(routingContext) && token != null) { + // Parse json + try { + final String json = routingContext.getBodyAsString().trim(); + if (!json.isEmpty() && json.charAt(0) == '{') { + getUser( + token, + user -> { + handleJsonSingleRequest(routingContext, new JsonObject(json), user); + }); + } else { + final JsonArray array = new JsonArray(json); + if (array.size() < 1) { + handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); + return; + } + getUser( + token, + user -> { + handleJsonBatchRequest(routingContext, array, user); + }); } - getUser( - token, - user -> { - handleJsonBatchRequest(routingContext, array, user); - }); + } catch (final DecodeException ex) { + handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); } - } catch (final DecodeException ex) { - handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); + } else { + // no auth token when auth required + handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); } } From d39091ac3bd219c498a6249f4316c02f99f84dd5 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 12:04:03 +1000 Subject: [PATCH 07/17] fixed dodgy if statement --- .../pantheon/ethereum/jsonrpc/JsonRpcHttpService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 7b4f00fe15..07d4bd64bd 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -343,7 +343,10 @@ public String url() { private void handleJsonRPCRequest(final RoutingContext routingContext) { // first check token if authentication is required String token = getToken(routingContext); - if (requiresAuthentication(routingContext) && token != null) { + if (requiresAuthentication(routingContext) && token == null) { + // no auth token when auth required + handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); + } else { // Parse json try { final String json = routingContext.getBodyAsString().trim(); @@ -368,9 +371,6 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { } catch (final DecodeException ex) { handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); } - } else { - // no auth token when auth required - handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); } } From ccd333d329c083be09effd9f4522f68e23f02517 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 12:18:36 +1000 Subject: [PATCH 08/17] use HTTP unauthorized status code --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 07d4bd64bd..b52a7af8fd 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -231,7 +231,7 @@ private Handler checkWhitelistHostHeader() { } private boolean requiresAuthentication(final RoutingContext routingContext) { - return (authenticationService.isPresent()); + return authenticationService.isPresent(); } @VisibleForTesting @@ -345,29 +345,29 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { String token = getToken(routingContext); if (requiresAuthentication(routingContext) && token == null) { // no auth token when auth required - handleJsonRpcError(routingContext, null, JsonRpcError.UNAUTHORIZED); + handleJsonRpcUnauthorizedError(routingContext, null, JsonRpcError.UNAUTHORIZED); } else { - // Parse json - try { - final String json = routingContext.getBodyAsString().trim(); - if (!json.isEmpty() && json.charAt(0) == '{') { - getUser( - token, - user -> { - handleJsonSingleRequest(routingContext, new JsonObject(json), user); - }); - } else { - final JsonArray array = new JsonArray(json); - if (array.size() < 1) { - handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); - return; + // Parse json + try { + final String json = routingContext.getBodyAsString().trim(); + if (!json.isEmpty() && json.charAt(0) == '{') { + getUser( + token, + user -> { + handleJsonSingleRequest(routingContext, new JsonObject(json), user); + }); + } else { + final JsonArray array = new JsonArray(json); + if (array.size() < 1) { + handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); + return; + } + getUser( + token, + user -> { + handleJsonBatchRequest(routingContext, array, user); + }); } - getUser( - token, - user -> { - handleJsonBatchRequest(routingContext, array, user); - }); - } } catch (final DecodeException ex) { handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); } @@ -518,6 +518,14 @@ private void handleJsonRpcError( .end(Json.encode(new JsonRpcErrorResponse(id, error))); } + private void handleJsonRpcUnauthorizedError( + final RoutingContext routingContext, final Object id, final JsonRpcError error) { + routingContext + .response() + .setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()) + .end(Json.encode(new JsonRpcErrorResponse(id, error))); + } + private JsonRpcResponse errorResponse(final Object id, final JsonRpcError error) { return new JsonRpcErrorResponse(id, error); } From a933608d4f7c7dc50deb32dbcbbc6fb248401256 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 13:14:17 +1000 Subject: [PATCH 09/17] spots --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index b52a7af8fd..5d9759c9a1 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -347,27 +347,27 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { // no auth token when auth required handleJsonRpcUnauthorizedError(routingContext, null, JsonRpcError.UNAUTHORIZED); } else { - // Parse json - try { - final String json = routingContext.getBodyAsString().trim(); - if (!json.isEmpty() && json.charAt(0) == '{') { - getUser( - token, - user -> { - handleJsonSingleRequest(routingContext, new JsonObject(json), user); - }); - } else { - final JsonArray array = new JsonArray(json); - if (array.size() < 1) { - handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); - return; - } - getUser( - token, - user -> { - handleJsonBatchRequest(routingContext, array, user); - }); + // Parse json + try { + final String json = routingContext.getBodyAsString().trim(); + if (!json.isEmpty() && json.charAt(0) == '{') { + getUser( + token, + user -> { + handleJsonSingleRequest(routingContext, new JsonObject(json), user); + }); + } else { + final JsonArray array = new JsonArray(json); + if (array.size() < 1) { + handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); + return; } + getUser( + token, + user -> { + handleJsonBatchRequest(routingContext, array, user); + }); + } } catch (final DecodeException ex) { handleJsonRpcError(routingContext, null, JsonRpcError.PARSE_ERROR); } From 8167fe837d852e51503959a981ee0fcbc9c17094 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 15:00:22 +1000 Subject: [PATCH 10/17] added acceptance test for permissioned JSON RPC --- .../dsl/node/NodeConfiguration.java | 2 ++ .../acceptance/dsl/node/PantheonNode.java | 22 +++++++++++++++++++ .../HttpServiceLoginAcceptanceTest.java | 9 ++++++++ .../test/resources/authentication/auth.toml | 2 +- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/NodeConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/NodeConfiguration.java index bde4baf19c..5cc22d0d79 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/NodeConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/NodeConfiguration.java @@ -25,6 +25,8 @@ public interface NodeConfiguration { void useWebSocketsForJsonRpc(); + void useAuthenticationTokenInHeaderForJsonRpc(String token); + Optional jsonRpcWebSocketPort(); String hostName(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 5d7f5e3276..91b06aaae2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -90,6 +90,8 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto private HttpRequestFactory httpRequestFactory; private Optional 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(); + } + + 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 diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 784230263e..3b5d085e33 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -13,7 +13,9 @@ package tech.pegasys.pantheon.tests.acceptance.jsonrpc; import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; 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; @@ -48,6 +50,13 @@ public void shouldSucceedLoginWithCorrectCredentials() { node.verify(login.loginSucceeds("user", "pegasys")); } + @Test + public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { + String token = node.executeHttpTransaction(new LoginTransaction("user", "pegasys")); + ((PantheonNode) node).useAuthenticationTokenInHeaderForJsonRpc(token); + node.verify(net.awaitPeerCount(0)); + } + @Override public void tearDownAcceptanceTestBase() { authenticatedCluster.stop(); diff --git a/acceptance-tests/src/test/resources/authentication/auth.toml b/acceptance-tests/src/test/resources/authentication/auth.toml index f36672b9ba..7e226025c5 100644 --- a/acceptance-tests/src/test/resources/authentication/auth.toml +++ b/acceptance-tests/src/test/resources/authentication/auth.toml @@ -1,3 +1,3 @@ [Users.user] password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC" -permissions = ["fakePermission"] +permissions = ["fakePermission", "net/peerCount"] From 94cde248341d15096f9af28461c9301f088bf1e0 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 15:29:27 +1000 Subject: [PATCH 11/17] added DSL condition for NetVersion to fail because Unauthorized --- .../ExpectNetVersionPermissionException.java | 44 +++++++++++++++++++ .../tests/acceptance/dsl/jsonrpc/Net.java | 5 +++ .../net/NetVersionTransaction.java | 4 +- .../HttpServiceLoginAcceptanceTest.java | 1 + 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java new file mode 100644 index 0000000000..0be49e8635 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java @@ -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); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java index 5c651b6d19..001b33aad4 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java @@ -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; @@ -46,6 +47,10 @@ public Condition netVersionExceptional(final Class 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()); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java index 9217a60d48..7b4d7a92b3 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java @@ -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 { @@ -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); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 3b5d085e33..c86ec89aa2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -55,6 +55,7 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { String token = node.executeHttpTransaction(new LoginTransaction("user", "pegasys")); ((PantheonNode) node).useAuthenticationTokenInHeaderForJsonRpc(token); node.verify(net.awaitPeerCount(0)); + node.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } @Override From cb5bbefde80587ead72a0164a5f5e9464b664d17 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 15:53:44 +1000 Subject: [PATCH 12/17] Added condition to do login and set auth token --- .../pantheon/tests/acceptance/dsl/jsonrpc/Login.java | 11 +++++++++++ .../jsonrpc/HttpServiceLoginAcceptanceTest.java | 5 +---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java index 114483909e..d816ffa210 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java @@ -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 { @@ -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); + }; + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index c86ec89aa2..22f81a4107 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -13,9 +13,7 @@ package tech.pegasys.pantheon.tests.acceptance.jsonrpc; import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; -import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; -import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; 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; @@ -52,8 +50,7 @@ public void shouldSucceedLoginWithCorrectCredentials() { @Test public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { - String token = node.executeHttpTransaction(new LoginTransaction("user", "pegasys")); - ((PantheonNode) node).useAuthenticationTokenInHeaderForJsonRpc(token); + node.verify(login.loginSucceedsAndSetsAuthenticationToken("user", "pegasys")); node.verify(net.awaitPeerCount(0)); node.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } From 27788d2bfb90797d4c6ce05778f5bdbf6898c772 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 16:01:14 +1000 Subject: [PATCH 13/17] typos --- .../pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java | 2 +- .../ethereum/jsonrpc/internal/response/JsonRpcError.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 5d9759c9a1..e2bc7694da 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -256,7 +256,7 @@ public boolean isPermitted(final Optional optionalUser, final JsonRpcMetho }); } } else { - // no user means auth provider configured thus anything is permitted + // no user means no auth provider configured thus anything is permitted foundMatchingPermission.set(true); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index abff3e1d0b..142cf98e2e 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -79,7 +79,7 @@ public enum JsonRpcError { // Permissioning/Authorization errors UNAUTHORIZED(-40100, "Unauthorized"), - // Privacy errors + // Private transaction errors ENCLAVE_IS_DOWN(-32000, "Enclave is down"); private final int code; From 4ea975b7cef9278bd4e6f7a321b58d65f4f0686f Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 16:40:51 +1000 Subject: [PATCH 14/17] PR comments --- .../pantheon/tests/acceptance/dsl/node/PantheonNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 91b06aaae2..c4b8e3f5de 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -90,7 +90,6 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto private HttpRequestFactory httpRequestFactory; private Optional ethNetworkConfig = Optional.empty(); private boolean useWsForJsonRpc = false; - private boolean useAuthenticationTokenInHeaderForJsonRpc = false; private String token = null; public PantheonNode( @@ -203,7 +202,7 @@ private JsonRequestFactories jsonRequestFactories() { .map(url -> new HttpService(url)) .orElse(new HttpService("http://" + LOCALHOST + ":" + port)); - if (useAuthenticationTokenInHeaderForJsonRpc) { + if (token != null) { ((HttpService) web3jService).addHeader("Bearer", token); } @@ -252,6 +251,7 @@ public void useWebSocketsForJsonRpc() { if (jsonRequestFactories != null) { jsonRequestFactories.shutdown(); + jsonRequestFactories = null; } if (httpRequestFactory != null) { @@ -267,13 +267,13 @@ public void useAuthenticationTokenInHeaderForJsonRpc(final String token) { if (jsonRequestFactories != null) { jsonRequestFactories.shutdown(); + jsonRequestFactories = null; } if (httpRequestFactory != null) { httpRequestFactory = null; } - useAuthenticationTokenInHeaderForJsonRpc = true; this.token = token; } From df6bf629861000b746d8f85c7ee7efa7d4cc8c70 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 12 Feb 2019 19:12:32 +1000 Subject: [PATCH 15/17] don't set factories to null --- .../pantheon/tests/acceptance/dsl/node/PantheonNode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index c4b8e3f5de..e472ba7222 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -251,7 +251,6 @@ public void useWebSocketsForJsonRpc() { if (jsonRequestFactories != null) { jsonRequestFactories.shutdown(); - jsonRequestFactories = null; } if (httpRequestFactory != null) { @@ -267,7 +266,6 @@ public void useAuthenticationTokenInHeaderForJsonRpc(final String token) { if (jsonRequestFactories != null) { jsonRequestFactories.shutdown(); - jsonRequestFactories = null; } if (httpRequestFactory != null) { From cea1ea464a0d8ff70de6cc31e7d9863f27b718e1 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Wed, 13 Feb 2019 08:07:20 +1000 Subject: [PATCH 16/17] added */* permission --- .../ethereum/jsonrpc/internal/methods/JsonRpcMethod.java | 1 + .../ethereum/jsonrpc/internal/methods/NetListeningTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java index 4f2992e2d7..5db810f3b2 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/JsonRpcMethod.java @@ -42,6 +42,7 @@ public interface JsonRpcMethod { */ default List getPermissions() { List permissions = new ArrayList<>(); + permissions.add("*/*"); permissions.add(this.getName().replace('_', '/')); permissions.add(this.getName().substring(0, this.getName().indexOf('_')) + "/*"); return permissions; diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java index f19d8f4cc4..56399e2d04 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/NetListeningTest.java @@ -63,7 +63,7 @@ public void shouldReturnFalseWhenNetworkIsNotListening() { @Test public void getPermissions() { List permissions = method.getPermissions(); - assertThat(permissions).containsExactlyInAnyOrder("net/*", "net/listening"); + assertThat(permissions).containsExactlyInAnyOrder("net/*", "net/listening", "*/*"); } private JsonRpcRequest netListeningRequest() { From 8b2ed7628d7632a54faae24ff8d230364c8418d1 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Wed, 13 Feb 2019 09:52:54 +1000 Subject: [PATCH 17/17] added AwaitLoginResponse --- .../condition/login/AwaitLoginResponse.java | 34 +++++++++++++++++ .../httptransaction/HttpRequestFactory.java | 12 ++++++ .../dsl/httptransaction/LoginResponds.java | 37 +++++++++++++++++++ .../tests/acceptance/dsl/jsonrpc/Login.java | 6 +++ .../HttpServiceLoginAcceptanceTest.java | 1 + 5 files changed, 90 insertions(+) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/login/AwaitLoginResponse.java create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/LoginResponds.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/login/AwaitLoginResponse.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/login/AwaitLoginResponse.java new file mode 100644 index 0000000000..72a2c2935e --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/login/AwaitLoginResponse.java @@ -0,0 +1,34 @@ +/* + * 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.login; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils; +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginResponds; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; + +public class AwaitLoginResponse implements Condition { + + private final LoginResponds transaction; + + public AwaitLoginResponse(final LoginResponds transaction) { + this.transaction = transaction; + } + + @Override + public void verify(final Node node) { + WaitUtils.waitFor(() -> assertThat(node.executeHttpTransaction(transaction)).isNotNull()); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/HttpRequestFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/HttpRequestFactory.java index 45e43670e5..4817f74952 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/HttpRequestFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/HttpRequestFactory.java @@ -66,4 +66,16 @@ public void loginUnauthorized(final String username, final String password) thro assertThat(response.message()).isEqualTo("Unauthorized"); } } + + public String loginResponds(final String username, final String password) throws IOException { + final RequestBody requestBody = + RequestBody.create( + JSON, "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}"); + final Request request = new Request.Builder().post(requestBody).url(uri + "/login").build(); + try (final Response response = client.newCall(request).execute()) { + assertThat(response).isNotNull(); + assertThat(response.message()).isNotNull(); + return response.message(); + } + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/LoginResponds.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/LoginResponds.java new file mode 100644 index 0000000000..bece998a54 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/httptransaction/LoginResponds.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 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.httptransaction; + +import static org.junit.Assert.fail; + +import java.io.IOException; + +public class LoginResponds implements HttpTransaction { + private final String username; + private final String password; + + public LoginResponds(final String username, final String password) { + this.username = username; + this.password = password; + } + + @Override + public String execute(final HttpRequestFactory httpFactory) { + try { + return httpFactory.loginResponds(username, password); + } catch (IOException e) { + fail("Login request failed with exception: " + e.toString()); + return null; + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java index d816ffa210..9664a86e66 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Login.java @@ -15,6 +15,8 @@ import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.login.AwaitLoginResponse; +import tech.pegasys.pantheon.tests.acceptance.dsl.httptransaction.LoginResponds; 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; @@ -43,4 +45,8 @@ public Condition loginSucceedsAndSetsAuthenticationToken( ((PantheonNode) n).useAuthenticationTokenInHeaderForJsonRpc(token); }; } + + public Condition awaitLoginResponse(final String username, final String password) { + return new AwaitLoginResponse(new LoginResponds(username, password)); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 22f81a4107..4b9cf79d87 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -36,6 +36,7 @@ public void setUp() throws IOException, URISyntaxException { authenticatedCluster = new Cluster(clusterConfiguration, net); node = pantheon.createArchiveNodeWithAuthentication("node1"); authenticatedCluster.start(node); + node.verify(login.awaitLoginResponse("user", "badpassword")); } @Test