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

Commit

Permalink
[NC-2046] check for invalid token (#856)
Browse files Browse the repository at this point in the history
* check the token if authentication enabled, regardless of user optional status

* more tests
  • Loading branch information
macfarla authored Feb 14, 2019
1 parent a8e9a4d commit b1f22bb
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcNoResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponseType;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcUnauthorizedResponse;
import tech.pegasys.pantheon.metrics.LabelledMetric;
import tech.pegasys.pantheon.metrics.MetricCategory;
import tech.pegasys.pantheon.metrics.MetricsSystem;
Expand Down Expand Up @@ -230,7 +231,7 @@ private Handler<RoutingContext> checkWhitelistHostHeader() {
};
}

private boolean requiresAuthentication(final RoutingContext routingContext) {
private boolean requiresAuthentication() {
return authenticationService.isPresent();
}

Expand All @@ -239,24 +240,26 @@ public boolean isPermitted(final Optional<User> optionalUser, final JsonRpcMetho

AtomicBoolean foundMatchingPermission = new AtomicBoolean();

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

Expand All @@ -272,7 +275,7 @@ private String getToken(final RoutingContext routingContext) {

private void getUser(final String token, final Handler<Optional<User>> handler) {
try {
if (!authenticationService.isPresent()) {
if (!requiresAuthentication()) {
handler.handle(Optional.empty());
} else {
authenticationService
Expand Down Expand Up @@ -343,7 +346,7 @@ 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() && token == null) {
// no auth token when auth required
handleJsonRpcUnauthorizedError(routingContext, null, JsonRpcError.UNAUTHORIZED);
} else {
Expand Down Expand Up @@ -404,6 +407,8 @@ private void handleJsonSingleRequest(
private HttpResponseStatus status(final JsonRpcResponse response) {

switch (response.getType()) {
case UNAUTHORIZED:
return HttpResponseStatus.UNAUTHORIZED;
case ERROR:
return HttpResponseStatus.BAD_REQUEST;
case SUCCESS:
Expand Down Expand Up @@ -506,7 +511,7 @@ private JsonRpcResponse process(final JsonObject requestJson, final Optional<Use
return errorResponse(id, JsonRpcError.INVALID_PARAMS);
}
} else {
return errorResponse(id, JsonRpcError.UNAUTHORIZED);
return unauthorizedResponse(id, JsonRpcError.UNAUTHORIZED);
}
}

Expand All @@ -530,6 +535,10 @@ private JsonRpcResponse errorResponse(final Object id, final JsonRpcError error)
return new JsonRpcErrorResponse(id, error);
}

private JsonRpcResponse unauthorizedResponse(final Object id, final JsonRpcError error) {
return new JsonRpcUnauthorizedResponse(id, error);
}

private String buildCorsRegexFromConfig() {
if (config.getCorsAllowedDomains().isEmpty()) {
return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
public enum JsonRpcResponseType {
NONE,
SUCCESS,
ERROR
ERROR,
UNAUTHORIZED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.ethereum.jsonrpc.internal.response;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.common.base.Objects;

@JsonPropertyOrder({"jsonrpc", "id", "error"})
public class JsonRpcUnauthorizedResponse implements JsonRpcResponse {

private final Object id;
private final JsonRpcError error;

public JsonRpcUnauthorizedResponse(final Object id, final JsonRpcError error) {
this.id = id;
this.error = error;
}

@JsonGetter("id")
public Object getId() {
return id;
}

@JsonGetter("error")
public JsonRpcError getError() {
return error;
}

@Override
@JsonIgnore
public JsonRpcResponseType getType() {
return JsonRpcResponseType.UNAUTHORIZED;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final JsonRpcUnauthorizedResponse that = (JsonRpcUnauthorizedResponse) o;
return Objects.equal(id, that.id) && error == that.error;
}

@Override
public int hashCode() {
return Objects.hashCode(id, error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.KeyStoreOptions;
import io.vertx.ext.auth.PubSecKeyOptions;
Expand Down Expand Up @@ -98,6 +99,7 @@ public class JsonRpcHttpServiceLoginTest {
Arrays.asList(RpcApis.ETH, RpcApis.NET, RpcApis.WEB3, RpcApis.ADMIN);
protected static JWTAuth jwtAuth;
protected static String authPermissionsConfigFilePath = "JsonRpcHttpService/auth.toml";
protected final JsonRpcTestHelper testHelper = new JsonRpcTestHelper();

@BeforeClass
public static void initServerAndClient() throws Exception {
Expand Down Expand Up @@ -334,6 +336,30 @@ public void loginDoesntPopulateJWTPayloadWithPassword()
}
}

private String login(final String username, final String password) throws IOException {
final RequestBody loginBody =
RequestBody.create(
JSON, "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}");
final Request loginRequest =
new Request.Builder().post(loginBody).url(baseUrl + "/login").build();
final String token;
try (final Response loginResp = client.newCall(loginRequest).execute()) {
assertThat(loginResp.code()).isEqualTo(200);
assertThat(loginResp.message()).isEqualTo("OK");
assertThat(loginResp.body().contentType()).isNotNull();
assertThat(loginResp.body().contentType().type()).isEqualTo("application");
assertThat(loginResp.body().contentType().subtype()).isEqualTo("json");
final String bodyString = loginResp.body().string();
assertThat(bodyString).isNotNull();
assertThat(bodyString).isNotBlank();

final JsonObject respBody = new JsonObject(bodyString);
token = respBody.getString("token");
assertThat(token).isNotNull();
}
return token;
}

@Test
public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() throws IOException {
final RequestBody body =
Expand Down Expand Up @@ -381,6 +407,92 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro
public void checkPermissionsWithEmptyUser() {
JsonRpcMethod ethAccounts = new EthAccounts();

assertThat(service.isPermitted(Optional.empty(), ethAccounts)).isTrue();
assertThat(service.isPermitted(Optional.empty(), ethAccounts)).isFalse();
}

@Test
public void web3ClientVersionUnsuccessfulBeforeLogin() throws Exception {
final String id = "123";
final RequestBody body =
RequestBody.create(
JSON,
"{\"jsonrpc\":\"2.0\",\"id\":"
+ Json.encode(id)
+ ",\"method\":\"web3_clientVersion\"}");

try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(401);
assertThat(resp.message()).isEqualTo("Unauthorized");
}
}

@Test
public void web3ClientVersionUnsuccessfulWithBadBearer() throws Exception {
final String id = "123";
final RequestBody body =
RequestBody.create(
JSON,
"{\"jsonrpc\":\"2.0\",\"id\":"
+ Json.encode(id)
+ ",\"method\":\"web3_clientVersion\"}");

try (final Response resp = client.newCall(buildPostRequest(body, "badtoken")).execute()) {
assertThat(resp.code()).isEqualTo(401);
assertThat(resp.message()).isEqualTo("Unauthorized");
}
}

@Test
public void web3ClientVersionSuccessfulAfterLogin() throws Exception {
final String token = login("user", "pegasys");

final String id = "123";
final RequestBody web3ClientVersionBody =
RequestBody.create(
JSON,
"{\"jsonrpc\":\"2.0\",\"id\":"
+ Json.encode(id)
+ ",\"method\":\"web3_clientVersion\"}");

try (final Response web3ClientVersionResp =
client.newCall(buildPostRequest(web3ClientVersionBody, token)).execute()) {
assertThat(web3ClientVersionResp.code()).isEqualTo(200);
// Check general format of result
final JsonObject json = new JsonObject(web3ClientVersionResp.body().string());
testHelper.assertValidJsonRpcResult(json, id);
// Check result
final String result = json.getString("result");
assertThat(result).isEqualTo("TestClientVersion/0.1.0");
}
}

@Test
public void ethSyncingUnauthorisedWithoutPermission() throws Exception {
final String token = login("user", "pegasys");

final String id = "007";
final RequestBody body =
RequestBody.create(
JSON,
"{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_syncing\"}");

try (final Response resp = client.newCall(buildPostRequest(body, token)).execute()) {
assertThat(resp.code()).isEqualTo(401);
assertThat(resp.message()).isEqualTo("Unauthorized");
}
}

private Request buildPostRequest(final RequestBody body) {
return buildPostRequest(body, Optional.empty());
}

private Request buildPostRequest(final RequestBody body, final String token) {
return buildPostRequest(body, Optional.of(token));
}

private Request buildPostRequest(final RequestBody body, final Optional<String> token) {
final Request.Builder request = new Request.Builder().post(body).url(baseUrl);
token.ifPresent(t -> request.addHeader("Bearer", t));
return request.build();
}
}

0 comments on commit b1f22bb

Please sign in to comment.