diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/AppConfiguration.java b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/AppConfiguration.java index 824f88172c..bc04d72586 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/AppConfiguration.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/AppConfiguration.java @@ -16,8 +16,10 @@ import io.lettuce.core.ReadFrom; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; -import io.lettuce.core.api.sync.RedisStringCommands; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.reactive.RedisStringReactiveCommands; import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.reactive.RedisAdvancedClusterReactiveCommands; import io.lettuce.core.codec.StringCodec; import io.lettuce.core.masterreplica.MasterReplica; import io.lettuce.core.masterreplica.StatefulRedisMasterReplicaConnection; @@ -37,7 +39,7 @@ public class AppConfiguration { @Value("${redis.password}") private String redisPassword; - @Value("${redis.timeout:5000}") + @Value("${redis.timeout:500}") private Integer redisTimeout; @Value("${redis.ignoreErrors:true}") @@ -56,7 +58,7 @@ public IRevokedTokenRepository revokedTokenRepository() { return new IRevokedTokenRepository.MemoryRevokedTokenRepository(); } - private RedisStringCommands initRedisCommand() { + private RedisStringReactiveCommands initRedisCommand() { Duration timeoutDuration = Duration.ofMillis(redisTimeout); logger.info("The plugin using redis {} for storage revoked tokens.\n" + @@ -76,14 +78,14 @@ private RedisStringCommands initRedisCommand() { } } - public static RedisStringCommands initRedisCommandSentinel(String redisUrl, + public static RedisReactiveCommands initRedisCommandSentinel(String redisUrl, Duration redisTimeout) { RedisURI redisURI = RedisURI.create(redisUrl); redisURI.setTimeout(redisTimeout); - return RedisClient.create(redisURI).connect().sync(); + return RedisClient.create(redisURI).connect().reactive(); } - public static RedisStringCommands initRedisCommandStandalone(String redisUrl, + public static RedisReactiveCommands initRedisCommandStandalone(String redisUrl, String redisPassword, Duration redisTimeout) { String[] redisUrlParts = redisUrl.split(":"); @@ -94,26 +96,26 @@ public static RedisStringCommands initRedisCommandStandalone(Str .withTimeout(redisTimeout) .build(); - return RedisClient.create(redisURI).connect().sync(); + return RedisClient.create(redisURI).connect().reactive(); } - public static RedisStringCommands initRedisCommandCluster(String redisUrl, - String redisPassword, - Duration redisTimeout) { + public static RedisAdvancedClusterReactiveCommands initRedisCommandCluster(String redisUrl, + String redisPassword, + Duration redisTimeout) { List redisURIList = buildRedisUriList(redisUrl, redisPassword, redisTimeout); - return RedisClusterClient.create(redisURIList).connect().sync(); + return RedisClusterClient.create(redisURIList).connect().reactive(); } - public static RedisStringCommands initRedisCommandMasterReplica(String redisUrl, - String redisPassword, - Duration redisTimeout) { + public static RedisReactiveCommands initRedisCommandMasterReplica(String redisUrl, + String redisPassword, + Duration redisTimeout) { List redisURIList = buildRedisUriList(redisUrl, redisPassword, redisTimeout); RedisClient redisClient = RedisClient.create(); StatefulRedisMasterReplicaConnection connection = MasterReplica .connect(redisClient, StringCodec.UTF8, redisURIList); connection.setReadFrom(ReadFrom.MASTER_PREFERRED); - return connection.sync(); + return connection.reactive(); } public static List buildRedisUriList(String redisUrl, String redisPassword, Duration redisTimeout) { diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/ChannelLogoutController.java b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/ChannelLogoutController.java index 14493f9ee3..a29a9b8213 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/ChannelLogoutController.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/ChannelLogoutController.java @@ -32,7 +32,7 @@ public ResponseEntity addRevokedToken(@RequestParam MultiValueMap { logger.debug("Add new revoked token has sid: " + sid); - tokenRepository.add(sid); + tokenRepository.add(sid).block(); }, () -> logger.warn("`{}` is missing or invalid in request", TOKEN_PARAM)); return ResponseEntity.ok().build(); diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/IRevokedTokenRepository.java b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/IRevokedTokenRepository.java index dbbd1dfa70..156700938d 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/IRevokedTokenRepository.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/IRevokedTokenRepository.java @@ -5,11 +5,13 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import reactor.core.publisher.Mono; + public interface IRevokedTokenRepository { - void add(String sid); + Mono add(String sid); - boolean exist(String sid); + Mono exist(String sid); class MemoryRevokedTokenRepository implements IRevokedTokenRepository { @@ -22,13 +24,13 @@ public MemoryRevokedTokenRepository() { } @Override - public void add(String sid) { - cache.put(sid, true); + public Mono add(String sid) { + return Mono.fromRunnable(() -> cache.put(sid, true)); } @Override - public boolean exist(String sid) { - return cache.getIfPresent(sid) != null; + public Mono exist(String sid) { + return Mono.fromCallable(() -> cache.getIfPresent(sid) != null); } } } diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/RedisRevokedTokenRepository.java b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/RedisRevokedTokenRepository.java index 87d86cec6c..72d0dffef7 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/RedisRevokedTokenRepository.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/RedisRevokedTokenRepository.java @@ -5,9 +5,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.lettuce.core.RedisException; import io.lettuce.core.SetArgs; -import io.lettuce.core.api.sync.RedisStringCommands; +import io.lettuce.core.api.reactive.RedisStringReactiveCommands; +import reactor.core.publisher.Mono; public class RedisRevokedTokenRepository implements IRevokedTokenRepository { @@ -15,38 +15,40 @@ public class RedisRevokedTokenRepository implements IRevokedTokenRepository { public static final Boolean IGNORE_REDIS_ERRORS = true; private final Logger logger = LoggerFactory.getLogger("RevokedTokenPlugin"); - private final RedisStringCommands redisCommand; + private final RedisStringReactiveCommands redisCommand; private final boolean ignoreRedisErrors; - public RedisRevokedTokenRepository(RedisStringCommands redisCommand, + public RedisRevokedTokenRepository(RedisStringReactiveCommands redisCommand, boolean ignoreRedisErrors) { this.redisCommand = redisCommand; this.ignoreRedisErrors = ignoreRedisErrors; } @Override - public void add(String sid) { - try { - redisCommand.set(sid, Boolean.TRUE.toString(), SetArgs.Builder.ex(CACHE_DURATION)); - } catch (RedisException e) { - if (!ignoreRedisErrors) { - throw e; - } - logger.warn("Error while add revoked token in Redis: {}", e.getMessage()); - } + public Mono add(String sid) { + return redisCommand.set(sid, Boolean.TRUE.toString(), SetArgs.Builder.ex(CACHE_DURATION)) + .then() + .onErrorResume(e -> { + if (!ignoreRedisErrors) { + return Mono.error(e); + } + logger.warn("Error while add revoked token in Redis: {}", e.getMessage()); + return Mono.empty(); + }); } @Override - public boolean exist(String sid) { - try { - return Boolean.TRUE.toString().equalsIgnoreCase(redisCommand.get(sid)); - } catch (RedisException e) { - if (!ignoreRedisErrors) { - throw e; - } - logger.warn("Error while checking token in Redis: {}", e.getMessage()); - return false; - } + public Mono exist(String sid) { + return redisCommand.get(sid) + .map(Boolean.TRUE.toString()::equalsIgnoreCase) + .switchIfEmpty(Mono.just(false)) + .onErrorResume(e -> { + if (!ignoreRedisErrors) { + return Mono.error(e); + } + logger.warn("Error while checking token in Redis: {}", e.getMessage()); + return Mono.just(false); + }); } } diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/TokenRevokedFilter.java b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/TokenRevokedFilter.java index 8bd64e06d6..1048d2a4a9 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/TokenRevokedFilter.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/main/java/com/linagora/apisix/plugin/TokenRevokedFilter.java @@ -11,6 +11,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + @Component public class TokenRevokedFilter implements PluginFilter { private final Logger logger = LoggerFactory.getLogger("RevokedTokenPlugin"); @@ -29,25 +31,32 @@ public String name() { @Override public void filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) { logger.debug("Received a new request"); - sidFilter(request, response); - chain.filter(request, response); + sidFilter(request, response) + .doFinally(any -> chain.filter(request, response)) + .subscribe(); } - private void sidFilter(HttpRequest request, HttpResponse response) { - Optional.ofNullable(request.getHeader("Authorization")) + private Mono sidFilter(HttpRequest request, HttpResponse response) { + return Optional.ofNullable(request.getHeader("Authorization")) .or(() -> Optional.ofNullable(request.getHeader("authorization"))) .map(String::trim) .map(bearerToken -> bearerToken.startsWith("Bearer ") ? bearerToken.substring(7) : bearerToken) .flatMap(ChannelLogoutController::extractSidFromLogoutToken) - .ifPresent(sid -> { - boolean existSid = tokenRepository.exist(sid); + .map(sid -> validateSid(request, response, sid)) + .orElse(Mono.empty()); + } + + private Mono validateSid(HttpRequest request, HttpResponse response, String sid) { + return tokenRepository.exist(sid) + .doOnNext(existSid -> { if (existSid) { logger.info("Token has been revoked, Sid: " + sid); makeUnAuthorizedRequest(request, response); } else { logger.debug("Token valid, Sid: " + sid); } - }); + }) + .then(); } public static void makeUnAuthorizedRequest(HttpRequest request, HttpResponse response) { diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisMasterReplicaRevokedTokenRepositoryTest.java b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisMasterReplicaRevokedTokenRepositoryTest.java index ba2ba3dc29..c65b7fe0b0 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisMasterReplicaRevokedTokenRepositoryTest.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisMasterReplicaRevokedTokenRepositoryTest.java @@ -21,7 +21,7 @@ import org.testcontainers.utility.DockerImageName; import io.lettuce.core.RedisException; -import io.lettuce.core.api.sync.RedisStringCommands; +import io.lettuce.core.api.reactive.RedisStringReactiveCommands; class RedisMasterReplicaRevokedTokenRepositoryTest implements RevokedTokenRepositoryContract { private static final String REDIS_PASSWORD = "my_password"; @@ -75,7 +75,7 @@ static void afterAll() { @BeforeEach void setup() { - RedisStringCommands stringStringRedisStringCommands = AppConfiguration.initRedisCommandMasterReplica( + RedisStringReactiveCommands stringStringRedisStringCommands = AppConfiguration.initRedisCommandMasterReplica( String.format("localhost:%d,localhost:%d", REDIS_MASTER.getMappedPort(6379), REDIS_REPLICA.getMappedPort(6379)), REDIS_PASSWORD, Duration.ofSeconds(3)); @@ -106,16 +106,16 @@ void existShouldNotThrowWhenIgnoreWasConfiguredAndRedisError() throws Interrupte @Test void existsShouldReturnCorrectWhenIgnoreWasConfigured() throws InterruptedException { - testee().add("sid1"); - assertThat(testee().exist("sid1")).isTrue(); + testee().add("sid1").block(); + assertThat(testee().exist("sid1").block()).isTrue(); ContainerHelper.pause(REDIS_MASTER); TimeUnit.SECONDS.sleep(1); - assertThat(testee().exist("sid1")).isFalse(); + assertThat(testee().exist("sid1").block()).isFalse(); ContainerHelper.unPause(REDIS_MASTER); TimeUnit.SECONDS.sleep(1); - assertThat(testee().exist("sid1")).isTrue(); + assertThat(testee().exist("sid1").block()).isTrue(); } @Test @@ -128,6 +128,6 @@ void existsShouldThrowWhenIgnoreWasNotConfiguredAndRedisError() throws Interrupt ContainerHelper.pause(REDIS_MASTER); TimeUnit.SECONDS.sleep(1); - assertThatThrownBy(() -> testee.exist("sid1")).isInstanceOf(RedisException.class); + assertThatThrownBy(() -> testee.exist("sid1").block()).isInstanceOf(RedisException.class); } } diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisRevokedTokenRepositoryTest.java b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisRevokedTokenRepositoryTest.java index 5c2a176fee..13f4c06246 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisRevokedTokenRepositoryTest.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisRevokedTokenRepositoryTest.java @@ -18,7 +18,7 @@ import org.testcontainers.utility.DockerImageName; import io.lettuce.core.RedisException; -import io.lettuce.core.api.sync.RedisStringCommands; +import io.lettuce.core.api.reactive.RedisStringReactiveCommands; class RedisRevokedTokenRepositoryTest implements RevokedTokenRepositoryContract { static final String REDIS_PASSWORD = "redisSecret1"; @@ -40,7 +40,7 @@ static void afterAll() { @BeforeEach void beforeEach() { - RedisStringCommands redisStringCommands = AppConfiguration.initRedisCommandStandalone( + RedisStringReactiveCommands redisStringCommands = AppConfiguration.initRedisCommandStandalone( String.format("%s:%d", REDIS_CONTAINER.getHost(), REDIS_CONTAINER.getMappedPort(6379)), REDIS_PASSWORD, Duration.ofSeconds(3)); testee = new RedisRevokedTokenRepository(redisStringCommands, IGNORE_REDIS_ERRORS); @@ -65,22 +65,22 @@ void existShouldNotThrowWhenIgnoreWasConfiguredAndRedisError() throws Interrupte ContainerHelper.pause(REDIS_CONTAINER); TimeUnit.SECONDS.sleep(1); - assertThatCode(() -> testee().exist("sid1")).doesNotThrowAnyException(); + assertThatCode(() -> testee().exist("sid1").block()).doesNotThrowAnyException(); } @Test void existsShouldReturnCorrectWhenIgnoreWasConfigured() throws InterruptedException { - testee().add("sid1"); - assertThat(testee().exist("sid1")).isTrue(); + testee().add("sid1").block(); + assertThat(testee().exist("sid1").block()).isTrue(); ContainerHelper.pause(REDIS_CONTAINER); TimeUnit.SECONDS.sleep(1); - assertThat(testee().exist("sid1")).isFalse(); + assertThat(testee().exist("sid1").block()).isFalse(); ContainerHelper.unPause(REDIS_CONTAINER); TimeUnit.SECONDS.sleep(1); - assertThat(testee().exist("sid1")).isTrue(); + assertThat(testee().exist("sid1").block()).isTrue(); } @Test diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisSentinelRevokedTokenRepositoryTest.java b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisSentinelRevokedTokenRepositoryTest.java index d4746e5e3f..3a79ebee31 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisSentinelRevokedTokenRepositoryTest.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RedisSentinelRevokedTokenRepositoryTest.java @@ -10,8 +10,8 @@ import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.reactive.RedisStringReactiveCommands; import io.lettuce.core.api.sync.RedisCommands; -import io.lettuce.core.api.sync.RedisStringCommands; @Disabled("This test is disabled because it requires a Redis Sentinel cluster to be running, for manual testing only, " + "can run Redis Sentinel cluster by using docker-compose sample at https://github.com/apache/james-project/blob/149595da247dfb915ecb60d239edf627616916ae/server/mailet/rate-limiter-redis/docker-compose-sample/docker-compose-with-redis-sentinel.yml") @@ -21,7 +21,7 @@ public class RedisSentinelRevokedTokenRepositoryTest implements RevokedTokenRepo RedisRevokedTokenRepository revokedTokenRepository; - RedisStringCommands stringStringRedisStringCommands; + RedisStringReactiveCommands stringStringRedisStringCommands; @BeforeEach void setup() { diff --git a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RevokedTokenRepositoryContract.java b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RevokedTokenRepositoryContract.java index 897ba1c6e4..9e222e7f3d 100644 --- a/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RevokedTokenRepositoryContract.java +++ b/demo/apisix/tmail-apisix-plugin-runner/src/test/java/com/linagora/apisix/plugin/RevokedTokenRepositoryContract.java @@ -12,28 +12,28 @@ interface RevokedTokenRepositoryContract { @Test default void existShouldReturnFalseWhenDoesNotExist() { - assertThat(testee().exist("sid1")).isFalse(); + assertThat(testee().exist("sid1").block()).isFalse(); } @Test default void existShouldReturnTrueWhenExist() { String sid = "sid1"; - testee().add(sid); - assertThat(testee().exist(sid)).isTrue(); + testee().add(sid).block(); + assertThat(testee().exist(sid).block()).isTrue(); } @Test default void addShouldBeIdempotent() { String sid = "sid1"; - testee().add(sid); - assertThatCode(() -> testee().add(sid)).doesNotThrowAnyException(); - assertThat(testee().exist(sid)).isTrue(); + testee().add(sid).block(); + assertThatCode(() -> testee().add(sid).block()).doesNotThrowAnyException(); + assertThat(testee().exist(sid).block()).isTrue(); } @Test default void existShouldCheckingAssignKey() { - testee().add("sid2"); - assertThat(testee().exist("sid1")).isFalse(); + testee().add("sid2").block(); + assertThat(testee().exist("sid1").block()).isFalse(); } class MemoryRevokedTokenRepositoryTest implements RevokedTokenRepositoryContract {