/* * 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.acl.blocks; import org.apache.logging.log4j.Logger; import org.elasticsearch.plugin.readonlyrest.ESContext; import org.elasticsearch.plugin.readonlyrest.acl.domain.Verbosity; import org.elasticsearch.plugin.readonlyrest.requestcontext.RequestContext; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.AsyncRule; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.AsyncRuleAdapter; import org.elasticsearch.plugin.readonlyrest.acl.BlockPolicy; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.RuleExitResult; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.RulesFactory; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.RulesOrdering; import org.elasticsearch.plugin.readonlyrest.acl.blocks.rules.phantomtypes.Authentication; import org.elasticsearch.plugin.readonlyrest.settings.BlockSettings; import org.elasticsearch.plugin.readonlyrest.utils.FuturesSequencer; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.elasticsearch.plugin.readonlyrest.Constants.ANSI_CYAN; import static org.elasticsearch.plugin.readonlyrest.Constants.ANSI_RESET; import static org.elasticsearch.plugin.readonlyrest.Constants.ANSI_YELLOW; /** * Created by sscarduzio on 13/02/2016. */ public class Block { private final Logger logger; private final BlockSettings settings; private final List<AsyncRule> conditionsToCheck; private final boolean authHeaderAccepted; public Block(BlockSettings settings, RulesFactory rulesFactory, ESContext context) { this.logger = context.logger(getClass()); this.settings = settings; this.conditionsToCheck = settings.getRules().stream() .map(rulesFactory::create) .collect(Collectors.toList()); this.conditionsToCheck.sort(new RulesOrdering()); this.authHeaderAccepted = conditionsToCheck.stream().anyMatch(this::isAuthenticationRule); } public List<AsyncRule> getRules() { return conditionsToCheck; } public String getName() { return settings.getName(); } public BlockPolicy getPolicy() { return settings.getPolicy(); } public Verbosity getVerbosity() { return settings.getVerbosity(); } public boolean isAuthHeaderAccepted() { return authHeaderAccepted; } /* * Check all the conditions of this rule and return a rule exit result * */ public CompletableFuture<BlockExitResult> check(RequestContext rc) { return checkAsyncRules(rc) .thenApply(asyncCheck -> { if (asyncCheck != null && asyncCheck) { return finishWithMatchResult(); } else { return finishWithNoMatchResult(rc); } }); } private CompletableFuture<Boolean> checkAsyncRules(RequestContext rc) { // async rules should be checked in sequence due to interaction with not thread safe objects like RequestContext Set<RuleExitResult> thisBlockHistory = new HashSet<>(conditionsToCheck.size()); return checkAsyncRulesInSequence(rc, conditionsToCheck.iterator(), thisBlockHistory) .thenApply(result -> { rc.addToHistory(this, thisBlockHistory); return result; }); } private CompletableFuture<Boolean> checkAsyncRulesInSequence(RequestContext rc, Iterator<AsyncRule> rules, Set<RuleExitResult> thisBlockHistory) { return FuturesSequencer.runInSeqUntilConditionIsUndone( rules, rule -> rule.match(rc), ruleExitResult -> { thisBlockHistory.add(ruleExitResult); return !ruleExitResult.isMatch(); }, RuleExitResult::isMatch, nothing -> true ); } private BlockExitResult finishWithMatchResult() { logger.debug(ANSI_CYAN + "matched " + this + ANSI_RESET); return BlockExitResult.match(this); } private BlockExitResult finishWithNoMatchResult(RequestContext rc) { logger.debug(ANSI_YELLOW + "[" + settings.getName() + "] the request matches no rules in this block: " + rc + ANSI_RESET); return BlockExitResult.noMatch(); } private boolean isAuthenticationRule(AsyncRule rule) { return rule instanceof Authentication || (rule instanceof AsyncRuleAdapter && ((AsyncRuleAdapter) rule).getUnderlying() instanceof Authentication); } @Override public String toString() { return "readonlyrest Rules Block :: { name: '" + settings.getName() + "', policy: " + settings.getPolicy() + "}"; } }