/* * 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.dqp.internal.process; import java.util.*; import org.teiid.CommandContext; import org.teiid.PolicyDecider; import org.teiid.adminapi.DataPolicy; import org.teiid.adminapi.DataPolicy.Context; import org.teiid.adminapi.DataPolicy.PermissionType; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.core.CoreConstants; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidProcessingException; import org.teiid.dqp.internal.process.multisource.MultiSourceElement; import org.teiid.logging.AuditMessage; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.logging.MessageLevel; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.query.QueryPlugin; import org.teiid.query.function.FunctionLibrary; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Symbol; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.query.sql.visitor.GroupCollectorVisitor; import org.teiid.query.validator.AbstractValidationVisitor; public class AuthorizationValidationVisitor extends AbstractValidationVisitor { private CommandContext commandContext; private PolicyDecider decider; public AuthorizationValidationVisitor(PolicyDecider decider, CommandContext commandContext) { this.decider = decider; this.commandContext = commandContext; } // ############### Visitor methods for language objects ################## @Override public void visit(Create obj) { validateTemp(PermissionType.CREATE, obj.getTable().getNonCorrelationName(), false, obj.getTable(), Context.CREATE); } @Override public void visit(DynamicCommand obj) { if (obj.getIntoGroup() != null) { validateTemp(PermissionType.CREATE, obj.getIntoGroup().getNonCorrelationName(), false, obj.getIntoGroup(), Context.CREATE); } } @Override public void visit(AlterProcedure obj) { validateEntitlements(Arrays.asList(obj.getTarget()), DataPolicy.PermissionType.ALTER, Context.ALTER); } @Override public void visit(AlterTrigger obj) { validateEntitlements(Arrays.asList(obj.getTarget()), DataPolicy.PermissionType.ALTER, obj.isCreate()?Context.CREATE:Context.ALTER); } @Override public void visit(AlterView obj) { validateEntitlements(Arrays.asList(obj.getTarget()), DataPolicy.PermissionType.ALTER, Context.ALTER); } @Override public void visit(ObjectTable objectTable) { String language = ObjectTable.DEFAULT_LANGUAGE; if (objectTable.getScriptingLanguage() != null) { language = objectTable.getScriptingLanguage(); } Map<String, LanguageObject> map = new HashMap<String, LanguageObject>(); map.put(language, objectTable); validateEntitlements(PermissionType.LANGUAGE, Context.QUERY, map); } private void validateTemp(DataPolicy.PermissionType action, String resource, boolean schema, LanguageObject object, Context context) { Set<String> resources = Collections.singleton(resource); logRequest(resources, context); boolean allowed = decider.isTempAccessible(action, schema?resource:null, context, commandContext); logResult(resources, context, allowed); if (!allowed) { handleValidationError( QueryPlugin.Util.getString("ERR.018.005.0095", commandContext.getUserName(), "CREATE_TEMPORARY_TABLES"), //$NON-NLS-1$ //$NON-NLS-2$ Arrays.asList(object)); } } private void logRequest(Set<String> resources, Context context) { if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) { // Audit - request AuditMessage msg = new AuditMessage(context.name(), "getInaccessibleResources-request", resources.toArray(new String[resources.size()]), commandContext); //$NON-NLS-1$ LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, msg); } } @Override public void visit(Drop obj) { validateTemp(PermissionType.DROP, obj.getTable().getNonCorrelationName(), false, obj.getTable(), Context.DROP); } public void visit(Delete obj) { validateEntitlements(obj); } public void visit(Insert obj) { validateEntitlements(obj); } public void visit(Query obj) { validateEntitlements(obj); } public void visit(Update obj) { validateEntitlements(obj); } public void visit(StoredProcedure obj) { validateEntitlements(obj); } public void visit(Function obj) { if (FunctionLibrary.LOOKUP.equalsIgnoreCase(obj.getName())) { try { ResolverUtil.ResolvedLookup lookup = ResolverUtil.resolveLookup(obj, this.getMetadata()); List<Symbol> symbols = new LinkedList<Symbol>(); symbols.add(lookup.getGroup()); symbols.add(lookup.getKeyElement()); symbols.add(lookup.getReturnElement()); validateEntitlements(symbols, DataPolicy.PermissionType.READ, Context.QUERY); } catch (TeiidComponentException e) { handleException(e, obj); } catch (TeiidProcessingException e) { handleException(e, obj); } } else { String schema = obj.getFunctionDescriptor().getSchema(); if (schema != null && !isSystemSchema(schema)) { Map<String, Function> map = new HashMap<String, Function>(); map.put(obj.getFunctionDescriptor().getFullName(), obj); validateEntitlements(PermissionType.EXECUTE, Context.FUNCTION, map); } } } // ######################### Validation methods ######################### /** * Validate insert/merge entitlements */ protected void validateEntitlements(Insert obj) { List<LanguageObject> insert = new LinkedList<LanguageObject>(); insert.add(obj.getGroup()); insert.addAll(obj.getVariables()); validateEntitlements( insert, DataPolicy.PermissionType.CREATE, Context.INSERT); if (obj.isUpsert()) { validateEntitlements( insert, DataPolicy.PermissionType.UPDATE, Context.MERGE); } } /** * Validate update entitlements */ protected void validateEntitlements(Update obj) { // Check that all elements used in criteria have read permission HashSet<ElementSymbol> elements = new HashSet<ElementSymbol>(); ElementCollectorVisitor.getElements(obj.getChangeList().getClauseMap().values(), elements); if (obj.getCriteria() != null) { ElementCollectorVisitor.getElements(obj.getCriteria(), elements); } validateEntitlements( elements, DataPolicy.PermissionType.READ, Context.UPDATE); // The variables from the changes must be checked for UPDATE entitlement // validateEntitlements on all the variables used in the update. List<LanguageObject> updated = new LinkedList<LanguageObject>(); updated.add(obj.getGroup()); updated.addAll(obj.getChangeList().getClauseMap().keySet()); validateEntitlements(updated, DataPolicy.PermissionType.UPDATE, Context.UPDATE); } /** * Validate delete entitlements */ protected void validateEntitlements(Delete obj) { // Check that all elements used in criteria have read permission if (obj.getCriteria() != null) { validateEntitlements( ElementCollectorVisitor.getElements(obj.getCriteria(), true), DataPolicy.PermissionType.READ, Context.DELETE); } // Check that all elements of group being deleted have delete permission validateEntitlements(Arrays.asList(obj.getGroup()), DataPolicy.PermissionType.DELETE, Context.DELETE); } /** * Validate query entitlements */ protected void validateEntitlements(Query obj) { // If query contains SELECT INTO, validate INTO portion Into intoObj = obj.getInto(); if ( intoObj != null ) { GroupSymbol intoGroup = intoObj.getGroup(); Collection<LanguageObject> intoElements = new LinkedList<LanguageObject>(); intoElements.add(intoGroup); try { intoElements.addAll(ResolverUtil.resolveElementsInGroup(intoGroup, getMetadata())); } catch (QueryMetadataException err) { handleException(err, intoGroup); } catch (TeiidComponentException err) { handleException(err, intoGroup); } validateEntitlements(intoElements, DataPolicy.PermissionType.CREATE, Context.INSERT); } // Validate this query's entitlements Collection<LanguageObject> entitledObjects = new ArrayList<LanguageObject>(GroupCollectorVisitor.getGroupsIgnoreInlineViews(obj, true)); if (!isXMLCommand(obj)) { entitledObjects.addAll(ElementCollectorVisitor.getElements(obj, true)); } if(entitledObjects.size() == 0) { return; } validateEntitlements(entitledObjects, DataPolicy.PermissionType.READ, Context.QUERY); } /** * Validate query entitlements */ protected void validateEntitlements(StoredProcedure obj) { validateEntitlements(Arrays.asList(obj.getGroup()), DataPolicy.PermissionType.EXECUTE, Context.STORED_PROCEDURE); } /** * Check that the user is entitled to access all data elements in the command. * * @param symbols The collection of <code>Symbol</code>s affected by these actions. * @param actionCode The actions to validate for * @param auditContext The {@link AuthorizationService} to use when resource auditing is done. */ protected void validateEntitlements(Collection<? extends LanguageObject> symbols, DataPolicy.PermissionType actionCode, Context auditContext) { Map<String, LanguageObject> nameToSymbolMap = new LinkedHashMap<String, LanguageObject>(); for (LanguageObject symbol : symbols) { try { Object metadataID = null; if(symbol instanceof ElementSymbol) { metadataID = ((ElementSymbol)symbol).getMetadataID(); if (metadataID instanceof MultiSourceElement || metadataID instanceof TempMetadataID) { continue; } } else if(symbol instanceof GroupSymbol) { GroupSymbol group = (GroupSymbol)symbol; metadataID = group.getMetadataID(); if (metadataID instanceof TempMetadataID) { if (group.isProcedure()) { Map<String, LanguageObject> procMap = new LinkedHashMap<String, LanguageObject>(); addToNameMap(((TempMetadataID)metadataID).getOriginalMetadataID(), symbol, procMap, getMetadata()); validateEntitlements(PermissionType.EXECUTE, auditContext, procMap); } else if (group.isTempTable() && group.isImplicitTempGroupSymbol()) { validateTemp(actionCode, group.getNonCorrelationName(), false, group, auditContext); } continue; } } addToNameMap(metadataID, symbol, nameToSymbolMap, getMetadata()); } catch(QueryMetadataException e) { handleException(e); } catch(TeiidComponentException e) { handleException(e); } } validateEntitlements(actionCode, auditContext, nameToSymbolMap); } static void addToNameMap(Object metadataID, LanguageObject symbol, Map<String, LanguageObject> nameToSymbolMap, QueryMetadataInterface metadata) throws QueryMetadataException, TeiidComponentException { String fullName = metadata.getFullName(metadataID); Object modelId = metadata.getModelID(metadataID); String modelName = metadata.getFullName(modelId); if (!isSystemSchema(modelName)) { //foreign temp table full names are not schema qualified by default if (!metadata.isVirtualModel(modelId)) { GroupSymbol group = null; if (symbol instanceof ElementSymbol) { group = ((ElementSymbol)symbol).getGroupSymbol(); } else if (symbol instanceof GroupSymbol) { group = (GroupSymbol)symbol; } if (group != null && group.isTempGroupSymbol() && !group.isGlobalTable()) { fullName = modelName + AbstractMetadataRecord.NAME_DELIM_CHAR + modelId; } } nameToSymbolMap.put(fullName, symbol); } } static private boolean isSystemSchema(String modelName) { return CoreConstants.SYSTEM_MODEL.equalsIgnoreCase(modelName) || CoreConstants.ODBC_MODEL.equalsIgnoreCase(modelName); } private void validateEntitlements(DataPolicy.PermissionType actionCode, Context auditContext, Map<String, ? extends LanguageObject> nameToSymbolMap) { if (nameToSymbolMap.isEmpty()) { return; } Collection<String> inaccessibleResources = getInaccessibleResources(actionCode, nameToSymbolMap.keySet(), auditContext); if(inaccessibleResources.isEmpty()) { return; } List<LanguageObject> inaccessibleSymbols = new ArrayList<LanguageObject>(inaccessibleResources.size()); for (String name : inaccessibleResources) { inaccessibleSymbols.add(nameToSymbolMap.get(name)); } // CASE 2362 - do not include the names of the elements for which the user // is not authorized in the exception message handleValidationError( QueryPlugin.Util.getString("ERR.018.005.0095", commandContext.getUserName(), actionCode), //$NON-NLS-1$ inaccessibleSymbols); } /** * Out of the resources specified, return the subset for which the specified not have authorization to access. */ public Set<String> getInaccessibleResources(DataPolicy.PermissionType action, Set<String> resources, Context context) { logRequest(resources, context); Set<String> results = decider.getInaccessibleResources(action, resources, context, commandContext); logResult(resources, context, results.isEmpty()); return results; } private void logResult(Set<String> resources, Context context, boolean granted) { if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) { if (granted) { AuditMessage msg = new AuditMessage(context.name(), "getInaccessibleResources-granted all", resources.toArray(new String[resources.size()]), commandContext); //$NON-NLS-1$ LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, msg); } else { AuditMessage msg = new AuditMessage(context.name(), "getInaccessibleResources-denied", resources.toArray(new String[resources.size()]), commandContext); //$NON-NLS-1$ LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, msg); } } } }