/* * This file is part of ReadonlyREST. * * ReadonlyREST is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ReadonlyREST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ */ package org.elasticsearch.plugin.readonlyrest.es.requestcontext; import com.google.common.base.Joiner; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.common.util.ArrayUtils; import org.elasticsearch.index.Index; import org.elasticsearch.plugin.readonlyrest.ESContext; import org.elasticsearch.plugin.readonlyrest.requestcontext.Transactional; import org.elasticsearch.plugin.readonlyrest.utils.ReflecUtils; import org.reflections.ReflectionUtils; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import static org.elasticsearch.plugin.readonlyrest.utils.ReflecUtils.extractStringArrayFromPrivateMethod; /** * Created by sscarduzio on 14/04/2017. */ public class RCTransactionalIndices { //private static final Logger logger = Loggers.getLogger(RCTransactionalIndices.class); // #XXX hacky as hell - needed for bulk request private static final Map<String, Set<String>> restLevelIndicesCache = Maps.newHashMap(); public static Transactional<Set<String>> mkInstance(RequestContextImpl rc, ESContext es) { final Logger logger = es.logger(RCTransactionalIndices.class); if (!rc.involvesIndices()) { return new DummyTXIndices(es); } return new Transactional<Set<String>>("rc-indices", es) { @Override public Set<String> initialize() { if (!rc.involvesIndices()) { return Collections.emptySet(); } String restRequestId = rc.getId().split("-")[0]; Set<String> initialIndices = restLevelIndicesCache.get(restRequestId); if (initialIndices != null && !initialIndices.isEmpty()) { logger.debug("Finding cached indices for: " + rc.getId() + " " + rc.getUnderlyingRequest().getClass().getSimpleName() + ": " + Joiner.on(",").join(initialIndices) ); return initialIndices; } else { restLevelIndicesCache.clear(); logger.debug("Finding indices for: " + rc.getId() + " " + rc.getUnderlyingRequest().getClass().getSimpleName()); Set<String> indices = findIndices(rc); restLevelIndicesCache.put(restRequestId, indices); return indices; } } private Set<String> findIndices(RequestContextImpl rc) { String[] indices = new String[0]; ActionRequest ar = rc.getUnderlyingRequest(); // CompositeIndicesRequests if (ar instanceof MultiGetRequest) { MultiGetRequest cir = (MultiGetRequest) ar; for (MultiGetRequest.Item ir : cir.getItems()) { indices = ArrayUtils.concat(indices, ir.indices(), String.class); } } else if (ar instanceof MultiSearchRequest) { MultiSearchRequest cir = (MultiSearchRequest) ar; for (SearchRequest ir : cir.requests()) { indices = ArrayUtils.concat(indices, ir.indices(), String.class); } } else if (ar instanceof MultiTermVectorsRequest) { MultiTermVectorsRequest cir = (MultiTermVectorsRequest) ar; for (TermVectorsRequest ir : cir.getRequests()) { indices = ArrayUtils.concat(indices, ir.indices(), String.class); } } else if (ar instanceof BulkRequest) { BulkRequest cir = (BulkRequest) ar; for (DocWriteRequest<?> ir : cir.requests()) { String[] docIndices = extractStringArrayFromPrivateMethod("indices", ir, es); if (docIndices.length == 0) { docIndices = extractStringArrayFromPrivateMethod("index", ir, es); } indices = ArrayUtils.concat(indices, docIndices, String.class); } } else if (ar instanceof IndexRequest) { IndexRequest ir = (IndexRequest) ar; indices = ir.indices(); } else if (ar instanceof CompositeIndicesRequest) { logger.error( "Found an instance of CompositeIndicesRequest that could not be handled: report this as a bug immediately! " + ar.getClass().getSimpleName()); } else { indices = extractStringArrayFromPrivateMethod("indices", ar, es); if (indices == null || indices.length == 0) { indices = extractStringArrayFromPrivateMethod("index", ar, es); } } if (indices == null) { indices = new String[0]; } Set<String> indicesSet = Sets.newHashSet(indices); if (logger.isDebugEnabled()) { String idxs = String.join(",", indicesSet); logger.debug("Discovered indices: " + idxs); } return indicesSet; } @Override public Set<String> copy(Set<String> initial) { return Sets.newHashSet(initial); } @Override public void onCommit(Set<String> newIndices) { // Setting indices by reflection.. newIndices.remove("<no-index>"); newIndices.remove(""); ActionRequest actionRequest = rc.getUnderlyingRequest(); if (newIndices.equals(getInitial())) { logger.debug("id: " + rc.getId() + " - Not replacing. Indices are the same. Old:" + get() + " New:" + newIndices); return; } logger.debug("id: " + rc.getId() + " - Replacing indices. Old:" + getInitial() + " New:" + newIndices); if (newIndices.size() == 0) { throw es.rorException( "Attempted to set empty indices list, this would allow full access, therefore this is forbidden." + " If this was intended, set '*' as indices."); } if (actionRequest instanceof BulkShardRequest) { BulkShardRequest bsr = (BulkShardRequest) actionRequest; String singleIndex = newIndices.iterator().next(); String uuid = rc.getIndexMetadata(singleIndex).iterator().next(); AccessController.doPrivileged((PrivilegedAction<Void>) () -> { @SuppressWarnings("unchecked") Set<Field> fields = ReflectionUtils.getAllFields(bsr.shardId().getClass(), ReflectionUtils.withName("index")); fields.stream().forEach(f -> { f.setAccessible(true); try { f.set(bsr.shardId(), new Index(singleIndex, uuid)); } catch (Throwable e) { e.printStackTrace(); } }); return null; }); } boolean okSetResult = ReflecUtils.setIndices(actionRequest, Sets.newHashSet("index", "indices"), newIndices, logger); if (!okSetResult && actionRequest instanceof IndicesAliasesRequest) { IndicesAliasesRequest iar = (IndicesAliasesRequest) actionRequest; List<IndicesAliasesRequest.AliasActions> actions = iar.getAliasActions(); final boolean[] okSubResult = {false}; actions.forEach(a -> { okSubResult[0] &= ReflecUtils.setIndices(a, Sets.newHashSet("index"), newIndices, logger); }); okSetResult &= okSubResult[0]; } if (okSetResult) { logger.debug("success changing indices: " + newIndices + " correctly set as " + get()); } else { logger.error("Failed to set indices for type " + rc.getUnderlyingRequest().getClass().getSimpleName()); } } }; } static class DummyTXIndices extends Transactional<Set<String>> { DummyTXIndices(ESContext es) { super("rc-indices-dummy", es); } @Override public Set<String> initialize() { return Collections.emptySet(); } @Override public Set<String> copy(Set<String> initial) { return initial; } @Override public void onCommit(Set<String> value) { } } }