/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.optimizer.relational; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.teiid.adminapi.DataPolicy; import org.teiid.adminapi.impl.DataPolicyMetadata; import org.teiid.adminapi.impl.DataPolicyMetadata.PermissionMetaData; import org.teiid.api.exception.query.ExpressionEvaluationException; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.api.exception.query.QueryProcessingException; import org.teiid.api.exception.query.QueryResolverException; import org.teiid.api.exception.query.QueryValidatorException; import org.teiid.common.buffer.BlockedException; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidException; import org.teiid.core.TeiidProcessingException; import org.teiid.metadata.FunctionMethod.Determinism; import org.teiid.query.QueryPlugin; import org.teiid.query.eval.Evaluator; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.parser.QueryParser; import org.teiid.query.resolver.QueryResolver; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.resolver.util.ResolverVisitor; import org.teiid.query.rewriter.QueryRewriter; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.navigator.PreOrPostOrderNavigator; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.visitor.CorrelatedReferenceCollectorVisitor; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.query.sql.visitor.EvaluatableVisitor; import org.teiid.query.sql.visitor.ExpressionMappingVisitor; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; import org.teiid.query.util.CommandContext; import org.teiid.query.validator.ValidationVisitor; import org.teiid.query.validator.Validator; import org.teiid.query.validator.ValidatorFailure; import org.teiid.query.validator.ValidatorReport; public class RowBasedSecurityHelper { static class RecontextVisitor extends ExpressionMappingVisitor { private final GroupSymbol group; private final String definition; RecontextVisitor(GroupSymbol group) { super(null); this.group = group; this.definition = group.getDefinition(); } @Override public Expression replaceExpression( Expression element) { if (element instanceof ElementSymbol) { ElementSymbol es = (ElementSymbol)element; if (es.getGroupSymbol().getDefinition() == null && es.getGroupSymbol().getName().equalsIgnoreCase(this.definition)) { es.getGroupSymbol().setDefinition(group.getDefinition()); es.getGroupSymbol().setName(group.getName()); } } return element; } } public static boolean applyRowSecurity(QueryMetadataInterface metadata, final GroupSymbol group, CommandContext cc) throws QueryMetadataException, TeiidComponentException { Map<String, DataPolicy> policies = cc.getAllowedDataPolicies(); if (policies == null || policies.isEmpty()) { return false; } String fullName = metadata.getFullName(group.getMetadataID()); for (Map.Entry<String, DataPolicy> entry : policies.entrySet()) { DataPolicyMetadata dpm = (DataPolicyMetadata)entry.getValue(); if (dpm.hasRowSecurity(fullName)) { return true; } } return false; } public static Criteria getRowBasedFilters(QueryMetadataInterface metadata, final GroupSymbol group, CommandContext cc, boolean constraintsOnly) throws QueryMetadataException, TeiidComponentException, TeiidProcessingException { Map<String, DataPolicy> policies = cc.getAllowedDataPolicies(); if (policies == null || policies.isEmpty()) { return null; } boolean user = false; ArrayList<Criteria> crits = null; Object metadataID = group.getMetadataID(); String fullName = metadata.getFullName(metadataID); for (Map.Entry<String, DataPolicy> entry : policies.entrySet()) { DataPolicyMetadata dpm = (DataPolicyMetadata)entry.getValue(); PermissionMetaData pmd = dpm.getPermissionMap().get(fullName); if (pmd == null) { continue; } String filterString = pmd.getCondition(); if (filterString == null) { continue; } if (constraintsOnly && Boolean.FALSE.equals(pmd.getConstraint())) { continue; } Criteria filter = resolveCondition(metadata, group, fullName, entry, pmd, filterString); if (!dpm.isAnyAuthenticated()) { user = true; } if (crits == null) { crits = new ArrayList<Criteria>(2); } crits.add(filter); } if (crits == null || crits.isEmpty()) { return null; } Criteria result = null; if (crits.size() == 1) { result = crits.get(0); } else { result = new CompoundCriteria(CompoundCriteria.OR, crits); } if (group.getDefinition() != null) { ExpressionMappingVisitor emv = new RecontextVisitor(group); PreOrPostOrderNavigator.doVisit(result, emv, PreOrPostOrderNavigator.PRE_ORDER, true); } //we treat this as user deterministic since the data roles won't change. this may change if the logic becomes dynamic if (user) { cc.setDeterminismLevel(Determinism.USER_DETERMINISTIC); } Expression ex = QueryRewriter.rewriteExpression(result, cc, metadata, true); if (ex instanceof Criteria) { return (Criteria)ex; } return QueryRewriter.rewriteCriteria(new ExpressionCriteria(ex), cc, metadata); } static Criteria resolveCondition(QueryMetadataInterface metadata, final GroupSymbol group, String fullName, Map.Entry<String, DataPolicy> entry, PermissionMetaData pmd, String filterString) throws QueryMetadataException { Criteria filter = (Criteria)pmd.getResolvedCondition(); if (filter == null) { try { filter = QueryParser.getQueryParser().parseCriteria(filterString); GroupSymbol gs = group; if (group.getDefinition() != null) { gs = new GroupSymbol(fullName); gs.setMetadataID(group.getMetadataID()); } Collection<GroupSymbol> groups = Arrays.asList(gs); for (SubqueryContainer container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(filter)) { container.getCommand().pushNewResolvingContext(groups); QueryResolver.resolveCommand(container.getCommand(), metadata, false); } ResolverVisitor.resolveLanguageObject(filter, groups, metadata); ValidatorReport report = Validator.validate(filter, metadata, new ValidationVisitor()); if (report.hasItems()) { ValidatorFailure firstFailure = report.getItems().iterator().next(); throw new QueryMetadataException(QueryPlugin.Event.TEIID31129, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31129, entry.getKey(), fullName) + " " + firstFailure); //$NON-NLS-1$ } pmd.setResolvedCondition(filter.clone()); } catch (QueryMetadataException e) { throw e; } catch (TeiidException e) { throw new QueryMetadataException(QueryPlugin.Event.TEIID31129, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31129, entry.getKey(), fullName)); } } else { filter = (Criteria) filter.clone(); } return filter; } public static Command checkUpdateRowBasedFilters(ProcedureContainer container, Command procedure, RelationalPlanner planner) throws QueryMetadataException, TeiidComponentException, TeiidProcessingException, QueryResolverException { if (container instanceof StoredProcedure) { return procedure; } Criteria filter = RowBasedSecurityHelper.getRowBasedFilters(planner.metadata, container.getGroup(), planner.context, false); if (filter == null) { return procedure; } addFilter(container, planner, filter); //we won't enforce on the update side through a virtual if (procedure != null) { return procedure; } filter = RowBasedSecurityHelper.getRowBasedFilters(planner.metadata, container.getGroup(), planner.context, true); if (filter == null) { return procedure; } //TODO: alter the compensation logic in RelationalPlanner to produce an row-by-row check for insert/update //check constraints Map<ElementSymbol, Expression> values = null; boolean compensate = false; if (container instanceof Update) { Collection<ElementSymbol> elems = ElementCollectorVisitor.getElements(filter, true); //check the change set against the filter values = new HashMap<ElementSymbol, Expression>(); Update update = (Update)container; boolean constraintApplicable = false; for (SetClause clause : update.getChangeList().getClauses()) { if (!elems.contains(clause.getSymbol())) { continue; } constraintApplicable = true; //TODO: do this is a single eval pass if (EvaluatableVisitor.isFullyEvaluatable(clause.getValue(), true)) { values.put(clause.getSymbol(), clause.getValue()); } else if (!compensate && !EvaluatableVisitor.isFullyEvaluatable(clause.getValue(), false)) { compensate = true; } } if (!constraintApplicable) { return procedure; } //TOOD: decompose the where clause to see if there are any more static //values that can be plugged in } else if (container instanceof Insert) { Insert insert = (Insert)container; if (insert.getQueryExpression() == null) { values = new HashMap<ElementSymbol, Expression>(); Collection<ElementSymbol> insertElmnts = ResolverUtil.resolveElementsInGroup(insert.getGroup(), planner.metadata); for (ElementSymbol elementSymbol : insertElmnts) { Expression value = null; int index = insert.getVariables().indexOf(elementSymbol); if (index == -1) { value = ResolverUtil.getDefault(elementSymbol, planner.metadata); values.put(elementSymbol, value); } else { value = (Expression) insert.getValues().get(index); if (EvaluatableVisitor.isFullyEvaluatable(value, true)) { values.put(elementSymbol, value); } } } } else { validateAndPlanSubqueries(filter, container.getGroup(), planner); insert.setConstraint(filter); } } if (values != null) { if (!values.isEmpty()) { ExpressionMappingVisitor.mapExpressions(filter, values); filter = QueryRewriter.rewriteCriteria(filter, planner.context, planner.metadata); } if (filter != QueryRewriter.TRUE_CRITERIA) { if (filter == QueryRewriter.FALSE_CRITERIA || filter == QueryRewriter.UNKNOWN_CRITERIA) { throw new TeiidProcessingException(QueryPlugin.Event.TEIID31130, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31130, container)); } if (container instanceof Update) { Collection<ElementSymbol> elems = ElementCollectorVisitor.getElements(filter, true); Update update = (Update)container; if (!Collections.disjoint(elems, update.getChangeList().getClauseMap().keySet())) { validateAndPlanSubqueries(filter, container.getGroup(), planner); update.setConstraint(filter); if (compensate) { try { planner.validateRowProcessing(container); } catch (QueryPlannerException e) { throw new TeiidProcessingException(QueryPlugin.Event.TEIID31131, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31131, container)); } return QueryRewriter.createUpdateProcedure((Update)container, planner.metadata, planner.context); } } } else if (container instanceof Insert) { validateAndPlanSubqueries(filter, container.getGroup(), planner); ((Insert)container).setConstraint(filter); } } } return procedure; } /** * because of the way constraints are enforced, we cannot allow correlated references * the issues are: * - the insert may be partially specified and relevant default values may not be known * - to know the full update row, we have to use row processing * @param object * @param gs * @param planner * @throws QueryValidatorException * @throws TeiidComponentException * @throws QueryMetadataException * @throws QueryPlannerException */ private static void validateAndPlanSubqueries(LanguageObject object, GroupSymbol gs, RelationalPlanner planner) throws QueryValidatorException, QueryPlannerException, QueryMetadataException, TeiidComponentException { List<SubqueryContainer<?>> subqueries = ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(object); if (subqueries.isEmpty()) { return; } Set<GroupSymbol> groups = Collections.singleton(gs); planner.planSubqueries(null, groups, null, subqueries, true); List<Reference> refs = new LinkedList<Reference>(); CorrelatedReferenceCollectorVisitor.collectReferences(object, groups, refs, planner.metadata); if (!refs.isEmpty()) { throw new QueryValidatorException(QueryPlugin.Event.TEIID31142, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31142, object, gs)); } } private static void addFilter(ProcedureContainer container, RelationalPlanner planner, Criteria filter) { if (container instanceof FilteredCommand) { FilteredCommand fc = (FilteredCommand)container; if (fc.getCriteria() == null) { fc.setCriteria(filter); } else { fc.setCriteria(QueryRewriter.optimizeCriteria(new CompoundCriteria(Arrays.asList(fc.getCriteria(), filter)), planner.metadata)); } } } public static void checkConstraints(Command atomicCommand, Evaluator eval) throws ExpressionEvaluationException, BlockedException, TeiidComponentException, QueryProcessingException { Criteria constraint = null; HashMap<ElementSymbol, Expression> values = null; int rows = -1; if (atomicCommand instanceof Update) { Update update = (Update)atomicCommand; constraint = update.getConstraint(); if (constraint != null) { values = new HashMap<ElementSymbol, Expression>(); for (SetClause clause : update.getChangeList().getClauses()) { values.put(clause.getSymbol(), clause.getValue()); if (rows == -1) { rows = getMultiValuedSize(clause.getValue()); } } } } else if (atomicCommand instanceof Insert) { Insert insert = (Insert)atomicCommand; constraint = insert.getConstraint(); if (constraint != null) { values = new HashMap<ElementSymbol, Expression>(); if (insert.getQueryExpression() == null) { for (int i = 0; i < insert.getVariables().size(); i++) { ElementSymbol symbol = insert.getVariables().get(i); Expression value = (Expression) insert.getValues().get(i); values.put(symbol, value); if (rows == -1) { rows = getMultiValuedSize(value); } } } } } else if (atomicCommand instanceof BatchedUpdateCommand) { BatchedUpdateCommand buc = (BatchedUpdateCommand)atomicCommand; List<Command> commands = buc.getUpdateCommands(); for (Command command : commands) { checkConstraints(command, eval); } return; } if (constraint == null) { return; } if (rows != -1) { Map<ElementSymbol, Expression> currentValues = new HashMap<ElementSymbol, Expression>(); for (int i = 0; i < rows; i++) { currentValues.clear(); for (Map.Entry<ElementSymbol, Expression> entry : values.entrySet()) { ElementSymbol symbol = entry.getKey(); Expression value = entry.getValue(); if (value instanceof Constant && ((Constant)value).isMultiValued()) { Object obj = ((List<?>)((Constant)value).getValue()).get(i); value = new Constant(obj, symbol.getType()); } currentValues.put(symbol, value); } evaluateConstraint(atomicCommand, eval, constraint, currentValues); } } else { evaluateConstraint(atomicCommand, eval, constraint, values); } } private static void evaluateConstraint(Command atomicCommand, Evaluator eval, Criteria constraint, Map<ElementSymbol, Expression> values) throws BlockedException, TeiidComponentException, QueryProcessingException { Criteria clone = (Criteria) constraint.clone(); ExpressionMappingVisitor.mapExpressions(clone, values); try { if (!eval.evaluate(clone, null)) { throw new QueryProcessingException(QueryPlugin.Event.TEIID31130, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31130, atomicCommand)); } } catch (ExpressionEvaluationException e) { throw new QueryProcessingException(QueryPlugin.Event.TEIID31130, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31130, atomicCommand)); } } private static int getMultiValuedSize(Expression value) { if (value instanceof Constant && ((Constant)value).isMultiValued()) { return ((List<?>)((Constant)value).getValue()).size(); } return 1; } }