Skip to content

Commit

Permalink
Rebase on PR redis#3068
Browse files Browse the repository at this point in the history
  • Loading branch information
ggivo committed Dec 4, 2024
1 parent e570e31 commit 6531424
Showing 1 changed file with 46 additions and 56 deletions.
102 changes: 46 additions & 56 deletions src/test/java/io/lettuce/core/AuthenticationIntegrationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@

import javax.inject.Inject;

import io.lettuce.authx.TokenBasedRedisCredentialsProvider;
import io.lettuce.authx.TokenBasedRedisCredentialsProvider;
import io.lettuce.core.event.command.CommandListener;
import io.lettuce.core.event.command.CommandSucceededEvent;
import io.lettuce.core.protocol.RedisCommand;
import io.lettuce.test.Delay;
import io.lettuce.test.Delay;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
Expand All @@ -27,11 +25,13 @@
import io.lettuce.test.WithPassword;
import io.lettuce.test.condition.EnabledOnCommand;
import io.lettuce.test.settings.TestSettings;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import redis.clients.authentication.core.SimpleToken;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
Expand Down Expand Up @@ -122,12 +122,38 @@ void streamingCredentialProvider(RedisClient client) {
client.removeListener(listener);
}

private boolean isAuthCommandWithCredentials(RedisCommand<?, ?, ?> command, String username, char[] password) {
if (command.getType() == CommandType.AUTH) {
CommandArgs<?, ?> args = command.getArgs();
return args.toCommandString().contains(username) && args.toCommandString().contains(String.valueOf(password));
}
return false;

@Test
@Inject
void tokenBasedCredentialProvider(RedisClient client) {

TestCommandListener listener = new TestCommandListener();
client.addListener(listener);

TestTokenManager tokenManager = new TestTokenManager(null, null);
TokenBasedRedisCredentialsProvider credentialsProvider = new TokenBasedRedisCredentialsProvider(tokenManager);

// Build RedisURI with streaming credentials provider
RedisURI uri = RedisURI.builder().withHost(TestSettings.host()).withPort(TestSettings.port())
.withClientName("streaming_cred_test").withAuthentication(credentialsProvider)
.withTimeout(Duration.ofSeconds(5)).build();
tokenManager.emitToken(testToken(TestSettings.username(), TestSettings.password().toString().toCharArray()));

StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8, uri);
assertThat(connection.sync().aclWhoami()).isEqualTo(TestSettings.username());

// rotate the credentials
tokenManager.emitToken(testToken("steave", "foobared".toCharArray()));

Awaitility.await().atMost(Duration.ofSeconds(1)).until(() -> listener.succeeded.stream()
.anyMatch(command -> isAuthCommandWithCredentials(command, "steave", "foobared".toCharArray())));

// verify that the connection is re-authenticated with the new user credentials
assertThat(connection.sync().aclWhoami()).isEqualTo("steave");

credentialsProvider.shutdown();
client.removeListener(listener);
connection.close();
}

static class TestCommandListener implements CommandListener {
Expand All @@ -143,52 +169,16 @@ public void commandSucceeded(CommandSucceededEvent event) {

}

}

@Test
@Inject
void tokenBasedCredentialProvider(RedisClient client) {

ClientOptions clientOptions = ClientOptions.builder()
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build();
client.setOptions(clientOptions);
// Connection used to simulate test user credential rotation
StatefulRedisConnection<String, String> defaultConnection = client.connect();

String testUser = "streaming_cred_test_user";
char[] testPassword1 = "token_1".toCharArray();
char[] testPassword2 = "token_2".toCharArray();

TestTokenManager tokenManager = new TestTokenManager(null, null);

// streaming credentials provider that emits redis credentials which will trigger connection re-authentication
// token manager is used to emit updated credentials
TokenBasedRedisCredentialsProvider credentialsProvider = new TokenBasedRedisCredentialsProvider(tokenManager);

RedisURI uri = RedisURI.builder().withTimeout(Duration.ofSeconds(1)).withClientName("streaming_cred_test")
.withHost(TestSettings.host()).withPort(TestSettings.port()).withAuthentication(credentialsProvider).build();

// create test user with initial credentials set to 'testPassword1'
createTestUser(defaultConnection, testUser, testPassword1);
tokenManager.emitToken(testToken(testUser, testPassword1));

StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8, uri);
assertThat(connection.sync().aclWhoami()).isEqualTo(testUser);

// update test user credentials in Redis server (password changed to testPassword2)
// then emit updated credentials trough streaming credentials provider
// and trigger re-connect to force re-authentication
// updated credentials should be used for re-authentication
updateTestUser(defaultConnection, testUser, testPassword2);
tokenManager.emitToken(testToken(testUser, testPassword2));
connection.sync().quit();

Delay.delay(Duration.ofMillis(100));
assertThat(connection.sync().ping()).isEqualTo("PONG");

String res = connection.sync().aclWhoami();
assertThat(res).isEqualTo(testUser);
private boolean isAuthCommandWithCredentials(RedisCommand<?, ?, ?> command, String username, char[] password) {
if (command.getType() == CommandType.AUTH) {
CommandArgs<?, ?> args = command.getArgs();
return args.toCommandString().contains(username) && args.toCommandString().contains(String.valueOf(password));
}
return false;
}

defaultConnection.close();
connection.close();
private SimpleToken testToken(String username, char[] password) {
return new SimpleToken(String.valueOf(password), Instant.now().plusMillis(500).toEpochMilli(),
Instant.now().toEpochMilli(), Collections.singletonMap("oid", username));
}
}

0 comments on commit 6531424

Please sign in to comment.