com.floragunn.searchguard.configuration.PrivilegesEvaluator.java Source code

Java tutorial

Introduction

Here is the source code for com.floragunn.searchguard.configuration.PrivilegesEvaluator.java

Source

/*
 * Copyright 2015 floragunn UG (haftungsbeschrnkt)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.floragunn.searchguard.configuration;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.RealtimeRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.get.MultiGetAction;
import org.elasticsearch.action.percolate.MultiPercolateAction;
import org.elasticsearch.action.search.MultiSearchAction;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.TransportRequest;

import com.floragunn.searchguard.action.configupdate.TransportConfigUpdateAction;
import com.floragunn.searchguard.auditlog.AuditLog;
import com.floragunn.searchguard.support.Base64Helper;
import com.floragunn.searchguard.support.ConfigConstants;
import com.floragunn.searchguard.support.WildcardMatcher;
import com.floragunn.searchguard.user.User;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.collect.UnmodifiableIterator;

public class PrivilegesEvaluator implements ConfigChangeListener {

    private static final Set<String> NULL_SET = Sets.newHashSet((String) null);
    private final Set<String> DLSFLS = ImmutableSet.of("_dls_", "_fls_");
    protected final ESLogger log = Loggers.getLogger(this.getClass());
    private final ClusterService clusterService;
    private volatile Settings rolesMapping;
    private volatile Settings roles;
    private volatile Settings config;
    private final ActionGroupHolder ah;
    private final IndexNameExpressionResolver resolver;
    private final Map<Class<?>, Method> typeCache = Collections.synchronizedMap(new HashMap<Class<?>, Method>(100));
    private final Map<Class<?>, Method> typesCache = Collections
            .synchronizedMap(new HashMap<Class<?>, Method>(100));
    private final String[] deniedActionPatterns;
    private final AuditLog auditLog;
    private final static IndicesOptions DEFAULT_INDICES_OPTIONS = IndicesOptions.lenientExpandOpen();

    private final String searchguardIndex;
    private PrivilegesInterceptor privilegesInterceptor;

    @Inject
    public PrivilegesEvaluator(final ClusterService clusterService, final TransportConfigUpdateAction tcua,
            final ActionGroupHolder ah, final IndexNameExpressionResolver resolver, final AuditLog auditLog,
            final Settings settings, final PrivilegesInterceptor privilegesInterceptor) {
        super();
        tcua.addConfigChangeListener(ConfigurationService.CONFIGNAME_ROLES_MAPPING, this);
        tcua.addConfigChangeListener(ConfigurationService.CONFIGNAME_ROLES, this);
        tcua.addConfigChangeListener(ConfigurationService.CONFIGNAME_CONFIG, this);
        this.clusterService = clusterService;
        this.ah = ah;
        this.resolver = resolver;
        this.auditLog = auditLog;
        this.searchguardIndex = settings.get(ConfigConstants.SG_CONFIG_INDEX,
                ConfigConstants.SG_DEFAULT_CONFIG_INDEX);
        this.privilegesInterceptor = privilegesInterceptor;

        /*
        indices:admin/template/delete
        indices:admin/template/get
        indices:admin/template/put
            
        indices:admin/aliases
        indices:admin/aliases/exists
        indices:admin/aliases/get
        indices:admin/analyze
        indices:admin/cache/clear
        -> indices:admin/close
        indices:admin/create
        -> indices:admin/delete
        indices:admin/get
        indices:admin/exists
        indices:admin/flush
        indices:admin/mapping/put
        indices:admin/mappings/fields/get
        indices:admin/mappings/get
        indices:admin/open
        indices:admin/optimize
        indices:admin/refresh
        indices:admin/settings/update
        indices:admin/shards/search_shards
        indices:admin/types/exists
        indices:admin/upgrade
        indices:admin/validate/query
        indices:admin/warmers/delete
        indices:admin/warmers/get
        indices:admin/warmers/put
        */

        final List<String> deniedActionPatternsList = new ArrayList<String>();
        deniedActionPatternsList.add("indices:data/write*");
        deniedActionPatternsList.add("indices:admin/close");
        deniedActionPatternsList.add("indices:admin/delete");
        //deniedActionPatternsList.add("indices:admin/settings/update");
        //deniedActionPatternsList.add("indices:admin/upgrade");

        deniedActionPatterns = deniedActionPatternsList.toArray(new String[0]);

    }

    @Override
    public void onChange(final String event, final Settings settings) {
        switch (event) {
        case "roles":
            roles = settings;
            break;
        case "rolesmapping":
            rolesMapping = settings;
            break;
        case "config":
            config = settings;
            break;
        }
    }

    @Override
    public boolean isInitialized() {
        return rolesMapping != null && roles != null;
    }

    @Override
    public void validate(final String event, final Settings settings) throws ElasticsearchSecurityException {

    }

    public static class IndexType {

        private String index;
        private String type;

        public IndexType(String index, String type) {
            super();
            this.index = index;
            this.type = type.equals("_all") ? "*" : type;
        }

        public String getCombinedString() {
            return index + "#" + type;
        }

        public String getIndex() {
            return index;
        }

        public String getType() {
            return type;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((index == null) ? 0 : index.hashCode());
            result = prime * result + ((type == null) ? 0 : type.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            IndexType other = (IndexType) obj;
            if (index == null) {
                if (other.index != null)
                    return false;
            } else if (!index.equals(other.index))
                return false;
            if (type == null) {
                if (other.type != null)
                    return false;
            } else if (!type.equals(other.type))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "IndexType [index=" + index + ", type=" + type + "]";
        }
    }

    public boolean evaluate(final User user, final String action, final ActionRequest<?> request) {

        if (action.startsWith("cluster:admin/snapshot/restore")) {
            auditLog.logMissingPrivileges(action, request);
            log.warn(action + " is not allowed for a regular user");
            return false;
        }

        final TransportAddress caller = Objects
                .requireNonNull((TransportAddress) request.getFromContext(ConfigConstants.SG_REMOTE_ADDRESS));

        if (log.isDebugEnabled()) {
            log.debug("evaluate permissions for {}", user);
            log.debug("requested {} from {}", action, caller);
        }

        final ClusterState clusterState = clusterService.state();
        final MetaData metaData = clusterState.metaData();
        final Tuple<Set<String>, Set<String>> requestedResolvedAliasesIndicesTypes = resolve(user, action, request,
                metaData);

        final Set<String> requestedResolvedIndices = Collections
                .unmodifiableSet(requestedResolvedAliasesIndicesTypes.v1());
        final Set<IndexType> requestedResolvedIndexTypes;

        {
            final Set<IndexType> requestedResolvedIndexTypes0 = new HashSet<IndexType>(
                    requestedResolvedAliasesIndicesTypes.v1().size()
                            * requestedResolvedAliasesIndicesTypes.v2().size());

            for (String index : requestedResolvedAliasesIndicesTypes.v1()) {
                for (String type : requestedResolvedAliasesIndicesTypes.v2()) {
                    requestedResolvedIndexTypes0.add(new IndexType(index, type));
                }
            }

            requestedResolvedIndexTypes = Collections.unmodifiableSet(requestedResolvedIndexTypes0);
        }

        if (log.isDebugEnabled()) {
            log.debug("requested resolved indextypes: {}", requestedResolvedIndexTypes);
        }

        if (requestedResolvedIndices.contains(searchguardIndex)
                && WildcardMatcher.matchAny(deniedActionPatterns, action)) {
            auditLog.logSgIndexAttempt(request, action);
            log.warn(action + " for '{}' index is not allowed for a regular user", searchguardIndex);
            return false;
        }

        if (requestedResolvedIndices.contains("_all") && WildcardMatcher.matchAny(deniedActionPatterns, action)) {
            auditLog.logSgIndexAttempt(request, action);
            log.warn(action + " for '_all' indices is not allowed for a regular user");
            return false;
        }

        if (requestedResolvedIndices.contains(searchguardIndex) || requestedResolvedIndices.contains("_all")) {

            if (request instanceof SearchRequest) {
                ((SearchRequest) request).requestCache(Boolean.FALSE);
                if (log.isDebugEnabled()) {
                    log.debug("Disable search request cache for this request");
                }
            }

            if (request instanceof RealtimeRequest) {
                ((RealtimeRequest) request).realtime(Boolean.FALSE);
                if (log.isDebugEnabled()) {
                    log.debug("Disable realtime for this request");
                }
            }
        }

        final Set<String> sgRoles = mapSgRoles(user, caller);

        if (log.isDebugEnabled()) {
            log.debug("mapped roles for {}: {}", user.getName(), sgRoles);
        }

        if (privilegesInterceptor.getClass() != PrivilegesInterceptor.class) {

            final boolean denyRequest = privilegesInterceptor.replaceKibanaIndex(request, action, user, config,
                    requestedResolvedIndices, mapTenants(user, caller));

            if (denyRequest) {
                auditLog.logMissingPrivileges(action, request);
                return false;
            }
        }

        boolean allowAction = false;

        final Map<String, Set<String>> dlsQueries = new HashMap<String, Set<String>>();
        final Map<String, Set<String>> flsFields = new HashMap<String, Set<String>>();

        final Set<IndexType> leftovers = new HashSet<PrivilegesEvaluator.IndexType>();

        for (final Iterator<String> iterator = sgRoles.iterator(); iterator.hasNext();) {
            final String sgRole = (String) iterator.next();
            final Settings sgRoleSettings = roles.getByPrefix(sgRole);

            if (sgRoleSettings.names().isEmpty()) {

                if (log.isDebugEnabled()) {
                    log.debug("sg_role {} is empty", sgRole);
                }

                continue;
            }

            if (log.isDebugEnabled()) {
                log.debug("---------- evaluate sg_role: {}", sgRole);
            }

            final boolean compositeEnabled = config.getAsBoolean("searchguard.dynamic.composite_enabled", false);

            if (action.startsWith("cluster:") || action.startsWith("indices:admin/template/delete")
                    || action.startsWith("indices:admin/template/get")
                    || action.startsWith("indices:admin/template/put")
                    || action.startsWith("indices:data/read/scroll")
                    //M*
                    || (compositeEnabled && action.equals(BulkAction.NAME))
                    || (compositeEnabled && action.equals(IndicesAliasesAction.NAME))
                    || (compositeEnabled && action.equals(MultiGetAction.NAME))
                    || (compositeEnabled && action.equals(MultiPercolateAction.NAME))
                    || (compositeEnabled && action.equals(MultiSearchAction.NAME))
                    || (compositeEnabled && action.equals(MultiTermVectorsAction.NAME))
                    || (compositeEnabled && action.equals("indices:data/read/coordinate-msearch"))
            //|| (compositeEnabled && action.startsWith(MultiPercolateAction.NAME))
            ) {

                final Set<String> resolvedActions = resolveActions(
                        sgRoleSettings.getAsArray(".cluster", new String[0]));

                if (log.isDebugEnabled()) {
                    log.debug("  resolved cluster actions:{}", resolvedActions);
                }

                if (WildcardMatcher.matchAny(resolvedActions.toArray(new String[0]), action)) {
                    if (log.isDebugEnabled()) {
                        log.debug("  found a match for '{}' and {}, skip other roles", sgRole, action);
                    }
                    return true;
                } else {
                    //check other roles #108
                    if (log.isDebugEnabled()) {
                        log.debug("  not match found a match for '{}' and {}, check next role", sgRole, action);
                    }
                    continue;
                }
            }

            final Map<String, Settings> permittedAliasesIndices0 = sgRoleSettings.getGroups(".indices");
            final Map<String, Settings> permittedAliasesIndices = new HashMap<String, Settings>(
                    permittedAliasesIndices0.size());

            for (String origKey : permittedAliasesIndices0.keySet()) {
                permittedAliasesIndices.put(
                        origKey.replace("${user.name}", user.getName()).replace("${user_name}", user.getName()),
                        permittedAliasesIndices0.get(origKey));
            }

            /*
            sg_role_starfleet:
            indices:
            sf: #<--- is an alias or cindex, can contain wildcards, will be resolved to concrete indices
            # if this contain wildcards we do a wildcard based check
            # if contains no wildcards we resolve this to concrete indices an do a exact check
            #
                
            ships:  <-- is a type, can contain wildcards
            - READ
            public:
            - 'indices:*'
            students:
            - READ
            alumni:
            - READ
            'admin*':
            - READ
            'pub*':
            '*':
            - READ
             */

            final ListMultimap<String, String> resolvedRoleIndices = Multimaps
                    .synchronizedListMultimap(ArrayListMultimap.<String, String>create());

            final Set<IndexType> _requestedResolvedIndexTypes = new HashSet<IndexType>(requestedResolvedIndexTypes);
            //iterate over all beneath indices:
            permittedAliasesIndices: for (final String permittedAliasesIndex : permittedAliasesIndices.keySet()) {

                //final Map<String, Settings> permittedTypes = sgRoleSettings.getGroups(".indices."+permittedAliasesIndex);

                //System.out.println(permittedTypes);

                if (WildcardMatcher.containsWildcard(permittedAliasesIndex)) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Try wildcard match for {}", permittedAliasesIndex);
                    }

                    handleIndicesWithWildcard(action, permittedAliasesIndex, permittedAliasesIndices,
                            requestedResolvedIndexTypes, _requestedResolvedIndexTypes, requestedResolvedIndices);

                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("  Resolve and match {}", permittedAliasesIndex);
                    }

                    handleIndicesWithoutWildcard(action, permittedAliasesIndex, permittedAliasesIndices,
                            requestedResolvedIndexTypes, _requestedResolvedIndexTypes);
                }

                if (log.isDebugEnabled()) {
                    log.debug("For index {} remaining requested indextype: {}", permittedAliasesIndex,
                            _requestedResolvedIndexTypes);
                }

                if (_requestedResolvedIndexTypes.isEmpty()) {

                    int filteredAliasCount = 0;

                    //check filtered aliases
                    for (String requestAliasOrIndex : requestedResolvedIndices) {

                        //System.out.println(clusterState.metaData().getAliasAndIndexLookup().get(requestAliasOrIndex));
                        IndexMetaData indexMetaData = clusterState.metaData().getIndices().get(requestAliasOrIndex);

                        if (indexMetaData == null) {
                            log.warn("{} does not exist in cluster metadata", requestAliasOrIndex);
                            continue;
                        }

                        ImmutableOpenMap<String, AliasMetaData> aliases = indexMetaData.getAliases();

                        log.debug("Aliases for {}: {}", requestAliasOrIndex, aliases);

                        if (aliases != null && aliases.size() > 0) {

                            UnmodifiableIterator<String> it = aliases.keysIt();
                            while (it.hasNext()) {
                                String a = it.next();
                                AliasMetaData aliasMetaData = aliases.get(a);

                                if (aliasMetaData != null && aliasMetaData.filteringRequired()) {
                                    filteredAliasCount++;
                                    log.debug(a + " is a filtered alias " + aliasMetaData.getFilter());
                                } else {
                                    log.debug(a + " is not an alias or does not have a filter");
                                }

                            }

                        }
                    }

                    if (filteredAliasCount > 1) {
                        //TODO add queries as dls queries (works only if dls module is installed)
                        log.warn(
                                "More than one ({}) filtered alias found for same index ({}). This is currently not supported",
                                filteredAliasCount, permittedAliasesIndex);
                        continue permittedAliasesIndices;
                    }

                    if (log.isDebugEnabled()) {
                        log.debug("found a match for '{}.{}', evaluate other roles", sgRole, permittedAliasesIndex);
                    }

                    resolvedRoleIndices.put(sgRole, permittedAliasesIndex);
                }

            } // end loop permittedAliasesIndices

            if (!resolvedRoleIndices.isEmpty()) {
                for (String resolvedRole : resolvedRoleIndices.keySet()) {
                    for (String indexPattern : resolvedRoleIndices.get(resolvedRole)) {
                        String dls = roles.get(resolvedRole + ".indices." + indexPattern + "._dls_");
                        final String[] fls = roles.getAsArray(resolvedRole + ".indices." + indexPattern + "._fls_");

                        //only when dls and fls != null
                        String[] concreteIndices = new String[0];

                        if ((dls != null && dls.length() > 0) || (fls != null && fls.length > 0)) {
                            concreteIndices = resolver.concreteIndices(clusterService.state(),
                                    DEFAULT_INDICES_OPTIONS/*??*/, indexPattern);
                        }

                        if (dls != null && dls.length() > 0) {

                            //TODO use UserPropertyReplacer, make it registerable for ldap user
                            dls = dls.replace("${user.name}", user.getName()).replace("${user_name}",
                                    user.getName());

                            if (dlsQueries.containsKey(indexPattern)) {
                                dlsQueries.get(indexPattern).add(dls);
                            } else {
                                dlsQueries.put(indexPattern, new HashSet<String>());
                                dlsQueries.get(indexPattern).add(dls);
                            }

                            for (int i = 0; i < concreteIndices.length; i++) {
                                final String ci = concreteIndices[i];
                                if (dlsQueries.containsKey(ci)) {
                                    dlsQueries.get(ci).add(dls);
                                } else {
                                    dlsQueries.put(ci, new HashSet<String>());
                                    dlsQueries.get(ci).add(dls);
                                }
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("dls query {} for {}", dls, Arrays.toString(concreteIndices));
                            }

                        }

                        if (fls != null && fls.length > 0) {

                            if (flsFields.containsKey(indexPattern)) {
                                flsFields.get(indexPattern).addAll(Sets.newHashSet(fls));
                            } else {
                                flsFields.put(indexPattern, new HashSet<String>());
                                flsFields.get(indexPattern).addAll(Sets.newHashSet(fls));
                            }

                            for (int i = 0; i < concreteIndices.length; i++) {
                                final String ci = concreteIndices[i];
                                if (flsFields.containsKey(ci)) {
                                    flsFields.get(ci).addAll(Sets.newHashSet(fls));
                                } else {
                                    flsFields.put(ci, new HashSet<String>());
                                    flsFields.get(ci).addAll(Sets.newHashSet(fls));
                                }
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("fls fields {} for {}", Sets.newHashSet(fls),
                                        Arrays.toString(concreteIndices));
                            }

                        }

                    }
                }

                allowAction = true;
            }

            leftovers.addAll(_requestedResolvedIndexTypes);

        } // end sg role loop

        if (!allowAction && log.isInfoEnabled()) {
            log.info("No perm match for {} {} [Action [{}]] [RolesChecked {}]", user, requestedResolvedIndexTypes,
                    action, sgRoles);
        }

        if (!dlsQueries.isEmpty()) {
            request.putHeader(ConfigConstants.SG_DLS_QUERY,
                    Base64Helper.serializeObject((Serializable) dlsQueries));
        }

        if (!flsFields.isEmpty()) {
            request.putHeader(ConfigConstants.SG_FLS_FIELDS,
                    Base64Helper.serializeObject((Serializable) flsFields));
        }

        if (!allowAction && privilegesInterceptor.getClass() != PrivilegesInterceptor.class) {
            return privilegesInterceptor.replaceAllowedIndices(request, action, user, config, leftovers);
        }

        return allowAction;
    }

    //---- end evaluate()

    public Set<String> mapSgRoles(final User user, final TransportAddress caller) {

        if (user == null) {
            return Collections.emptySet();
        }

        final Set<String> sgRoles = new TreeSet<String>();
        for (final String roleMap : rolesMapping.names()) {
            final Settings roleMapSettings = rolesMapping.getByPrefix(roleMap);

            if (WildcardMatcher.allPatternsMatched(roleMapSettings.getAsArray(".and_backendroles"),
                    user.getRoles().toArray(new String[0]))) {
                sgRoles.add(roleMap);
                continue;
            }

            if (WildcardMatcher.matchAny(roleMapSettings.getAsArray(".backendroles"),
                    user.getRoles().toArray(new String[0]))) {
                sgRoles.add(roleMap);
                continue;
            }

            if (WildcardMatcher.matchAny(roleMapSettings.getAsArray(".users"), user.getName())) {
                sgRoles.add(roleMap);
                continue;
            }

            if (caller != null
                    && WildcardMatcher.matchAny(roleMapSettings.getAsArray(".hosts"), caller.getAddress())) {
                sgRoles.add(roleMap);
                continue;
            }

            if (caller != null
                    && WildcardMatcher.matchAny(roleMapSettings.getAsArray(".hosts"), caller.getHost())) {
                sgRoles.add(roleMap);
                continue;
            }

        }

        return Collections.unmodifiableSet(sgRoles);

    }

    public Map<String, Boolean> mapTenants(final User user, final TransportAddress caller) {

        if (user == null) {
            return Collections.emptyMap();
        }

        final Map<String, Boolean> result = new HashMap<String, Boolean>();
        result.put(user.getName(), true);

        for (String sgRole : mapSgRoles(user, caller)) {
            Settings tenants = roles.getByPrefix(sgRole + ".tenants.");

            if (tenants != null) {
                for (String tenant : tenants.names()) {
                    if ("RW".equalsIgnoreCase(tenants.get(tenant, "RO"))) {
                        result.put(tenant, true);
                    } else {
                        result.put(tenant, false);
                    }
                }
            }

        }

        return Collections.unmodifiableMap(result);
    }

    private void handleIndicesWithWildcard(final String action, final String permittedAliasesIndex,
            final Map<String, Settings> permittedAliasesIndices, final Set<IndexType> requestedResolvedIndexTypes,
            final Set<IndexType> _requestedResolvedIndexTypes, final Set<String> requestedResolvedIndices0) {

        List<String> wi = null;
        if (!(wi = WildcardMatcher.getMatchAny(permittedAliasesIndex,
                requestedResolvedIndices0.toArray(new String[0]))).isEmpty()) {

            if (log.isDebugEnabled()) {
                log.debug("  Wildcard match for {}: {}", permittedAliasesIndex, wi);
            }

            final Set<String> permittedTypes = new HashSet<String>(
                    permittedAliasesIndices.get(permittedAliasesIndex).names());
            permittedTypes.removeAll(DLSFLS);

            if (log.isDebugEnabled()) {
                log.debug("  matches for {}, will check now types {}", permittedAliasesIndex, permittedTypes);
            }

            for (final String type : permittedTypes) {

                final Set<String> resolvedActions = resolveActions(
                        permittedAliasesIndices.get(permittedAliasesIndex).getAsArray(type));

                if (WildcardMatcher.matchAny(resolvedActions.toArray(new String[0]), action)) {
                    if (log.isDebugEnabled()) {
                        log.debug("    match requested action {} against {}/{}: {}", action, permittedAliasesIndex,
                                type, resolvedActions);
                    }

                    for (String it : wi) {
                        boolean removed = wildcardRemoveFromSet(_requestedResolvedIndexTypes,
                                new IndexType(it, type));

                        if (removed) {
                            log.debug("    removed {}", it + type);
                        } else {
                            log.debug("    no match {} in {}", it + type, _requestedResolvedIndexTypes);
                        }
                    }
                }

            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("  No wildcard match found for {}", permittedAliasesIndex);
            }

            return;
        }
    }

    private void handleIndicesWithoutWildcard(final String action, final String permittedAliasesIndex,
            final Map<String, Settings> permittedAliasesIndices, final Set<IndexType> requestedResolvedIndexTypes,
            final Set<IndexType> _requestedResolvedIndexTypes) {

        final Set<String> resolvedPermittedAliasesIndex = new HashSet<String>();

        if (!resolver.hasIndexOrAlias(permittedAliasesIndex, clusterService.state())) {

            if (log.isDebugEnabled()) {
                log.debug("no permittedAliasesIndex '{}' found for  '{}'", permittedAliasesIndex, action);

                for (String pai : permittedAliasesIndices.keySet()) {
                    Settings paiSettings = permittedAliasesIndices.get(pai);
                    log.debug("permittedAliasesIndices '{}' -> '{}'", permittedAliasesIndices,
                            paiSettings == null ? "null" : String.valueOf(paiSettings.getAsMap()));
                }

                log.debug("requestedResolvedIndexTypes '{}'", requestedResolvedIndexTypes);
            }

            resolvedPermittedAliasesIndex.add(permittedAliasesIndex);

        } else {
            resolvedPermittedAliasesIndex.addAll(Arrays.asList(resolver.concreteIndices(clusterService.state(),
                    DEFAULT_INDICES_OPTIONS, permittedAliasesIndex)));
        }

        if (log.isDebugEnabled()) {
            log.debug("  resolved permitted aliases indices for {}: {}", permittedAliasesIndex,
                    resolvedPermittedAliasesIndex);
        }

        //resolvedPermittedAliasesIndex -> resolved indices from role entry n
        final Set<String> permittedTypes = new HashSet<String>(
                permittedAliasesIndices.get(permittedAliasesIndex).names());
        permittedTypes.removeAll(DLSFLS);

        if (log.isDebugEnabled()) {
            log.debug("  matches for {}, will check now types {}", permittedAliasesIndex, permittedTypes);
        }

        for (final String type : permittedTypes) {

            final Set<String> resolvedActions = resolveActions(
                    permittedAliasesIndices.get(permittedAliasesIndex).getAsArray(type));

            if (WildcardMatcher.matchAny(resolvedActions.toArray(new String[0]), action)) {
                if (log.isDebugEnabled()) {
                    log.debug("    match requested action {} against {}/{}: {}", action, permittedAliasesIndex,
                            type, resolvedActions);
                }

                for (String resolvedPermittedIndex : resolvedPermittedAliasesIndex) {
                    boolean removed = wildcardRemoveFromSet(_requestedResolvedIndexTypes,
                            new IndexType(resolvedPermittedIndex, type));

                    if (removed) {
                        log.debug("    removed {}", resolvedPermittedIndex + type);

                    } else {
                        log.debug("    no match {} in {}", resolvedPermittedIndex + type,
                                _requestedResolvedIndexTypes);
                    }
                }
            }
        }
    }

    private Tuple<Set<String>, Set<String>> resolve(final User user, final String action,
            final TransportRequest request, final MetaData metaData) {

        if (!(request instanceof CompositeIndicesRequest) && !(request instanceof IndicesRequest)) {

            if (log.isDebugEnabled()) {
                log.debug("{} is not an IndicesRequest", request.getClass());
            }

            return new Tuple<Set<String>, Set<String>>(Sets.newHashSet("_all"), Sets.newHashSet("_all"));
        }

        final Set<String> indices = new HashSet<String>();
        final Set<String> types = new HashSet<String>();

        if (request instanceof CompositeIndicesRequest) {
            for (final IndicesRequest indicesRequest : ((CompositeIndicesRequest) request).subRequests()) {
                final Tuple<Set<String>, Set<String>> t = resolve(user, action, indicesRequest, metaData);
                indices.addAll(t.v1());
                types.addAll(t.v2());
            }
        } else {
            final Tuple<Set<String>, Set<String>> t = resolve(user, action, (IndicesRequest) request, metaData);
            indices.addAll(t.v1());
            types.addAll(t.v2());
        }

        //for PutIndexTemplateRequest the index does not exists yet typically
        if (IndexNameExpressionResolver.isAllIndices(new ArrayList<String>(indices))) {
            if (log.isDebugEnabled()) {
                log.debug("The following list are '_all' indices: {}", indices);
            }
            indices.clear();
            indices.add("_all");
        }

        if (types.isEmpty()) {
            types.add("_all");
        }

        return new Tuple<Set<String>, Set<String>>(Collections.unmodifiableSet(indices),
                Collections.unmodifiableSet(types));
    }

    private Tuple<Set<String>, Set<String>> resolve(final User user, final String action,
            final IndicesRequest request, final MetaData metaData) {

        if (log.isDebugEnabled()) {
            log.debug("Resolve {} from {}", request.indices(), request.getClass());
        }

        final Class<? extends IndicesRequest> requestClass = request.getClass();
        final Set<String> requestTypes = new HashSet<String>();

        Method typeMethod = null;
        if (typeCache.containsKey(requestClass)) {
            typeMethod = typeCache.get(requestClass);
        } else {
            try {
                typeMethod = requestClass.getMethod("type");
                typeCache.put(requestClass, typeMethod);
            } catch (NoSuchMethodException e) {
                typeCache.put(requestClass, null);
            } catch (SecurityException e) {
                log.error("Cannot evaluate type() for {} due to {}", requestClass, e);
            }

        }

        Method typesMethod = null;
        if (typesCache.containsKey(requestClass)) {
            typesMethod = typesCache.get(requestClass);
        } else {
            try {
                typesMethod = requestClass.getMethod("types");
                typesCache.put(requestClass, typesMethod);
            } catch (NoSuchMethodException e) {
                typesCache.put(requestClass, null);
            } catch (SecurityException e) {
                log.error("Cannot evaluate types() for {} due to {}", requestClass, e);
            }

        }

        if (typeMethod != null) {
            try {
                String type = (String) typeMethod.invoke(request);
                if (type != null) {
                    requestTypes.add(type);
                }
            } catch (Exception e) {
                log.error("Unable to invoke type() for {} due to {}", e, requestClass, e);
            }
        }

        if (typesMethod != null) {
            try {
                final String[] types = (String[]) typesMethod.invoke(request);

                if (types != null) {
                    requestTypes.addAll(Arrays.asList(types));
                }
            } catch (Exception e) {
                log.error("Unable to invoke types() for {} due to {}", e, requestClass, e);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("indicesOptions {}", request.indicesOptions());
            log.debug("raw indices {}", Arrays.toString(request.indices()));
        }

        final Set<String> indices = new HashSet<String>();

        if (request.indices() == null || request.indices().length == 0
                || new HashSet<String>(Arrays.asList(request.indices())).equals(NULL_SET)) {

            if (log.isDebugEnabled()) {
                log.debug("No indices found in request, assume _all");
            }

            indices.addAll(
                    Arrays.asList(resolver.concreteIndices(clusterService.state(), DEFAULT_INDICES_OPTIONS, "*")));

        } else {

            try {
                indices.addAll(Arrays.asList(resolver.concreteIndices(clusterService.state(), request)));
                if (log.isDebugEnabled()) {
                    log.debug("Resolved {} to {}", request.indices(), indices);
                }
            } catch (final Exception e) {
                log.debug("Cannot resolve {} (due to {}) so we use the raw values",
                        Arrays.toString(request.indices()), e);
                indices.addAll(Arrays.asList(request.indices()));
            }
        }

        return new Tuple<Set<String>, Set<String>>(indices, requestTypes);
    }

    private Set<String> resolveActions(final String[] actions) {
        final Set<String> resolvedActions = new HashSet<String>();
        for (int i = 0; i < actions.length; i++) {
            final String string = actions[i];
            final Set<String> groups = ah.getGroupMembers(string);
            if (groups.isEmpty()) {
                resolvedActions.add(string);
            } else {
                resolvedActions.addAll(groups);
            }
        }

        return resolvedActions;
    }

    private boolean wildcardRemoveFromSet(Set<IndexType> set, IndexType stringContainingWc) {
        if (set.contains(stringContainingWc)) {
            return set.remove(stringContainingWc);
        } else {
            boolean modified = false;
            Set<IndexType> copy = new HashSet<IndexType>(set);

            for (IndexType it : copy) {
                if (WildcardMatcher.match(stringContainingWc.getCombinedString(), it.getCombinedString())) {
                    modified = set.remove(it) | modified;
                }
            }
            return modified;
        }
    }
}