|
@@ -17,14 +17,18 @@ import org.elasticsearch.test.ESTestCase;
|
|
|
import org.elasticsearch.threadpool.TestThreadPool;
|
|
|
import org.elasticsearch.threadpool.ThreadPool;
|
|
|
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
|
|
+import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
|
|
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
|
|
import org.junit.After;
|
|
|
import org.junit.Before;
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Collection;
|
|
|
import java.util.List;
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.IntStream;
|
|
|
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
|
import static org.hamcrest.Matchers.is;
|
|
@@ -100,14 +104,16 @@ public class CachingServiceAccountsTokenStoreTests extends ESTestCase {
|
|
|
store.authenticate(token2Invalid, future4);
|
|
|
assertThat(future4.get(), is(false));
|
|
|
assertThat(doAuthenticateInvoked.get(), is(true));
|
|
|
- assertThat(cache.count(), equalTo(2));
|
|
|
+ assertThat(cache.count(), equalTo(1)); // invalid token not cached
|
|
|
doAuthenticateInvoked.set(false); // reset
|
|
|
|
|
|
- // 5th auth with the wrong token2 again should use cache
|
|
|
+ // 5th auth with the wrong token2 again does not use cache
|
|
|
final PlainActionFuture<Boolean> future5 = new PlainActionFuture<>();
|
|
|
store.authenticate(token2Invalid, future5);
|
|
|
assertThat(future5.get(), is(false));
|
|
|
- assertThat(doAuthenticateInvoked.get(), is(false));
|
|
|
+ assertThat(doAuthenticateInvoked.get(), is(true));
|
|
|
+ assertThat(cache.count(), equalTo(1)); // invalid token not cached
|
|
|
+ doAuthenticateInvoked.set(false); // reset
|
|
|
|
|
|
// 6th auth with the right token2
|
|
|
final PlainActionFuture<Boolean> future6 = new PlainActionFuture<>();
|
|
@@ -159,4 +165,62 @@ public class CachingServiceAccountsTokenStoreTests extends ESTestCase {
|
|
|
store.authenticate(mock(ServiceAccountToken.class), future);
|
|
|
assertThat(future.get(), is(success));
|
|
|
}
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public void testCacheInvalidateByKeys() {
|
|
|
+ final CachingServiceAccountsTokenStore store = new CachingServiceAccountsTokenStore(globalSettings, threadPool) {
|
|
|
+ @Override
|
|
|
+ void doAuthenticate(ServiceAccountToken token, ActionListener<Boolean> listener) {
|
|
|
+ listener.onResponse(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void findTokensFor(ServiceAccountId accountId, ActionListener<Collection<TokenInfo>> listener) {
|
|
|
+ listener.onFailure(new UnsupportedOperationException());
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ final ServiceAccountId accountId = new ServiceAccountId(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8));
|
|
|
+
|
|
|
+ final ArrayList<ServiceAccountToken> tokens = new ArrayList<>();
|
|
|
+ IntStream.range(0, randomIntBetween(3, 8)).forEach(i -> {
|
|
|
+ final ServiceAccountToken token = ServiceAccountToken.newToken(accountId,
|
|
|
+ randomValueOtherThanMany(n -> n.length() > 248, ValidationTests::randomTokenName));
|
|
|
+ tokens.add(token);
|
|
|
+ store.authenticate(token, mock(ActionListener.class));
|
|
|
+
|
|
|
+ final ServiceAccountToken tokenWithSuffix =
|
|
|
+ ServiceAccountToken.newToken(accountId, token.getTokenName() + randomAlphaOfLengthBetween(3, 8));
|
|
|
+ tokens.add(tokenWithSuffix);
|
|
|
+ store.authenticate(tokenWithSuffix, mock(ActionListener.class));
|
|
|
+ });
|
|
|
+ assertThat(store.getCache().count(), equalTo(tokens.size()));
|
|
|
+
|
|
|
+ // Invalidate a single entry
|
|
|
+ store.invalidate(List.of(randomFrom(tokens).getQualifiedName()));
|
|
|
+ assertThat(store.getCache().count(), equalTo(tokens.size() - 1));
|
|
|
+
|
|
|
+ // Invalidate all entries
|
|
|
+ store.invalidate(List.of(accountId.asPrincipal() + "/"));
|
|
|
+ assertThat(store.getCache().count(), equalTo(0));
|
|
|
+
|
|
|
+ // auth everything again
|
|
|
+ tokens.forEach(t -> store.authenticate(t, mock(ActionListener.class)));
|
|
|
+ assertThat(store.getCache().count(), equalTo(tokens.size()));
|
|
|
+
|
|
|
+ final int nInvalidation = randomIntBetween(1, tokens.size() - 1);
|
|
|
+ final List<String> tokenIdsToInvalidate = randomSubsetOf(nInvalidation, tokens).stream()
|
|
|
+ .map(ServiceAccountToken::getQualifiedName)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ final boolean hasPrefixWildcard = randomBoolean();
|
|
|
+ if (hasPrefixWildcard) {
|
|
|
+ tokenIdsToInvalidate.add(accountId.asPrincipal() + "/");
|
|
|
+ }
|
|
|
+ store.invalidate(tokenIdsToInvalidate);
|
|
|
+ if (hasPrefixWildcard) {
|
|
|
+ assertThat(store.getCache().count(), equalTo(0));
|
|
|
+ } else {
|
|
|
+ assertThat(store.getCache().count(), equalTo(tokens.size() - nInvalidation));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|