|
@@ -27,6 +27,7 @@ import org.elasticsearch.action.search.MultiSearchResponse;
|
|
|
import org.elasticsearch.action.search.SearchRequest;
|
|
|
import org.elasticsearch.action.search.SearchRequestBuilder;
|
|
|
import org.elasticsearch.action.search.SearchResponse;
|
|
|
+import org.elasticsearch.action.search.ShardSearchFailure;
|
|
|
import org.elasticsearch.action.search.TransportMultiSearchAction;
|
|
|
import org.elasticsearch.action.search.TransportSearchAction;
|
|
|
import org.elasticsearch.action.support.PlainActionFuture;
|
|
@@ -54,6 +55,7 @@ import org.elasticsearch.index.query.BoolQueryBuilder;
|
|
|
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
|
|
|
import org.elasticsearch.index.query.QueryBuilder;
|
|
|
import org.elasticsearch.index.query.QueryBuilders;
|
|
|
+import org.elasticsearch.index.query.TermQueryBuilder;
|
|
|
import org.elasticsearch.index.shard.ShardId;
|
|
|
import org.elasticsearch.search.SearchHits;
|
|
|
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
|
@@ -69,6 +71,7 @@ import org.elasticsearch.transport.RemoteTransportException;
|
|
|
import org.elasticsearch.xcontent.XContentBuilder;
|
|
|
import org.elasticsearch.xcontent.XContentFactory;
|
|
|
import org.elasticsearch.xpack.core.common.ResultsAndErrors;
|
|
|
+import org.elasticsearch.xpack.core.security.action.apikey.ApiKey;
|
|
|
import org.elasticsearch.xpack.core.security.action.profile.Profile;
|
|
|
import org.elasticsearch.xpack.core.security.action.profile.SuggestProfilesRequest;
|
|
|
import org.elasticsearch.xpack.core.security.action.profile.SuggestProfilesRequestTests;
|
|
@@ -80,7 +83,9 @@ import org.elasticsearch.xpack.core.security.authc.DomainConfig;
|
|
|
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
|
|
import org.elasticsearch.xpack.core.security.authc.RealmDomain;
|
|
|
import org.elasticsearch.xpack.core.security.authc.Subject;
|
|
|
+import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
|
|
import org.elasticsearch.xpack.core.security.user.User;
|
|
|
+import org.elasticsearch.xpack.security.authc.Realms;
|
|
|
import org.elasticsearch.xpack.security.profile.ProfileDocument.ProfileDocumentUser;
|
|
|
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
|
|
import org.elasticsearch.xpack.security.support.SecuritySystemIndices;
|
|
@@ -97,6 +102,7 @@ import java.util.Arrays;
|
|
|
import java.util.Base64;
|
|
|
import java.util.Collection;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Locale;
|
|
|
import java.util.Map;
|
|
@@ -117,13 +123,18 @@ import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SEC
|
|
|
import static org.hamcrest.Matchers.anEmptyMap;
|
|
|
import static org.hamcrest.Matchers.arrayContaining;
|
|
|
import static org.hamcrest.Matchers.arrayWithSize;
|
|
|
+import static org.hamcrest.Matchers.contains;
|
|
|
+import static org.hamcrest.Matchers.containsInAnyOrder;
|
|
|
import static org.hamcrest.Matchers.containsString;
|
|
|
+import static org.hamcrest.Matchers.either;
|
|
|
import static org.hamcrest.Matchers.empty;
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
|
import static org.hamcrest.Matchers.hasItem;
|
|
|
import static org.hamcrest.Matchers.instanceOf;
|
|
|
import static org.hamcrest.Matchers.is;
|
|
|
+import static org.hamcrest.Matchers.iterableWithSize;
|
|
|
import static org.hamcrest.Matchers.not;
|
|
|
+import static org.hamcrest.Matchers.nullValue;
|
|
|
import static org.hamcrest.Matchers.sameInstance;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
@@ -175,6 +186,7 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
private Client client;
|
|
|
private SecurityIndexManager profileIndex;
|
|
|
private ProfileService profileService;
|
|
|
+ Function<RealmConfig.RealmIdentifier, Authentication.RealmRef> realmRefLookup;
|
|
|
private boolean useProfileOrigin;
|
|
|
|
|
|
@Before
|
|
@@ -206,6 +218,10 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
when(featureService.clusterHasFeature(any(), eq(SecuritySystemIndices.SECURITY_PROFILE_ORIGIN_FEATURE))).thenReturn(
|
|
|
useProfileOrigin
|
|
|
);
|
|
|
+ realmRefLookup = realmIdentifier -> null;
|
|
|
+ Realms realms = mock(Realms.class);
|
|
|
+ when(realms.getDomainConfig(anyString())).then(args -> new DomainConfig(args.getArgument(0), Set.of(), false, null));
|
|
|
+ when(realms.getRealmRef(any(RealmConfig.RealmIdentifier.class))).then(args -> realmRefLookup.apply(args.getArgument(0)));
|
|
|
this.profileService = new ProfileService(
|
|
|
Settings.EMPTY,
|
|
|
Clock.systemUTC(),
|
|
@@ -213,7 +229,7 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
profileIndex,
|
|
|
clusterService,
|
|
|
featureService,
|
|
|
- name -> new DomainConfig(name, Set.of(), false, null)
|
|
|
+ realms
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -486,6 +502,8 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testLiteralUsernameWillThrowOnDuplicate() throws IOException {
|
|
|
final Subject subject = new Subject(AuthenticationTestHelper.randomUser(), AuthenticationTestHelper.randomRealmRef(true));
|
|
|
+ Realms realms = mock(Realms.class);
|
|
|
+ when(realms.getDomainConfig(anyString())).then(args -> new DomainConfig(args.getArgument(0), Set.of(), true, "suffix"));
|
|
|
final ProfileService service = new ProfileService(
|
|
|
Settings.EMPTY,
|
|
|
Clock.systemUTC(),
|
|
@@ -493,7 +511,7 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
profileIndex,
|
|
|
mock(ClusterService.class),
|
|
|
mock(FeatureService.class),
|
|
|
- domainName -> new DomainConfig(domainName, Set.of(), true, "suffix")
|
|
|
+ realms
|
|
|
);
|
|
|
final PlainActionFuture<Profile> future = new PlainActionFuture<>();
|
|
|
service.maybeIncrementDifferentiatorAndCreateNewProfile(
|
|
@@ -648,6 +666,15 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
}
|
|
|
|
|
|
public void testActivateProfileWithDifferentUidFormats() throws IOException {
|
|
|
+ Realms realms = mock(Realms.class);
|
|
|
+ when(realms.getDomainConfig(anyString())).then(args -> {
|
|
|
+ String domainName = args.getArgument(0);
|
|
|
+ if (domainName.startsWith("hash")) {
|
|
|
+ return new DomainConfig(domainName, Set.of(), false, null);
|
|
|
+ } else {
|
|
|
+ return new DomainConfig(domainName, Set.of(), true, "suffix");
|
|
|
+ }
|
|
|
+ });
|
|
|
final ProfileService service = spy(
|
|
|
new ProfileService(
|
|
|
Settings.EMPTY,
|
|
@@ -656,13 +683,7 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
profileIndex,
|
|
|
mock(ClusterService.class),
|
|
|
mock(FeatureService.class),
|
|
|
- domainName -> {
|
|
|
- if (domainName.startsWith("hash")) {
|
|
|
- return new DomainConfig(domainName, Set.of(), false, null);
|
|
|
- } else {
|
|
|
- return new DomainConfig(domainName, Set.of(), true, "suffix");
|
|
|
- }
|
|
|
- }
|
|
|
+ realms
|
|
|
)
|
|
|
);
|
|
|
|
|
@@ -1061,6 +1082,318 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
assertThat(future.actionGet(), equalTo(Map.of("total", 0L, "enabled", 0L, "recent", 0L)));
|
|
|
}
|
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public void testProfileSearchForApiKeyOwnerWithoutDomain() throws Exception {
|
|
|
+ String realmName = "realmName_" + randomAlphaOfLength(8);
|
|
|
+ String realmType = "realmType_" + randomAlphaOfLength(8);
|
|
|
+ String username = "username_" + randomAlphaOfLength(8);
|
|
|
+ List<ApiKey> apiKeys = List.of(createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), username, realmName, realmType));
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), is(realmName));
|
|
|
+ assertThat(realmIdentifier.getType(), is(realmType));
|
|
|
+ return new Authentication.RealmRef(realmName, realmType, "nodeName_" + randomAlphaOfLength(8));
|
|
|
+ };
|
|
|
+ MultiSearchResponse.Item[] responseItems = new MultiSearchResponse.Item[1];
|
|
|
+ responseItems[0] = new MultiSearchResponse.Item(new TestEmptySearchResponse(), null);
|
|
|
+ MultiSearchResponse emptyMultiSearchResponse = new MultiSearchResponse(responseItems, randomNonNegativeLong());
|
|
|
+ try {
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ assertThat(
|
|
|
+ threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
|
|
|
+ equalTo(useProfileOrigin ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
|
|
|
+ );
|
|
|
+ MultiSearchRequest multiSearchRequest = (MultiSearchRequest) invocation.getArguments()[1];
|
|
|
+ assertThat(multiSearchRequest.requests(), iterableWithSize(1));
|
|
|
+ assertThat(multiSearchRequest.requests().get(0).source().query(), instanceOf(BoolQueryBuilder.class));
|
|
|
+ assertThat(((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(), iterableWithSize(3));
|
|
|
+ assertThat(
|
|
|
+ ((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(),
|
|
|
+ containsInAnyOrder(
|
|
|
+ new TermQueryBuilder("user_profile.user.username.keyword", username),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.type", realmType),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.name", realmName)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onResponse(emptyMultiSearchResponse);
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ Collection<String> profileUids = listener.get();
|
|
|
+ assertThat(profileUids, iterableWithSize(1));
|
|
|
+ assertThat(profileUids.iterator().next(), nullValue());
|
|
|
+ } finally {
|
|
|
+ emptyMultiSearchResponse.decRef();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public void testProfileSearchForApiKeyOwnerWithDomain() throws Exception {
|
|
|
+ String realmName = "realmName_" + randomAlphaOfLength(8);
|
|
|
+ String realmType = "realmType_" + randomAlphaOfLength(8);
|
|
|
+ String username = "username_" + randomAlphaOfLength(8);
|
|
|
+ int domainSize = randomIntBetween(1, 3);
|
|
|
+ Set<RealmConfig.RealmIdentifier> domain = new HashSet<>(domainSize + 1);
|
|
|
+ domain.add(new RealmConfig.RealmIdentifier(realmType, realmName));
|
|
|
+ for (int i = 0; i < domainSize; i++) {
|
|
|
+ domain.add(new RealmConfig.RealmIdentifier("realmTypeFromDomain_" + i, "realmNameFromDomain_" + i));
|
|
|
+ }
|
|
|
+ RealmDomain realmDomain = new RealmDomain("domainName_ " + randomAlphaOfLength(8), domain);
|
|
|
+ List<ApiKey> apiKeys = List.of(createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), username, realmName, realmType));
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), is(realmName));
|
|
|
+ assertThat(realmIdentifier.getType(), is(realmType));
|
|
|
+ return new Authentication.RealmRef(realmName, realmType, "nodeName_" + randomAlphaOfLength(8), realmDomain);
|
|
|
+ };
|
|
|
+ MultiSearchResponse.Item[] responseItems = new MultiSearchResponse.Item[1];
|
|
|
+ responseItems[0] = new MultiSearchResponse.Item(new TestEmptySearchResponse(), null);
|
|
|
+ MultiSearchResponse emptyMultiSearchResponse = new MultiSearchResponse(responseItems, randomNonNegativeLong());
|
|
|
+ try {
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ assertThat(
|
|
|
+ threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
|
|
|
+ equalTo(useProfileOrigin ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
|
|
|
+ );
|
|
|
+ MultiSearchRequest multiSearchRequest = (MultiSearchRequest) invocation.getArguments()[1];
|
|
|
+ assertThat(multiSearchRequest.requests(), iterableWithSize(1));
|
|
|
+ assertThat(multiSearchRequest.requests().get(0).source().query(), instanceOf(BoolQueryBuilder.class));
|
|
|
+ assertThat(((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(), iterableWithSize(1));
|
|
|
+ assertThat(
|
|
|
+ ((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(),
|
|
|
+ contains(new TermQueryBuilder("user_profile.user.username.keyword", username))
|
|
|
+ );
|
|
|
+ assertThat(
|
|
|
+ ((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).should(),
|
|
|
+ iterableWithSize(domain.size())
|
|
|
+ );
|
|
|
+ assertThat(((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).minimumShouldMatch(), is("1"));
|
|
|
+ for (RealmConfig.RealmIdentifier domainRealmIdentifier : domain) {
|
|
|
+ BoolQueryBuilder realmDomainBoolQueryBuilder = new BoolQueryBuilder();
|
|
|
+ realmDomainBoolQueryBuilder.filter()
|
|
|
+ .add(new TermQueryBuilder("user_profile.user.realm.type", domainRealmIdentifier.getType()));
|
|
|
+ realmDomainBoolQueryBuilder.filter()
|
|
|
+ .add(new TermQueryBuilder("user_profile.user.realm.name", domainRealmIdentifier.getName()));
|
|
|
+ assertThat(
|
|
|
+ ((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).should(),
|
|
|
+ hasItem(realmDomainBoolQueryBuilder)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onResponse(emptyMultiSearchResponse);
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ Collection<String> profileUids = listener.get();
|
|
|
+ assertThat(profileUids, iterableWithSize(1));
|
|
|
+ assertThat(profileUids.iterator().next(), nullValue());
|
|
|
+ } finally {
|
|
|
+ emptyMultiSearchResponse.decRef();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public void testProfileSearchForOwnerOfMultipleApiKeys() throws Exception {
|
|
|
+ String realmName = "realmName_" + randomAlphaOfLength(8);
|
|
|
+ String realmType = "realmType_" + randomAlphaOfLength(8);
|
|
|
+ String username = "username_" + randomAlphaOfLength(8);
|
|
|
+ int apiKeyCount = randomIntBetween(2, 6);
|
|
|
+ List<ApiKey> apiKeys = new ArrayList<>(apiKeyCount);
|
|
|
+ for (int i = 0; i < apiKeyCount; i++) {
|
|
|
+ // all keys have the same owner
|
|
|
+ apiKeys.add(createApiKeyForOwner("keyId_" + i, username, realmName, realmType));
|
|
|
+ }
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), is(realmName));
|
|
|
+ assertThat(realmIdentifier.getType(), is(realmType));
|
|
|
+ return new Authentication.RealmRef(realmName, realmType, "nodeName");
|
|
|
+ };
|
|
|
+ MultiSearchResponse.Item[] responseItems = new MultiSearchResponse.Item[1];
|
|
|
+ responseItems[0] = new MultiSearchResponse.Item(new TestEmptySearchResponse(), null);
|
|
|
+ MultiSearchResponse emptyMultiSearchResponse = new MultiSearchResponse(responseItems, randomNonNegativeLong());
|
|
|
+ try {
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ assertThat(
|
|
|
+ threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
|
|
|
+ equalTo(useProfileOrigin ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
|
|
|
+ );
|
|
|
+ MultiSearchRequest multiSearchRequest = (MultiSearchRequest) invocation.getArguments()[1];
|
|
|
+ // a single search request for a single owner of multiple keys
|
|
|
+ assertThat(multiSearchRequest.requests(), iterableWithSize(1));
|
|
|
+ assertThat(multiSearchRequest.requests().get(0).source().query(), instanceOf(BoolQueryBuilder.class));
|
|
|
+ assertThat(((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(), iterableWithSize(3));
|
|
|
+ assertThat(
|
|
|
+ ((BoolQueryBuilder) multiSearchRequest.requests().get(0).source().query()).filter(),
|
|
|
+ containsInAnyOrder(
|
|
|
+ new TermQueryBuilder("user_profile.user.username.keyword", username),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.type", realmType),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.name", realmName)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onResponse(emptyMultiSearchResponse);
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ Collection<String> profileUids = listener.get();
|
|
|
+ assertThat(profileUids, iterableWithSize(apiKeyCount));
|
|
|
+ var profileUidsIterator = profileUids.iterator();
|
|
|
+ while (profileUidsIterator.hasNext()) {
|
|
|
+ assertThat(profileUidsIterator.next(), nullValue());
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ emptyMultiSearchResponse.decRef();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testProfileSearchErrorForApiKeyOwner() {
|
|
|
+ // 2 keys with different owners
|
|
|
+ List<ApiKey> apiKeys = List.of(
|
|
|
+ createApiKeyForOwner("keyId_0", "username_0", "realmName_0", "realmType_0"),
|
|
|
+ createApiKeyForOwner("keyId_1", "username_1", "realmName_1", "realmType_1")
|
|
|
+ );
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), either(is("realmName_0")).or(is("realmName_1")));
|
|
|
+ assertThat(realmIdentifier.getType(), either(is("realmType_0")).or(is("realmType_1")));
|
|
|
+ return new Authentication.RealmRef(realmIdentifier.getName(), realmIdentifier.getType(), "nodeName");
|
|
|
+ };
|
|
|
+ MultiSearchResponse.Item[] responseItems = new MultiSearchResponse.Item[2];
|
|
|
+ // one search request (for one of the key owner) fails
|
|
|
+ if (randomBoolean()) {
|
|
|
+ responseItems[0] = new MultiSearchResponse.Item(new TestEmptySearchResponse(), null);
|
|
|
+ responseItems[1] = new MultiSearchResponse.Item(null, new Exception("test search failure"));
|
|
|
+ } else {
|
|
|
+ responseItems[0] = new MultiSearchResponse.Item(null, new Exception("test search failure"));
|
|
|
+ responseItems[1] = new MultiSearchResponse.Item(new TestEmptySearchResponse(), null);
|
|
|
+ }
|
|
|
+ MultiSearchResponse multiSearchResponseWithError = new MultiSearchResponse(responseItems, randomNonNegativeLong());
|
|
|
+ try {
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ assertThat(
|
|
|
+ threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
|
|
|
+ equalTo(useProfileOrigin ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
|
|
|
+ );
|
|
|
+ // a single search request for a single owner of multiple keys
|
|
|
+ MultiSearchRequest multiSearchRequest = (MultiSearchRequest) invocation.getArguments()[1];
|
|
|
+ // 2 search requests for the 2 Api key owners
|
|
|
+ assertThat(multiSearchRequest.requests(), iterableWithSize(2));
|
|
|
+ for (int i = 0; i < 2; i++) {
|
|
|
+ assertThat(multiSearchRequest.requests().get(i).source().query(), instanceOf(BoolQueryBuilder.class));
|
|
|
+ assertThat(((BoolQueryBuilder) multiSearchRequest.requests().get(i).source().query()).filter(), iterableWithSize(3));
|
|
|
+ List<QueryBuilder> filters = ((BoolQueryBuilder) multiSearchRequest.requests().get(i).source().query()).filter();
|
|
|
+ assertThat(
|
|
|
+ filters,
|
|
|
+ either(
|
|
|
+ Matchers.<QueryBuilder>containsInAnyOrder(
|
|
|
+ new TermQueryBuilder("user_profile.user.username.keyword", "username_1"),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.type", "realmType_1"),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.name", "realmName_1")
|
|
|
+ )
|
|
|
+ ).or(
|
|
|
+ Matchers.<QueryBuilder>containsInAnyOrder(
|
|
|
+ new TermQueryBuilder("user_profile.user.username.keyword", "username_0"),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.type", "realmType_0"),
|
|
|
+ new TermQueryBuilder("user_profile.user.realm.name", "realmName_0")
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onResponse(multiSearchResponseWithError);
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ ExecutionException e = expectThrows(ExecutionException.class, () -> listener.get());
|
|
|
+ assertThat(
|
|
|
+ e.getMessage(),
|
|
|
+ containsString("failed to retrieve profile for users. please retry without fetching profile uid (with_profile_uid=false)")
|
|
|
+ );
|
|
|
+ } finally {
|
|
|
+ multiSearchResponseWithError.decRef();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testUnclearApiKeyOwnersAreIgnoredWhenRetrievingProfiles() throws Exception {
|
|
|
+ String realmName = "realmName_" + randomAlphaOfLength(8);
|
|
|
+ String realmType = "realmType_" + randomAlphaOfLength(8);
|
|
|
+ String username = "username_" + randomAlphaOfLength(8);
|
|
|
+ List<ApiKey> apiKeys = List.of(
|
|
|
+ // null username
|
|
|
+ createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), null, randomAlphaOfLength(4), randomAlphaOfLength(4)),
|
|
|
+ // null realm name
|
|
|
+ createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), randomAlphaOfLength(4), null, randomAlphaOfLength(4)),
|
|
|
+ // null realm type
|
|
|
+ createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), randomAlphaOfLength(4), randomAlphaOfLength(4), null),
|
|
|
+ // the realm does not exist
|
|
|
+ createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), username, realmName, realmType)
|
|
|
+ );
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), is(realmName));
|
|
|
+ assertThat(realmIdentifier.getType(), is(realmType));
|
|
|
+ // realm not configured
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onFailure(new Exception("test failed, code should not be reachable"));
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ Collection<String> profileUids = listener.get();
|
|
|
+ assertThat(profileUids, iterableWithSize(4));
|
|
|
+ assertThat(profileUids, contains(nullValue(), nullValue(), nullValue(), nullValue()));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testProfilesIndexMissingOrUnavailableWhenRetrievingProfilesOfApiKeyOwners() throws Exception {
|
|
|
+ // profiles index missing
|
|
|
+ when(this.profileIndex.indexExists()).thenReturn(false);
|
|
|
+ String realmName = "realmName_" + randomAlphaOfLength(8);
|
|
|
+ String realmType = "realmType_" + randomAlphaOfLength(8);
|
|
|
+ String username = "username_" + randomAlphaOfLength(8);
|
|
|
+ List<ApiKey> apiKeys = List.of(createApiKeyForOwner("keyId_" + randomAlphaOfLength(8), username, realmName, realmType));
|
|
|
+ realmRefLookup = realmIdentifier -> {
|
|
|
+ assertThat(realmIdentifier.getName(), is(realmName));
|
|
|
+ assertThat(realmIdentifier.getType(), is(realmType));
|
|
|
+ return new Authentication.RealmRef(realmName, realmType, "nodeName_" + randomAlphaOfLength(8));
|
|
|
+ };
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ var listener = (ActionListener<MultiSearchResponse>) invocation.getArgument(2);
|
|
|
+ listener.onFailure(new Exception("test failed, code should not be reachable"));
|
|
|
+ return null;
|
|
|
+ }).when(client).execute(eq(TransportMultiSearchAction.TYPE), any(MultiSearchRequest.class), anyActionListener());
|
|
|
+ when(client.prepareMultiSearch()).thenReturn(new MultiSearchRequestBuilder(client));
|
|
|
+ PlainActionFuture<Collection<String>> listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ Collection<String> profileUids = listener.get();
|
|
|
+ assertThat(profileUids, nullValue());
|
|
|
+ // profiles index unavailable
|
|
|
+ when(this.profileIndex.indexExists()).thenReturn(true);
|
|
|
+ when(this.profileIndex.isAvailable(any())).thenReturn(false);
|
|
|
+ when(this.profileIndex.getUnavailableReason(any())).thenReturn(new ElasticsearchException("test unavailable"));
|
|
|
+ listener = new PlainActionFuture<>();
|
|
|
+ profileService.resolveProfileUidsForApiKeys(apiKeys, listener);
|
|
|
+ PlainActionFuture<Collection<String>> finalListener = listener;
|
|
|
+ ExecutionException e = expectThrows(ExecutionException.class, () -> finalListener.get());
|
|
|
+ assertThat(e.getMessage(), containsString("test unavailable"));
|
|
|
+ }
|
|
|
+
|
|
|
record SampleDocumentParameter(String uid, String username, List<String> roles, long lastSynchronized) {}
|
|
|
|
|
|
private void mockMultiGetRequest(List<SampleDocumentParameter> sampleDocumentParameters) {
|
|
@@ -1123,4 +1456,59 @@ public class ProfileServiceTests extends ESTestCase {
|
|
|
null
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ private static ApiKey createApiKeyForOwner(String apiKeyId, String username, String realmName, String realmType) {
|
|
|
+ return new ApiKey(
|
|
|
+ randomAlphaOfLength(4),
|
|
|
+ apiKeyId,
|
|
|
+ randomFrom(ApiKey.Type.values()),
|
|
|
+ Instant.now(),
|
|
|
+ Instant.now().plusSeconds(3600),
|
|
|
+ false,
|
|
|
+ null,
|
|
|
+ username,
|
|
|
+ realmName,
|
|
|
+ realmType,
|
|
|
+ null,
|
|
|
+ List.of(
|
|
|
+ new RoleDescriptor(
|
|
|
+ randomAlphaOfLength(5),
|
|
|
+ new String[] { "manage_index_template" },
|
|
|
+ new RoleDescriptor.IndicesPrivileges[] {
|
|
|
+ RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("rad").build() },
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ Map.of("_key", "value"),
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ null
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class TestEmptySearchResponse extends SearchResponse {
|
|
|
+
|
|
|
+ TestEmptySearchResponse() {
|
|
|
+ super(
|
|
|
+ SearchHits.EMPTY_WITH_TOTAL_HITS,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ false,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ 1,
|
|
|
+ null,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0L,
|
|
|
+ ShardSearchFailure.EMPTY_ARRAY,
|
|
|
+ Clusters.EMPTY,
|
|
|
+ null
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|