diff --git a/build-tools-internal/src/main/groovy/elasticsearch.run-ccs.gradle b/build-tools-internal/src/main/groovy/elasticsearch.run-ccs.gradle index 587c97d3476ea..07abe9a8e7633 100644 --- a/build-tools-internal/src/main/groovy/elasticsearch.run-ccs.gradle +++ b/build-tools-internal/src/main/groovy/elasticsearch.run-ccs.gradle @@ -48,6 +48,7 @@ tasks.register("run-ccs", RunTask) { useCluster queryingCluster doFirst { queryingCluster.get().getNodes().each { node -> + node.setting('cluster.remote.my_remote_cluster.tags', 'env-dev') if (proxyMode) { node.setting('cluster.remote.my_remote_cluster.mode', 'proxy') if (basicSecurityMode) { diff --git a/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java new file mode 100644 index 0000000000000..a3ca1528bd7e1 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch; + +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.transport.RemoteClusterService; + +import java.util.List; + +public interface FlatIndicesRequest extends IndicesRequest { + boolean requiresRewrite(); + + void indexExpressions(List indexExpressions); + + boolean checkRemote(List tags); + + record IndexExpression(String original, List rewritten) {} +} diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java index 2e24858d9781f..ec04bb9bd67c5 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.fieldcaps; +import org.elasticsearch.FlatIndicesRequest; import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.IndicesRequest; @@ -18,11 +19,13 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.Nullable; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.transport.RemoteClusterAware; +import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; @@ -30,11 +33,16 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -public final class FieldCapabilitiesRequest extends LegacyActionRequest implements IndicesRequest.Replaceable, ToXContentObject { +public final class FieldCapabilitiesRequest extends LegacyActionRequest + implements + FlatIndicesRequest, + IndicesRequest.Replaceable, + ToXContentObject { public static final String NAME = "field_caps_request"; public static final IndicesOptions DEFAULT_INDICES_OPTIONS = IndicesOptions.strictExpandOpenAndForbidClosed(); @@ -52,6 +60,8 @@ public final class FieldCapabilitiesRequest extends LegacyActionRequest implemen private QueryBuilder indexFilter; private Map runtimeFields = Collections.emptyMap(); private Long nowInMillis; + @Nullable + private List indexExpressions; public FieldCapabilitiesRequest(StreamInput in) throws IOException { super(in); @@ -323,4 +333,21 @@ public String getDescription() { } }; } + + @Override + public boolean requiresRewrite() { + return indexExpressions == null; + } + + @Override + public void indexExpressions(List indexExpressions) { + assert requiresRewrite(); + this.indexExpressions = indexExpressions; + indices(indexExpressions.stream().flatMap(indexExpression -> indexExpression.rewritten().stream()).toArray(String[]::new)); + } + + @Override + public boolean checkRemote(List tags) { + return true; + } } diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java index fda2df81d3f94..7d890cd71140e 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.search; +import org.elasticsearch.FlatIndicesRequest; import org.elasticsearch.TransportVersions; import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; @@ -31,6 +32,7 @@ import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.xcontent.ToXContent; import java.io.IOException; @@ -53,7 +55,11 @@ * @see Client#search(SearchRequest) * @see SearchResponse */ -public class SearchRequest extends LegacyActionRequest implements IndicesRequest.Replaceable, Rewriteable { +public class SearchRequest extends LegacyActionRequest + implements + FlatIndicesRequest, + IndicesRequest.Replaceable, + Rewriteable { public static final ToXContent.Params FORMAT_PARAMS = new ToXContent.MapParams(Collections.singletonMap("pretty", "false")); @@ -69,6 +75,11 @@ public class SearchRequest extends LegacyActionRequest implements IndicesRequest private SearchType searchType = SearchType.DEFAULT; private String[] indices = Strings.EMPTY_ARRAY; + // This will be a more complex thing in the real implementation -- a lucene expression instead of just a list of literals + private List routingTags = List.of(); + + @Nullable + private List indexExpressions; @Nullable private String routing; @@ -400,6 +411,11 @@ public SearchRequest indices(String... indices) { return this; } + public SearchRequest routingTags(List routingTags) { + this.routingTags = routingTags; + return this; + } + private static void validateIndices(String... indices) { Objects.requireNonNull(indices, "indices must not be null"); for (String index : indices) { @@ -853,4 +869,30 @@ public String toString() { + source + '}'; } + + @Override + public boolean requiresRewrite() { + return indexExpressions == null; + } + + @Override + public void indexExpressions(List indexExpressions) { + assert requiresRewrite(); + this.indexExpressions = indexExpressions; + indices(indexExpressions.stream().flatMap(indexExpression -> indexExpression.rewritten().stream()).toArray(String[]::new)); + } + + @Override + public boolean checkRemote(List tags) { + if (routingTags.isEmpty()) { + return true; // no routing requested, so no constraints + } + // if any tag in routingTags matches one in tags, return true + for (RemoteClusterService.RemoteTag tag : routingTags) { + if (tags.contains(tag)) { + return true; + } + } + return false; + } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index 1fbc8993cc5aa..f21f1231c76d0 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -367,6 +367,7 @@ public void apply(Settings value, Settings current, Settings previous) { TransportSearchAction.SHARD_COUNT_LIMIT_SETTING, TransportSearchAction.DEFAULT_PRE_FILTER_SHARD_SIZE, RemoteClusterService.REMOTE_CLUSTER_SKIP_UNAVAILABLE, + RemoteClusterService.REMOTE_CLUSTER_TAGS, SniffConnectionStrategy.REMOTE_CONNECTIONS_PER_CLUSTER, RemoteClusterService.REMOTE_INITIAL_CONNECTION_TIMEOUT_SETTING, RemoteClusterService.REMOTE_NODE_ATTRIBUTE, @@ -483,6 +484,7 @@ public void apply(Settings value, Settings current, Settings previous) { SearchService.ALLOW_EXPENSIVE_QUERIES, SearchService.CCS_VERSION_CHECK_SETTING, SearchService.CCS_COLLECT_TELEMETRY, + SearchService.FLAT_WORLD_ENABLED, SearchService.BATCHED_QUERY_PHASE, SearchService.PREWARMING_THRESHOLD_THREADPOOL_SIZE_FACTOR_POOL_SIZE, MultiBucketConsumerService.MAX_BUCKET_SETTING, diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java index 5eda47bc32354..ee67432e6050f 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java @@ -9,6 +9,8 @@ package org.elasticsearch.rest.action.search; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.search.SearchRequest; @@ -35,6 +37,7 @@ import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; +import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.usage.SearchUsageHolder; import org.elasticsearch.xcontent.XContentParser; @@ -63,6 +66,7 @@ public class RestSearchAction extends BaseRestHandler { public static final String TYPED_KEYS_PARAM = "typed_keys"; public static final String INCLUDE_NAMED_QUERIES_SCORE_PARAM = "include_named_queries_score"; public static final Set RESPONSE_PARAMS = Set.of(TYPED_KEYS_PARAM, TOTAL_HITS_AS_INT_PARAM, INCLUDE_NAMED_QUERIES_SCORE_PARAM); + private static final Logger log = LogManager.getLogger(RestSearchAction.class); private final SearchUsageHolder searchUsageHolder; private final Predicate clusterSupportsFeature; @@ -98,6 +102,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC client.threadPool().getThreadContext().setErrorTraceTransportHeader(request); } SearchRequest searchRequest = new SearchRequest(); + // access the BwC param, but just drop it // this might be set by old clients request.param("min_compatible_shard_node"); @@ -167,6 +172,16 @@ public static void parseSearchRequest( searchRequest.source(new SearchSourceBuilder()); } searchRequest.indices(Strings.splitStringByCommaToArray(request.param("index"))); + + var routingTags = request.param("routing_tags", null); + if (routingTags != null) { + searchRequest.routingTags( + Arrays.stream(Strings.splitStringByCommaToArray(routingTags)).map(RemoteClusterService.RemoteTag::fromString).toList() + ); + } else { + log.info("No routing tags"); + } + if (requestContentParser != null) { if (searchUsageHolder == null) { searchRequest.source().parseXContent(requestContentParser, true, clusterSupportsFeature); diff --git a/server/src/main/java/org/elasticsearch/search/SearchService.java b/server/src/main/java/org/elasticsearch/search/SearchService.java index af568c7b5d2cb..ddd39676b35cd 100644 --- a/server/src/main/java/org/elasticsearch/search/SearchService.java +++ b/server/src/main/java/org/elasticsearch/search/SearchService.java @@ -297,6 +297,8 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv Setting.Property.NodeScope ); + public static final Setting FLAT_WORLD_ENABLED = Setting.boolSetting("search.flat_world.enabled", false, Property.NodeScope); + private static final boolean BATCHED_QUERY_PHASE_FEATURE_FLAG = new FeatureFlag("batched_query_phase").isEnabled(); /** diff --git a/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java b/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java index 95e507f70d7a9..763faed028b10 100644 --- a/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java +++ b/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java @@ -56,6 +56,10 @@ protected static Set getEnabledRemoteClusters(final Settings settings) { return RemoteConnectionStrategy.getRemoteClusters(settings); } + protected static Map> getEnabledRemoteClustersWithTags(final Settings settings) { + return RemoteConnectionStrategy.getRemoteTags(settings); + } + /** * Check whether the index expression represents remote index or not. * The index name is assumed to be individual index (no commas) but can contain `-`, wildcards, diff --git a/server/src/main/java/org/elasticsearch/transport/RemoteClusterService.java b/server/src/main/java/org/elasticsearch/transport/RemoteClusterService.java index fdb597b47c137..0ac19dff6971c 100644 --- a/server/src/main/java/org/elasticsearch/transport/RemoteClusterService.java +++ b/server/src/main/java/org/elasticsearch/transport/RemoteClusterService.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -97,6 +98,33 @@ public final class RemoteClusterService extends RemoteClusterAware (ns, key) -> boolSetting(key, true, new RemoteConnectionEnabled<>(ns, key), Setting.Property.Dynamic, Setting.Property.NodeScope) ); + public record RemoteTag(String key, String value) { + public static RemoteTag fromString(String tag) { + if (tag == null || tag.isEmpty()) { + throw new IllegalArgumentException("Remote tag must not be null or empty"); + } + // - as a separator to simplify search path param parsing; won't be like this in the real implementation + int idx = tag.indexOf('-'); + if (idx < 0) { + return new RemoteTag(tag, ""); + } else { + return new RemoteTag(tag.substring(0, idx), tag.substring(idx + 1)); + } + } + } + + public static final Setting.AffixSetting> REMOTE_CLUSTER_TAGS = Setting.affixKeySetting( + "cluster.remote.", + "tags", + (ns, key) -> Setting.listSetting( + key, + Collections.emptyList(), + RemoteTag::fromString, + Setting.Property.Dynamic, + Setting.Property.NodeScope + ) + ); + public static final Setting.AffixSetting REMOTE_CLUSTER_PING_SCHEDULE = Setting.affixKeySetting( "cluster.remote.", "transport.ping_schedule", diff --git a/server/src/main/java/org/elasticsearch/transport/RemoteConnectionStrategy.java b/server/src/main/java/org/elasticsearch/transport/RemoteConnectionStrategy.java index a715797b97977..5b1925b0a20fa 100644 --- a/server/src/main/java/org/elasticsearch/transport/RemoteConnectionStrategy.java +++ b/server/src/main/java/org/elasticsearch/transport/RemoteConnectionStrategy.java @@ -189,6 +189,10 @@ static Set getRemoteClusters(Settings settings) { return enablementSettings.flatMap(s -> getClusterAlias(settings, s)).collect(Collectors.toSet()); } + static Map> getRemoteTags(Settings settings) { + return RemoteClusterService.REMOTE_CLUSTER_TAGS.getAsMap(settings); + } + public static boolean isConnectionEnabled(String clusterAlias, Settings settings) { ConnectionStrategy mode = REMOTE_CONNECTION_MODE.getConcreteSettingForNamespace(clusterAlias).get(settings); if (mode.equals(ConnectionStrategy.SNIFF)) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java index 2c831645d0e69..ef7446ae22ae5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java @@ -299,6 +299,11 @@ interface AuthorizedIndices { * Checks if an index-like resource name is authorized, for an action by a user. The resource might or might not exist. */ boolean check(String name, IndexComponentSelector selector); + + // Does not belong here + default boolean checkRemote(String remoteAlias) { + return false; + } } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java index ff39fd587dc3a..6057010c641cf 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java @@ -6,6 +6,9 @@ */ package org.elasticsearch.xpack.security.authz; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.FlatIndicesRequest; import org.elasticsearch.action.AliasesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -31,8 +34,10 @@ import org.elasticsearch.core.Tuple; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.search.SearchService; import org.elasticsearch.transport.NoSuchRemoteClusterException; import org.elasticsearch.transport.RemoteClusterAware; +import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.transport.RemoteConnectionStrategy; import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine; @@ -55,14 +60,18 @@ class IndicesAndAliasesResolver { + private static final Logger logger = LogManager.getLogger(IndicesAndAliasesResolver.class); + private final IndexNameExpressionResolver nameExpressionResolver; private final IndexAbstractionResolver indexAbstractionResolver; private final RemoteClusterResolver remoteClusterResolver; + private final boolean flatWorldEnabled; IndicesAndAliasesResolver(Settings settings, ClusterService clusterService, IndexNameExpressionResolver resolver) { this.nameExpressionResolver = resolver; this.indexAbstractionResolver = new IndexAbstractionResolver(resolver); this.remoteClusterResolver = new RemoteClusterResolver(settings, clusterService.getClusterSettings()); + this.flatWorldEnabled = SearchService.FLAT_WORLD_ENABLED.get(settings); } /** @@ -103,7 +112,6 @@ class IndicesAndAliasesResolver { * resolving wildcards. *

*/ - ResolvedIndices resolve( String action, TransportRequest request, @@ -124,9 +132,67 @@ ResolvedIndices resolve( if (request instanceof IndicesRequest == false) { throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be."); } + + if (flatWorldEnabled && request instanceof FlatIndicesRequest flatIndicesRequest && flatIndicesRequest.requiresRewrite()) { + rewriteFlatIndexExpression(flatIndicesRequest, authorizedIndices); + } + return resolveIndicesAndAliases(action, (IndicesRequest) request, projectMetadata, authorizedIndices); } + void rewriteFlatIndexExpression(FlatIndicesRequest request, AuthorizationEngine.AuthorizedIndices authorizedIndices) { + assert flatWorldEnabled && request.requiresRewrite(); + + Set remotes = remoteClusterResolver.clusters(); + Map> tags = remoteClusterResolver.tags(); + + logger.info("Remote available: {} with tags {}", remotes, tags); + // no remotes, nothing to rewrite... + if (remotes.isEmpty()) { + logger.info("No remotes, skipping..."); + return; + } + + var indices = request.indices(); + // empty indices actually means search everything so would need to also rewrite that + + var targetRemotes = new HashSet(); + for (var remote : remotes) { + List tagsForRemote = tags.get(remote); + logger.info("Remote [{}] has tags [{}]", remote, tagsForRemote); + // TODO routing also needs to apply to local + if (authorizedIndices.checkRemote(remote) && request.checkRemote(tagsForRemote)) { + logger.info("Remote [{}] authorized and matches routing", remote); + targetRemotes.add(remote); + } + } + + if (targetRemotes.isEmpty()) { + logger.info("No target remotes, skipping..."); + return; + } + + ResolvedIndices resolved = remoteClusterResolver.splitLocalAndRemoteIndexNames(indices); + // skip handling searches where there's both qualified and flat expressions to simplify POC + // in the real thing, we'd also rewrite these + if (resolved.getRemote().isEmpty() == false) { + return; + } + + List indexExpressions = new ArrayList<>(indices.length); + for (var local : resolved.getLocal()) { + List rewritten = new ArrayList<>(); + rewritten.add(local); + for (var cluster : targetRemotes) { + rewritten.add(RemoteClusterAware.buildRemoteIndexName(cluster, local)); + indexExpressions.add(new FlatIndicesRequest.IndexExpression(local, rewritten)); + } + logger.info("Rewrote [{}] to [{}]", local, rewritten); + } + + request.indexExpressions(indexExpressions); + } + /** * Attempt to resolve requested indices without expanding any wildcards. * @return The {@link ResolvedIndices} or null if wildcard expansion must be performed. @@ -544,10 +610,13 @@ private static List indicesList(String[] list) { private static class RemoteClusterResolver extends RemoteClusterAware { private final CopyOnWriteArraySet clusters; + // TODO consolidate + private final Map> tags; private RemoteClusterResolver(Settings settings, ClusterSettings clusterSettings) { super(settings); clusters = new CopyOnWriteArraySet<>(getEnabledRemoteClusters(settings)); + tags = RemoteClusterService.getEnabledRemoteClustersWithTags(settings); listenForUpdates(clusterSettings); } @@ -569,5 +638,13 @@ ResolvedIndices splitLocalAndRemoteIndexNames(String... indices) { .toList(); return new ResolvedIndices(local == null ? List.of() : local, remote); } + + Set clusters() { + return Collections.unmodifiableSet(clusters); + } + + Map> tags() { + return Collections.unmodifiableMap(tags); + } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index 1b99bd6888c4f..2e9a20d994e77 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -998,6 +998,10 @@ static AuthorizedIndices resolveAuthorizedIndicesFromRole( } // we don't support granting access to a backing index with a failure selector via the parent data stream } return predicate.test(indexAbstraction, selector); + }, name -> { + // just some bogus predicate that lets us differentiate between roles, not at all + // how this will work in the end + return Arrays.asList(role.names()).contains("_es_test_root"); }); } @@ -1125,15 +1129,18 @@ static final class AuthorizedIndices implements AuthorizationEngine.AuthorizedIn private final CachedSupplier> authorizedAndAvailableDataResources; private final CachedSupplier> authorizedAndAvailableFailuresResources; private final BiPredicate isAuthorizedPredicate; + private final Predicate projectPredicate; AuthorizedIndices( Supplier> authorizedAndAvailableDataResources, Supplier> authorizedAndAvailableFailuresResources, - BiPredicate isAuthorizedPredicate + BiPredicate isAuthorizedPredicate, + Predicate projectPredicate ) { this.authorizedAndAvailableDataResources = CachedSupplier.wrap(authorizedAndAvailableDataResources); this.authorizedAndAvailableFailuresResources = CachedSupplier.wrap(authorizedAndAvailableFailuresResources); this.isAuthorizedPredicate = Objects.requireNonNull(isAuthorizedPredicate); + this.projectPredicate = projectPredicate; } @Override @@ -1149,5 +1156,11 @@ public boolean check(String name, IndexComponentSelector selector) { Objects.requireNonNull(selector, "must specify a selector for authorization check"); return isAuthorizedPredicate.test(name, selector); } + + @Override + public boolean checkRemote(String remoteAlias) { + Objects.requireNonNull(remoteAlias, "must specify a project name for authorization check"); + return projectPredicate.test(remoteAlias); + } } }