From 98a308352a91dc6225f857e7b62cabffb1025bd6 Mon Sep 17 00:00:00 2001 From: jaymode Date: Thu, 14 Apr 2016 12:26:24 -0400 Subject: [PATCH] security: resolve date match expressions for authorization Elasticsearch supports the concept of date match expressions for index names and the authorization service was trying to authorize the names without resolving them to their concrete index names. This change now resolves these names Closes elastic/elasticsearch#1983 Original commit: elastic/x-pack-elasticsearch@3c6baa8e83a57f9c8150fa6070943178eb99b36d --- .../authz/InternalAuthorizationService.java | 8 +- .../DefaultIndicesAndAliasesResolver.java | 96 ++++++++++----- .../DateMathExpressionIntegTests.java | 109 ++++++++++++++++++ .../InternalAuthorizationServiceTests.java | 15 ++- .../DefaultIndicesResolverTests.java | 85 +++++++++++++- 5 files changed, 274 insertions(+), 39 deletions(-) create mode 100644 elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/InternalAuthorizationService.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/InternalAuthorizationService.java index e92bc8ecf11..c54d929a375 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/InternalAuthorizationService.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/InternalAuthorizationService.java @@ -12,6 +12,7 @@ import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.search.ClearScrollAction; import org.elasticsearch.action.search.SearchScrollAction; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasOrIndex; @@ -79,13 +80,14 @@ public class InternalAuthorizationService extends AbstractComponent implements A @Inject public InternalAuthorizationService(Settings settings, RolesStore rolesStore, ClusterService clusterService, - AuditTrail auditTrail, AuthenticationFailureHandler authcFailureHandler, ThreadPool threadPool) { + AuditTrail auditTrail, AuthenticationFailureHandler authcFailureHandler, + ThreadPool threadPool, IndexNameExpressionResolver nameExpressionResolver) { super(settings); this.rolesStore = rolesStore; this.clusterService = clusterService; this.auditTrail = auditTrail; - this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[]{ - new DefaultIndicesAndAliasesResolver(this) + this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[] { + new DefaultIndicesAndAliasesResolver(this, nameExpressionResolver) }; this.authcFailureHandler = authcFailureHandler; this.threadContext = threadPool.getThreadContext(); diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java index 2732d7c72de..9f575cafd9f 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java @@ -7,7 +7,9 @@ package org.elasticsearch.shield.authz.indicesresolver; import org.elasticsearch.action.AliasesRequest; import org.elasticsearch.action.CompositeIndicesRequest; +import org.elasticsearch.action.DocumentRequest; import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.metadata.AliasOrIndex; @@ -35,9 +37,11 @@ import java.util.SortedMap; public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolver { private final AuthorizationService authzService; + private final IndexNameExpressionResolver nameExpressionResolver; - public DefaultIndicesAndAliasesResolver(AuthorizationService authzService) { + public DefaultIndicesAndAliasesResolver(AuthorizationService authzService, IndexNameExpressionResolver nameExpressionResolver) { this.authzService = authzService; + this.nameExpressionResolver = nameExpressionResolver; } @Override @@ -80,25 +84,30 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv indices = Collections.singleton(((PutMappingRequest) indicesRequest).getConcreteIndex().getName()); assert indicesRequest.indices() == null || indicesRequest.indices().length == 0 : "indices are: " + Arrays.toString(indicesRequest.indices()); // Arrays.toString() can handle null values - all good - } else { - if (indicesRequest.indicesOptions().expandWildcardsOpen() || indicesRequest.indicesOptions().expandWildcardsClosed()) { - if (indicesRequest instanceof IndicesRequest.Replaceable) { - List authorizedIndices = replaceWildcardsWithAuthorizedIndices(indicesRequest.indices(), - indicesRequest.indicesOptions(), - metaData, authzService.authorizedIndicesAndAliases(user, action)); - ((IndicesRequest.Replaceable) indicesRequest).indices(authorizedIndices.toArray(new String[authorizedIndices.size()])); - } else { - assert !containsWildcards(indicesRequest) : - "There are no external requests known to support wildcards that don't support replacing their indices"; - - //NOTE: shard level requests do support wildcards (as they hold the original indices options) but don't support - // replacing their indices. - //That is fine though because they never contain wildcards, as they get replaced as part of the authorization of their - //corresponding parent request on the coordinating node. Hence wildcards don't need to get replaced nor exploded for - // shard level requests. - } - } + } else if (indicesRequest instanceof IndicesRequest.Replaceable) { + IndicesRequest.Replaceable replaceable = (IndicesRequest.Replaceable) indicesRequest; + final boolean replaceWildcards = indicesRequest.indicesOptions().expandWildcardsOpen() + || indicesRequest.indicesOptions().expandWildcardsClosed(); + List authorizedIndices = replaceWildcardsWithAuthorizedIndices(indicesRequest.indices(), + indicesRequest.indicesOptions(), + metaData, + authzService.authorizedIndicesAndAliases(user, action), + replaceWildcards); + replaceable.indices(authorizedIndices.toArray(new String[authorizedIndices.size()])); indices = Sets.newHashSet(indicesRequest.indices()); + } else { + assert !containsWildcards(indicesRequest) : + "There are no external requests known to support wildcards that don't support replacing their indices"; + //NOTE: shard level requests do support wildcards (as they hold the original indices options) but don't support + // replacing their indices. + //That is fine though because they never contain wildcards, as they get replaced as part of the authorization of their + //corresponding parent request on the coordinating node. Hence wildcards don't need to get replaced nor exploded for + // shard level requests. + List resolvedNames = new ArrayList<>(); + for (String name : indicesRequest.indices()) { + resolvedNames.add(nameExpressionResolver.resolveDateMathExpression(name)); + } + indices = Sets.newHashSet(resolvedNames); } if (indicesRequest instanceof AliasesRequest) { @@ -177,10 +186,14 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv } private List replaceWildcardsWithAuthorizedIndices(String[] indices, IndicesOptions indicesOptions, MetaData metaData, - List authorizedIndices) { + List authorizedIndices, boolean replaceWildcards) { // check for all and return list of authorized indices if (IndexNameExpressionResolver.isAllIndices(indicesList(indices))) { + if (replaceWildcards == false) { + // if we cannot replace wildcards, then we should not set all indices + return throwExceptionIfNoIndicesWereResolved(indices, null); + } List visibleIndices = new ArrayList<>(); for (String authorizedIndex : authorizedIndices) { if (isIndexVisible(authorizedIndex, indicesOptions, metaData)) { @@ -214,7 +227,7 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv aliasOrIndex = index; } - if (Regex.isSimpleMatchPattern(aliasOrIndex)) { + if (replaceWildcards && Regex.isSimpleMatchPattern(aliasOrIndex)) { for (String authorizedIndex : authorizedIndices) { if (Regex.simpleMatch(aliasOrIndex, authorizedIndex)) { if (minus) { @@ -227,15 +240,32 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv } } } else { - //MetaData#convertFromWildcards checks if the index exists here and throws IndexNotFoundException if not (based on - // ignore_unavailable). - //Do nothing as if the index is missing but the user is not authorized to it an AuthorizationException will be thrown. - //If the index is missing and the user is authorized to it, core will throw IndexNotFoundException later on. - //There is no problem with deferring this as we are dealing with an explicit name, not with wildcards. - if (minus) { - finalIndices.remove(aliasOrIndex); + // we always need to check for date math expressions + String dateMathName = nameExpressionResolver.resolveDateMathExpression(aliasOrIndex); + // we can use != here to compare strings since the name expression resolver returns the same instance, but add an assert + // to ensure we catch this if it changes + if (dateMathName != aliasOrIndex) { + assert dateMathName.equals(aliasOrIndex) == false; + if (authorizedIndices.contains(dateMathName)) { + if (minus) { + finalIndices.remove(dateMathName); + } else { + if (isIndexVisible(dateMathName, indicesOptions, metaData, true)) { + finalIndices.add(dateMathName); + } + } + } } else { - finalIndices.add(aliasOrIndex); + //MetaData#convertFromWildcards checks if the index exists here and throws IndexNotFoundException if not (based on + // ignore_unavailable). + //Do nothing as if the index is missing but the user is not authorized to it an AuthorizationException will be thrown. + //If the index is missing and the user is authorized to it, core will throw IndexNotFoundException later on. + //There is no problem with deferring this as we are dealing with an explicit name, not with wildcards. + if (minus) { + finalIndices.remove(aliasOrIndex); + } else { + finalIndices.add(aliasOrIndex); + } } } } @@ -258,6 +288,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv } private static boolean isIndexVisible(String index, IndicesOptions indicesOptions, MetaData metaData) { + return isIndexVisible(index, indicesOptions, metaData, false); + } + + private static boolean isIndexVisible(String index, IndicesOptions indicesOptions, MetaData metaData, boolean dateMathExpression) { if (metaData.hasConcreteIndex(index)) { IndexMetaData indexMetaData = metaData.index(index); if (indexMetaData == null) { @@ -265,10 +299,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv //complicated to support those options with aliases pointing to multiple indices... return true; } - if (indexMetaData.getState() == IndexMetaData.State.CLOSE && indicesOptions.expandWildcardsClosed()) { + if (indexMetaData.getState() == IndexMetaData.State.CLOSE && (indicesOptions.expandWildcardsClosed() || dateMathExpression)) { return true; } - if (indexMetaData.getState() == IndexMetaData.State.OPEN && indicesOptions.expandWildcardsOpen()) { + if (indexMetaData.getState() == IndexMetaData.State.OPEN && (indicesOptions.expandWildcardsOpen() || dateMathExpression)) { return true; } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java new file mode 100644 index 00000000000..5b626b3c2f8 --- /dev/null +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.integration; + +import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetResponse; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.MultiSearchResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.shield.authc.support.Hasher; +import org.elasticsearch.shield.authc.support.SecuredString; +import org.elasticsearch.test.ShieldIntegTestCase; + +import java.util.Collections; + +import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class DateMathExpressionIntegTests extends ShieldIntegTestCase { + + protected static final SecuredString USERS_PASSWD = new SecuredString("change_me".toCharArray()); + protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD)); + + @Override + protected String configUsers() { + return super.configUsers() + + "user1:" + USERS_PASSWD_HASHED + "\n"; + } + + @Override + protected String configUsersRoles() { + return super.configUsersRoles() + + "role1:user1\n"; + } + + @Override + protected String configRoles() { + return super.configRoles() + + "\nrole1:\n" + + " cluster: [ none ]\n" + + " indices:\n" + + " - names: 'datemath-*'\n" + + " privileges: [ ALL ]\n"; + } + + public void testDateMathExpressionsCanBeAuthorized() throws Exception { + final String expression = ""; + final String expectedIndexName = new IndexNameExpressionResolver(Settings.EMPTY).resolveDateMathExpression(expression); + final boolean refeshOnOperation = randomBoolean(); + Client client = client().filterWithHeader(Collections.singletonMap("Authorization", basicAuthHeaderValue("user1", USERS_PASSWD))); + + if (randomBoolean()) { + CreateIndexResponse response = client.admin().indices().prepareCreate(expression).get(); + assertThat(response.isAcknowledged(), is(true)); + } + IndexResponse response = client.prepareIndex(expression, "type").setSource("foo", "bar").setRefresh(refeshOnOperation).get(); + + assertThat(response.isCreated(), is(true)); + assertThat(response.getIndex(), containsString(expectedIndexName)); + + if (refeshOnOperation == false) { + client.admin().indices().prepareRefresh(expression).get(); + } + SearchResponse searchResponse = client.prepareSearch(expression) + .setQuery(QueryBuilders.matchAllQuery()) + .get(); + assertThat(searchResponse.getHits().getTotalHits(), is(1L)); + + MultiSearchResponse multiSearchResponse = client.prepareMultiSearch() + .add(client.prepareSearch(expression).setQuery(QueryBuilders.matchAllQuery()).request()) + .get(); + assertThat(multiSearchResponse.getResponses()[0].getResponse().getHits().getTotalHits(), is(1L)); + + UpdateResponse updateResponse = client.prepareUpdate(expression, "type", response.getId()) + .setDoc("new", "field") + .setRefresh(refeshOnOperation) + .get(); + assertThat(updateResponse.isCreated(), is(false)); + + if (refeshOnOperation == false) { + client.admin().indices().prepareRefresh(expression).get(); + } + GetResponse getResponse = client.prepareGet(expression, "type", response.getId()).setFetchSource(true).get(); + assertThat(getResponse.isExists(), is(true)); + assertThat(getResponse.getSourceAsMap().get("foo").toString(), is("bar")); + assertThat(getResponse.getSourceAsMap().get("new").toString(), is("field")); + + // multi get doesn't support expressions - this is probably a bug + MultiGetResponse multiGetResponse = client.prepareMultiGet() + .add(expression, "type", response.getId()) + .get(); + assertThat(multiGetResponse.getResponses()[0].getFailure().getMessage(), is("no such index")); + + DeleteIndexResponse deleteIndexResponse = client.admin().indices().prepareDelete(expression).get(); + assertThat(deleteIndexResponse.isAcknowledged(), is(true)); + } + +} diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/InternalAuthorizationServiceTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/InternalAuthorizationServiceTests.java index d3a69df1381..69b07d08634 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/InternalAuthorizationServiceTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/InternalAuthorizationServiceTests.java @@ -45,6 +45,7 @@ import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasMetaData; @@ -82,6 +83,8 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.AdditionalAnswers.returnsFirstArg; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -105,8 +108,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase { threadPool = mock(ThreadPool.class); when(threadPool.getThreadContext()).thenReturn(threadContext); + IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); + when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService, - auditTrail, new DefaultAuthenticationFailureHandler(), threadPool); + auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); } @After @@ -349,8 +354,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase { TransportRequest request = new IndicesExistsRequest("b"); ClusterState state = mock(ClusterState.class); AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build()); + IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); + when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail, - new DefaultAuthenticationFailureHandler(), threadPool); + new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); when(clusterService.state()).thenReturn(state); @@ -377,9 +384,11 @@ public class InternalAuthorizationServiceTests extends ESTestCase { .put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false) .build()); User anonymousUser = AnonymousUser.INSTANCE; + IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); + when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); internalAuthorizationService = new InternalAuthorizationService( Settings.builder().put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(), - rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool); + rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); when(clusterService.state()).thenReturn(state); diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java index c3c3e40e8cd..62680cce34f 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java @@ -20,6 +20,7 @@ import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.Requests; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasAction; @@ -45,6 +46,7 @@ import org.junit.Before; import java.util.Set; +import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -61,6 +63,7 @@ public class DefaultIndicesResolverTests extends ESTestCase { private RolesStore rolesStore; private MetaData metaData; private DefaultIndicesAndAliasesResolver defaultIndicesResolver; + private IndexNameExpressionResolver indexNameExpressionResolver; @Before public void setup() { @@ -70,6 +73,7 @@ public class DefaultIndicesResolverTests extends ESTestCase { .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, randomIntBetween(0, 1)) .build(); + indexNameExpressionResolver = new IndexNameExpressionResolver(Settings.EMPTY); MetaData.Builder mdBuilder = MetaData.builder() .put(indexBuilder("foo").putAlias(AliasMetaData.builder("foofoobar")).settings(settings)) .put(indexBuilder("foobar").putAlias(AliasMetaData.builder("foofoobar")).settings(settings)) @@ -81,6 +85,7 @@ public class DefaultIndicesResolverTests extends ESTestCase { .put(indexBuilder("bar").settings(settings)) .put(indexBuilder("bar-closed").state(IndexMetaData.State.CLOSE).settings(settings)) .put(indexBuilder("bar2").settings(settings)) + .put(indexBuilder(indexNameExpressionResolver.resolveDateMathExpression("")).settings(settings)) .put(indexBuilder(ShieldTemplateService.SECURITY_INDEX_NAME).settings(settings)); metaData = mdBuilder.build(); @@ -97,8 +102,8 @@ public class DefaultIndicesResolverTests extends ESTestCase { when(state.metaData()).thenReturn(metaData); InternalAuthorizationService authzService = new InternalAuthorizationService(settings, rolesStore, clusterService, - mock(AuditTrail.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class)); - defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService); + mock(AuditTrail.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class), indexNameExpressionResolver); + defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService, indexNameExpressionResolver); } public void testResolveEmptyIndicesExpandWilcardsOpenAndClosed() { @@ -840,6 +845,82 @@ public class DefaultIndicesResolverTests extends ESTestCase { assertThat(indices, not(hasItem(ShieldTemplateService.SECURITY_INDEX_NAME))); } + public void testResolvingDateExpression() { + // the user isn't authorized so resolution should fail + SearchRequest request = new SearchRequest(""); + if (randomBoolean()) { + request.indicesOptions(IndicesOptions.strictSingleIndexNoExpandForbidClosed()); + } + try { + defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData); + fail("user is not authorized to see this index"); + } catch (IndexNotFoundException e) { + assertThat(e.getMessage(), is("no such index")); + } + + // make the user authorized + String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", + indexNameExpressionResolver.resolveDateMathExpression("")}; + when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); + + Set indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData); + assertThat(indices.size(), equalTo(1)); + assertThat(request.indices()[0], equalTo(indexNameExpressionResolver.resolveDateMathExpression(""))); + } + + public void testMissingDateExpression() { + SearchRequest request = new SearchRequest(""); + try { + defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData); + fail("index should not exist"); + } catch (IndexNotFoundException e) { + assertThat(e.getMessage(), is("no such index")); + } + } + + public void testAliasDateMathExpressionNotSupported() { + // make the user authorized + String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", + indexNameExpressionResolver.resolveDateMathExpression("")}; + when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); + GetAliasesRequest request = new GetAliasesRequest("").indices("foo", "foofoo"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all indices and aliases gets returned + String[] expectedIndices = new String[]{"", "foo", "foofoo"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + assertThat(request.indices(), arrayContainingInAnyOrder("foo", "foofoo")); + assertThat(request.aliases(), arrayContainingInAnyOrder("")); + } + + public void testCompositeRequestsCanResolveExpressions() { + // make the user authorized + String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", + indexNameExpressionResolver.resolveDateMathExpression("")}; + when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); + MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); + multiSearchRequest.add(new SearchRequest("")); + multiSearchRequest.add(new SearchRequest("bar")); + Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, multiSearchRequest, metaData); + + String resolvedName = indexNameExpressionResolver.resolveDateMathExpression(""); + String[] expectedIndices = new String[]{resolvedName, "bar"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + assertThat(multiSearchRequest.requests().get(0).indices(), arrayContaining(resolvedName)); + assertThat(multiSearchRequest.requests().get(1).indices(), arrayContaining("bar")); + + // multi get doesn't support replacing indices but we should authorize the proper indices + MultiGetRequest multiGetRequest = new MultiGetRequest(); + multiGetRequest.add("", "type", "1"); + multiGetRequest.add("bar", "type", "1"); + indices = defaultIndicesResolver.resolve(user, MultiGetAction.NAME, multiGetRequest, metaData); + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + assertThat(multiGetRequest.getItems().get(0).indices(), arrayContaining("")); + assertThat(multiGetRequest.getItems().get(1).indices(), arrayContaining("bar")); + } + // TODO with the removal of DeleteByQuery is there another way to test resolving a write action? private static IndexMetaData.Builder indexBuilder(String index) {