mirror of https://github.com/hyperledger/besu
3465 jwt auth (#3508)
* refactored auth to allow variant JWT rules to be adopted by different stacks * configures JWT secret from a file, or creates a temporary one * jwt auth is optional for engine apis * test coverage of new cli options Signed-off-by: Justin Florentine <justin+github@florentine.us>pull/3433/head
parent
9cabf6f528
commit
6947a6c34d
@ -0,0 +1,292 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; |
||||
|
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; |
||||
|
||||
import java.io.File; |
||||
import java.util.Collection; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import javax.annotation.Nullable; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import io.netty.handler.codec.http.HttpResponseStatus; |
||||
import io.vertx.core.Handler; |
||||
import io.vertx.core.Vertx; |
||||
import io.vertx.core.http.HttpServerResponse; |
||||
import io.vertx.core.json.JsonObject; |
||||
import io.vertx.ext.auth.JWTOptions; |
||||
import io.vertx.ext.auth.User; |
||||
import io.vertx.ext.auth.authentication.AuthenticationProvider; |
||||
import io.vertx.ext.auth.jwt.JWTAuth; |
||||
import io.vertx.ext.auth.jwt.JWTAuthOptions; |
||||
import io.vertx.ext.web.RoutingContext; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** Provides authentication handlers for use in the http and websocket services */ |
||||
public class DefaultAuthenticationService implements AuthenticationService { |
||||
|
||||
public static final String USERNAME = "username"; |
||||
private final JWTAuth jwtAuthProvider; |
||||
@VisibleForTesting public final JWTAuthOptions jwtAuthOptions; |
||||
private final Optional<AuthenticationProvider> credentialAuthProvider; |
||||
private static final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); |
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultAuthenticationService.class); |
||||
|
||||
public DefaultAuthenticationService( |
||||
final JWTAuth jwtAuthProvider, |
||||
final JWTAuthOptions jwtAuthOptions, |
||||
final Optional<AuthenticationProvider> credentialAuthProvider) { |
||||
this.jwtAuthProvider = jwtAuthProvider; |
||||
this.jwtAuthOptions = jwtAuthOptions; |
||||
this.credentialAuthProvider = credentialAuthProvider; |
||||
} |
||||
|
||||
/** |
||||
* Creates a ready for use set of authentication providers if authentication is enabled |
||||
* |
||||
* @param vertx The vertx instance that will be providing requests that this set of authentication |
||||
* providers will be handling |
||||
* @param config The {{@link JsonRpcConfiguration}} that describes this rpc setup |
||||
* @return Optionally an authentication service. If empty then authentication isn't to be enabled |
||||
* on this service |
||||
*/ |
||||
public static Optional<AuthenticationService> create( |
||||
final Vertx vertx, final JsonRpcConfiguration config) { |
||||
return create( |
||||
vertx, |
||||
config.isAuthenticationEnabled(), |
||||
config.getAuthenticationCredentialsFile(), |
||||
config.getAuthenticationPublicKeyFile(), |
||||
config.getAuthenticationAlgorithm()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a ready for use set of authentication providers if authentication is enabled |
||||
* |
||||
* @param vertx The vertx instance that will be providing requests that this set of authentication |
||||
* providers will be handling |
||||
* @param config The {{@link WebSocketConfiguration}} that describes this rpc setup |
||||
* @return Optionally an authentication service. If empty then authentication isn't to be enabled |
||||
* on this service |
||||
*/ |
||||
public static Optional<AuthenticationService> create( |
||||
final Vertx vertx, final WebSocketConfiguration config) { |
||||
return create( |
||||
vertx, |
||||
config.isAuthenticationEnabled(), |
||||
config.getAuthenticationCredentialsFile(), |
||||
config.getAuthenticationPublicKeyFile(), |
||||
config.getAuthenticationAlgorithm()); |
||||
} |
||||
|
||||
private static Optional<AuthenticationService> create( |
||||
final Vertx vertx, |
||||
final boolean authenticationEnabled, |
||||
final String authenticationCredentialsFile, |
||||
final File authenticationPublicKeyFile, |
||||
final JwtAlgorithm authenticationAlgorithm) { |
||||
if (!authenticationEnabled) { |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
final JWTAuthOptions jwtAuthOptions; |
||||
if (authenticationPublicKeyFile == null) { |
||||
jwtAuthOptions = jwtAuthOptionsFactory.createWithGeneratedKeyPair(); |
||||
} else { |
||||
jwtAuthOptions = |
||||
authenticationAlgorithm == null |
||||
? jwtAuthOptionsFactory.createForExternalPublicKey(authenticationPublicKeyFile) |
||||
: jwtAuthOptionsFactory.createForExternalPublicKeyWithAlgorithm( |
||||
authenticationPublicKeyFile, authenticationAlgorithm); |
||||
} |
||||
final Optional<AuthenticationProvider> credentialAuthProvider = |
||||
makeCredentialAuthProvider(vertx, authenticationEnabled, authenticationCredentialsFile); |
||||
|
||||
return Optional.of( |
||||
new DefaultAuthenticationService( |
||||
JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider)); |
||||
} |
||||
|
||||
private static Optional<AuthenticationProvider> makeCredentialAuthProvider( |
||||
final Vertx vertx, |
||||
final boolean authenticationEnabled, |
||||
@Nullable final String authenticationCredentialsFile) { |
||||
if (authenticationEnabled && authenticationCredentialsFile != null) { |
||||
return Optional.of( |
||||
new TomlAuthOptions().setTomlPath(authenticationCredentialsFile).createProvider(vertx)); |
||||
} else { |
||||
return Optional.empty(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Static route for terminating login requests when Authentication is disabled |
||||
* |
||||
* @param routingContext The vertx routing context for this request |
||||
*/ |
||||
public static void handleDisabledLogin(final RoutingContext routingContext) { |
||||
routingContext |
||||
.response() |
||||
.setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) |
||||
.setStatusMessage("Authentication not enabled") |
||||
.end(); |
||||
} |
||||
|
||||
/** |
||||
* Handles a login request and checks the provided credentials against our credential auth |
||||
* provider |
||||
* |
||||
* @param routingContext Routing context associated with this request |
||||
*/ |
||||
@Override |
||||
public void handleLogin(final RoutingContext routingContext) { |
||||
if (credentialAuthProvider.isPresent()) { |
||||
login(routingContext, credentialAuthProvider.get()); |
||||
} else { |
||||
handleDisabledLogin(routingContext); |
||||
} |
||||
} |
||||
|
||||
private void login( |
||||
final RoutingContext routingContext, final AuthenticationProvider credentialAuthProvider) { |
||||
final JsonObject requestBody = routingContext.getBodyAsJson(); |
||||
|
||||
if (requestBody == null) { |
||||
routingContext |
||||
.response() |
||||
.setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) |
||||
.setStatusMessage(HttpResponseStatus.BAD_REQUEST.reasonPhrase()) |
||||
.end(); |
||||
return; |
||||
} |
||||
|
||||
// Check user
|
||||
final JsonObject authParams = new JsonObject(); |
||||
authParams.put(USERNAME, requestBody.getValue(USERNAME)); |
||||
authParams.put("password", requestBody.getValue("password")); |
||||
credentialAuthProvider.authenticate( |
||||
authParams, |
||||
r -> { |
||||
if (r.failed()) { |
||||
routingContext |
||||
.response() |
||||
.setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()) |
||||
.setStatusMessage(HttpResponseStatus.UNAUTHORIZED.reasonPhrase()) |
||||
.end(); |
||||
} else { |
||||
final User user = r.result(); |
||||
|
||||
final JWTOptions options = |
||||
new JWTOptions().setExpiresInMinutes(5).setAlgorithm("RS256"); |
||||
final JsonObject jwtContents = |
||||
new JsonObject() |
||||
.put("permissions", user.principal().getValue("permissions")) |
||||
.put(USERNAME, user.principal().getValue(USERNAME)); |
||||
final String privacyPublicKey = user.principal().getString("privacyPublicKey"); |
||||
if (privacyPublicKey != null) { |
||||
jwtContents.put("privacyPublicKey", privacyPublicKey); |
||||
} |
||||
|
||||
final String token = jwtAuthProvider.generateToken(jwtContents, options); |
||||
|
||||
final JsonObject responseBody = new JsonObject().put("token", token); |
||||
final HttpServerResponse response = routingContext.response(); |
||||
if (!response.closed()) { |
||||
response.setStatusCode(200); |
||||
response.putHeader("Content-Type", "application/json"); |
||||
response.end(responseBody.encode()); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public JWTAuth getJwtAuthProvider() { |
||||
return jwtAuthProvider; |
||||
} |
||||
|
||||
@Override |
||||
public void authenticate(final String token, final Handler<Optional<User>> handler) { |
||||
try { |
||||
getJwtAuthProvider() |
||||
.authenticate( |
||||
new JsonObject().put("token", token), |
||||
r -> { |
||||
if (r.succeeded()) { |
||||
final Optional<User> user = Optional.ofNullable(r.result()); |
||||
validateExpiryExists(user); |
||||
handler.handle(user); |
||||
} else { |
||||
LOG.debug("Invalid JWT token {}", r.cause().toString()); |
||||
handler.handle(Optional.empty()); |
||||
} |
||||
}); |
||||
|
||||
} catch (Exception e) { |
||||
LOG.debug("exception validating JWT ", e); |
||||
handler.handle(Optional.empty()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isPermitted( |
||||
final Optional<User> optionalUser, |
||||
final JsonRpcMethod jsonRpcMethod, |
||||
final Collection<String> noAuthMethods) { |
||||
AtomicBoolean foundMatchingPermission = new AtomicBoolean(); |
||||
// if the method is configured as a no auth method we skip permission check
|
||||
if (noAuthMethods.stream().anyMatch(m -> m.equals(jsonRpcMethod.getName()))) { |
||||
return true; |
||||
} |
||||
|
||||
if (optionalUser.isPresent()) { |
||||
User user = optionalUser.get(); |
||||
for (String perm : jsonRpcMethod.getPermissions()) { |
||||
user.isAuthorized( |
||||
perm, |
||||
(authed) -> { |
||||
if (authed.result()) { |
||||
LOG.trace( |
||||
"user {} authorized : {} via permission {}", |
||||
user, |
||||
jsonRpcMethod.getName(), |
||||
perm); |
||||
foundMatchingPermission.set(true); |
||||
} |
||||
}); |
||||
// exit if a matching permission was found, no need to keep checking
|
||||
if (foundMatchingPermission.get()) { |
||||
return foundMatchingPermission.get(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!foundMatchingPermission.get()) { |
||||
LOG.trace("user NOT authorized : {}", jsonRpcMethod.getName()); |
||||
} |
||||
return foundMatchingPermission.get(); |
||||
} |
||||
|
||||
private void validateExpiryExists(final Optional<User> user) { |
||||
if (!user.map(User::attributes).map(a -> a.containsKey("exp")).orElse(false)) { |
||||
throw new IllegalStateException("Invalid JWT doesn't have expiry"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,165 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; |
||||
|
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.Collection; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.Handler; |
||||
import io.vertx.core.Vertx; |
||||
import io.vertx.core.buffer.Buffer; |
||||
import io.vertx.core.json.JsonObject; |
||||
import io.vertx.ext.auth.JWTOptions; |
||||
import io.vertx.ext.auth.PubSecKeyOptions; |
||||
import io.vertx.ext.auth.User; |
||||
import io.vertx.ext.auth.impl.Codec; |
||||
import io.vertx.ext.auth.jwt.JWTAuth; |
||||
import io.vertx.ext.auth.jwt.JWTAuthOptions; |
||||
import io.vertx.ext.web.RoutingContext; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class EngineAuthService implements AuthenticationService { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EngineAuthService.class); |
||||
private final JWTAuth jwtAuthProvider; |
||||
|
||||
public EngineAuthService(final Vertx vertx, final Optional<File> signingKey, final Path datadir) { |
||||
final JWTAuthOptions jwtAuthOptions = |
||||
engineApiJWTOptions(JwtAlgorithm.HS256, signingKey, datadir); |
||||
this.jwtAuthProvider = JWTAuth.create(vertx, jwtAuthOptions); |
||||
} |
||||
|
||||
private JWTAuthOptions engineApiJWTOptions( |
||||
final JwtAlgorithm jwtAlgorithm, final Optional<File> keyFile, final Path datadir) { |
||||
byte[] signingKey = null; |
||||
if (!keyFile.isPresent()) { |
||||
final File jwtFile = new File(datadir.toFile(), "jwt.hex"); |
||||
jwtFile.deleteOnExit(); |
||||
final byte[] ephemeralKey = Bytes32.random().toArray(); |
||||
try { |
||||
Files.writeString(jwtFile.toPath(), Codec.base16Encode(ephemeralKey)); |
||||
} catch (IOException ioe) { |
||||
LOG.warn("Unable to write ephemeral jwt key file to {}", jwtFile.toPath().toString()); |
||||
LOG.info("JWT KEY: {}", Codec.base16Encode(ephemeralKey)); |
||||
} |
||||
signingKey = ephemeralKey; |
||||
} else { // user configured option to use a specified file
|
||||
if (keyFile.get().exists()) { |
||||
try { |
||||
final String keyHex = Files.readAllLines(keyFile.get().toPath()).get(0); |
||||
if (keyHex.length() >= 64) { |
||||
signingKey = Codec.base16Decode(keyHex); |
||||
} else { |
||||
UnsecurableEngineApiException e = |
||||
new UnsecurableEngineApiException("signing key too short, 256 bits required"); |
||||
e.fillInStackTrace(); |
||||
throw e; |
||||
} |
||||
} catch (IOException ioe) { |
||||
UnsecurableEngineApiException e = |
||||
new UnsecurableEngineApiException( |
||||
"Could not read key from " + keyFile.get().toString()); |
||||
e.fillInStackTrace(); |
||||
e.initCause(ioe); |
||||
throw e; |
||||
} |
||||
} else { |
||||
UnsecurableEngineApiException e = |
||||
new UnsecurableEngineApiException( |
||||
"Could not read key from " + keyFile.get().toString()); |
||||
e.fillInStackTrace(); |
||||
throw e; |
||||
} |
||||
} |
||||
if (signingKey == null || signingKey.length < 32) { |
||||
UnsecurableEngineApiException e = |
||||
new UnsecurableEngineApiException( |
||||
"Could not read at least 256 bits of key from " |
||||
+ (keyFile.isPresent() ? keyFile.get().toString() : "undefined")); |
||||
e.fillInStackTrace(); |
||||
throw e; |
||||
} |
||||
|
||||
return new JWTAuthOptions() |
||||
.setJWTOptions(new JWTOptions().setIgnoreExpiration(true).setLeeway(5)) |
||||
.addPubSecKey( |
||||
new PubSecKeyOptions() |
||||
.setAlgorithm(jwtAlgorithm.toString()) |
||||
.setBuffer(Buffer.buffer(signingKey))); |
||||
} |
||||
|
||||
@Override |
||||
public void handleLogin(final RoutingContext routingContext) { |
||||
LOG.warn("Engine Auth does not support logins, no login handled"); |
||||
} |
||||
|
||||
@Override |
||||
public JWTAuth getJwtAuthProvider() { |
||||
return this.jwtAuthProvider; |
||||
} |
||||
|
||||
@Override |
||||
public void authenticate(final String token, final Handler<Optional<User>> handler) { |
||||
try { |
||||
JsonObject jwt = new JsonObject().put("token", token); |
||||
getJwtAuthProvider() |
||||
.authenticate( |
||||
jwt, |
||||
r -> { |
||||
if (r.succeeded()) { |
||||
if (issuedRecently(r.result().attributes().getLong("iat"))) { |
||||
final Optional<User> user = Optional.ofNullable(r.result()); |
||||
handler.handle(user); |
||||
} else { |
||||
LOG.warn("Client sent stale token: {}", r.result().attributes()); |
||||
handler.handle(Optional.empty()); |
||||
} |
||||
|
||||
} else { |
||||
LOG.debug("Authentication failed: {}", r.cause().toString()); |
||||
handler.handle(Optional.empty()); |
||||
} |
||||
}); |
||||
|
||||
} catch (Exception e) { |
||||
LOG.debug("exception validating JWT ", e); |
||||
handler.handle(Optional.empty()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isPermitted( |
||||
final Optional<User> optionalUser, |
||||
final JsonRpcMethod jsonRpcMethod, |
||||
final Collection<String> noAuthMethods) { |
||||
return true; // no AuthZ for engine APIs
|
||||
} |
||||
|
||||
private boolean issuedRecently(final long iat) { |
||||
long iatSecondsSinceEpoch = iat; |
||||
long nowSecondsSinceEpoch = System.currentTimeMillis() / 1000; |
||||
return (Math.abs((nowSecondsSinceEpoch - iatSecondsSinceEpoch)) <= 5); |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; |
||||
|
||||
/* |
||||
* Thrown when config options are insufficient to secure the Engine API |
||||
*/ |
||||
public class UnsecurableEngineApiException extends RuntimeException { |
||||
public UnsecurableEngineApiException(final String reason) { |
||||
super(reason); |
||||
} |
||||
} |
@ -0,0 +1,120 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URISyntaxException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.util.Arrays; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.Handler; |
||||
import io.vertx.core.Vertx; |
||||
import io.vertx.core.json.JsonObject; |
||||
import io.vertx.ext.auth.User; |
||||
import io.vertx.ext.auth.jwt.JWTAuth; |
||||
import org.junit.Test; |
||||
|
||||
public class EngineAuthServiceTest { |
||||
|
||||
@Test |
||||
public void createsEphemeralByDefault() throws IOException { |
||||
Vertx vertx = mock(Vertx.class); |
||||
Path dataDir = Files.createTempDirectory("besuUnitTest"); |
||||
EngineAuthService auth = new EngineAuthService(vertx, Optional.empty(), dataDir); |
||||
assertThat(auth).isNotNull(); |
||||
assertThat(dataDir.toFile()).exists(); |
||||
assertThat(dataDir.toFile()).isDirectory(); |
||||
boolean defaultFileFound = |
||||
Arrays.stream(dataDir.toFile().listFiles()) |
||||
.anyMatch( |
||||
file -> { |
||||
return file.getName().equals("jwt.hex"); |
||||
}); |
||||
assertThat(defaultFileFound).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void usesSpecified() throws IOException, URISyntaxException { |
||||
Vertx vertx = mock(Vertx.class); |
||||
final Path userKey = |
||||
Paths.get(ClassLoader.getSystemResource("authentication/ee-jwt-secret.hex").toURI()); |
||||
Path dataDir = Files.createTempDirectory("besuUnitTest"); |
||||
EngineAuthService auth = new EngineAuthService(vertx, Optional.of(userKey.toFile()), dataDir); |
||||
assertThat(auth).isNotNull(); |
||||
JWTAuth jwtAuth = auth.getJwtAuthProvider(); |
||||
String token = |
||||
jwtAuth.generateToken(new JsonObject().put("iat", System.currentTimeMillis() / 1000)); |
||||
|
||||
Handler<Optional<User>> authHandler = |
||||
new Handler<Optional<User>>() { |
||||
@Override |
||||
public void handle(final Optional<User> event) { |
||||
assertThat(event).isPresent(); |
||||
assertThat(event.get()).isNotNull(); |
||||
} |
||||
}; |
||||
auth.authenticate(token, authHandler); |
||||
} |
||||
|
||||
@Test(expected = UnsecurableEngineApiException.class) |
||||
public void throwsOnShortKey() throws IOException, URISyntaxException { |
||||
Vertx vertx = mock(Vertx.class); |
||||
final Path userKey = |
||||
Paths.get( |
||||
ClassLoader.getSystemResource("authentication/ee-jwt-secret-too-short.hex").toURI()); |
||||
Path dataDir = Files.createTempDirectory("besuUnitTest"); |
||||
EngineAuthService auth = new EngineAuthService(vertx, Optional.of(userKey.toFile()), dataDir); |
||||
assertThat(auth).isNotNull(); |
||||
} |
||||
|
||||
@Test(expected = UnsecurableEngineApiException.class) |
||||
public void throwsKeyFileMissing() throws IOException, URISyntaxException { |
||||
Vertx vertx = mock(Vertx.class); |
||||
final Path userKey = Paths.get("no-such-file.hex"); |
||||
Path dataDir = Files.createTempDirectory("besuUnitTest"); |
||||
EngineAuthService auth = new EngineAuthService(vertx, Optional.of(userKey.toFile()), dataDir); |
||||
assertThat(auth).isNotNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void denyExpired() throws IOException, URISyntaxException { |
||||
Vertx vertx = mock(Vertx.class); |
||||
final Path userKey = |
||||
Paths.get(ClassLoader.getSystemResource("authentication/ee-jwt-secret.hex").toURI()); |
||||
Path dataDir = Files.createTempDirectory("besuUnitTest"); |
||||
EngineAuthService auth = new EngineAuthService(vertx, Optional.of(userKey.toFile()), dataDir); |
||||
assertThat(auth).isNotNull(); |
||||
JWTAuth jwtAuth = auth.getJwtAuthProvider(); |
||||
String token = |
||||
jwtAuth.generateToken(new JsonObject().put("iat", (System.currentTimeMillis() / 1000) - 6)); |
||||
|
||||
Handler<Optional<User>> authHandler = |
||||
new Handler<Optional<User>>() { |
||||
@Override |
||||
public void handle(final Optional<User> event) { |
||||
assertThat(event).isEmpty(); |
||||
} |
||||
}; |
||||
auth.authenticate(token, authHandler); |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
9465710175a93a3f2d67b0cb98d92d44ead4d1126a12233571884de92a8edc |
@ -0,0 +1 @@ |
||||
9465710175a93a3f2d67b0cb98d92d44ead4d1126a12233571884de92a8edc76 |
Loading…
Reference in new issue