/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.security.assertion; import com.eviware.soapui.SoapUI; import com.eviware.soapui.config.TestAssertionConfig; import com.eviware.soapui.impl.support.AbstractHttpRequest; import com.eviware.soapui.impl.wsdl.panels.assertions.AssertionCategoryMapping; import com.eviware.soapui.impl.wsdl.panels.assertions.AssertionListEntry; import com.eviware.soapui.impl.wsdl.support.HelpUrls; import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion; import com.eviware.soapui.impl.wsdl.teststeps.assertions.AbstractTestAssertionFactory; import com.eviware.soapui.model.TestPropertyHolder; import com.eviware.soapui.model.iface.MessageExchange; import com.eviware.soapui.model.iface.SubmitContext; import com.eviware.soapui.model.security.SecurityScan; import com.eviware.soapui.model.security.SensitiveInformationTableModel; import com.eviware.soapui.model.testsuite.Assertable; import com.eviware.soapui.model.testsuite.AssertionError; import com.eviware.soapui.model.testsuite.AssertionException; import com.eviware.soapui.model.testsuite.ResponseAssertion; import com.eviware.soapui.model.testsuite.TestProperty; import com.eviware.soapui.security.SensitiveInformationPropertyHolder; import com.eviware.soapui.support.SecurityScanUtil; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.components.JXToolBar; import com.eviware.soapui.support.swing.JTableFactory; import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder; import com.eviware.soapui.support.xml.XmlObjectConfigurationReader; import com.eviware.x.form.XFormDialog; import com.eviware.x.form.support.ADialogBuilder; import com.eviware.x.form.support.AField; import com.eviware.x.form.support.AField.AFieldType; import com.eviware.x.form.support.AForm; import org.apache.xmlbeans.XmlObject; import org.jdesktop.swingx.JXTable; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JPanel; import javax.swing.JScrollPane; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class SensitiveInfoExposureAssertion extends WsdlMessageAssertion implements ResponseAssertion { private static final String PREFIX = "~"; public static final String ID = "Sensitive Information Exposure"; public static final String LABEL = "Sensitive Information Exposure"; private List<String> assertionSpecificExposureList; private XFormDialog dialog; private static final String ASSERTION_SPECIFIC_EXPOSURE_LIST = "AssertionSpecificExposureList"; private static final String INCLUDE_GLOBAL = "IncludeGlobal"; private static final String INCLUDE_PROJECT_SPECIFIC = "IncludeProjectSpecific"; public static final String DESCRIPTION = "Checks that the last received message does not expose an sensitive information about the target system. Applicable to REST, SOAP and HTTP TestSteps."; private boolean includeGlobal; private boolean includeProjectSpecific; private JPanel sensitiveInfoTableForm; private SensitiveInformationTableModel sensitiveInformationTableModel; private JXTable tokenTable; public SensitiveInfoExposureAssertion(TestAssertionConfig assertionConfig, Assertable assertable) { super(assertionConfig, assertable, false, true, false, true); init(); } private void init() { XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader(getConfiguration()); includeGlobal = reader.readBoolean(INCLUDE_GLOBAL, true); includeProjectSpecific = reader.readBoolean(INCLUDE_PROJECT_SPECIFIC, true); assertionSpecificExposureList = StringUtils.toStringList(reader.readStrings(ASSERTION_SPECIFIC_EXPOSURE_LIST)); extractTokenTable(); } private void extractTokenTable() { SensitiveInformationPropertyHolder siph = new SensitiveInformationPropertyHolder(); for (String str : assertionSpecificExposureList) { if ("###".equals(str)) { continue; } String[] tokens = str.split("###"); if (tokens.length == 2) { siph.setPropertyValue(tokens[0], tokens[1]); } else if (tokens.length == 1) { siph.setPropertyValue(tokens[0], ""); } } sensitiveInformationTableModel = new SensitiveInformationTableModel(siph); } @Override protected String internalAssertResponse(MessageExchange messageExchange, SubmitContext context) throws AssertionException { Map<String, String> checkMap = createCheckMap(context); List<AssertionError> assertionErrorList = new ArrayList<AssertionError>(); String response = messageExchange.getResponseContent(); Set<String> messages = new HashSet<String>(); try { for (Map.Entry<String, String> tokenEntry : checkMap.entrySet()) { String token = tokenEntry.getKey(); boolean useRegexp = token.trim().startsWith(PREFIX); String description = !tokenEntry.getValue().equals("") ? tokenEntry.getValue() : token; if (useRegexp) { token = token.substring(token.indexOf(PREFIX) + 1); } String match = SecurityScanUtil.contains(context, response, token, useRegexp); if (match != null) { String message = description + " - Token [" + token + "] found [" + match + "]"; if (!messages.contains(message)) { assertionErrorList.add(new AssertionError(message)); messages.add(message); } } } } catch (Throwable e) { SoapUI.logError(e); } if (!messages.isEmpty()) { throw new AssertionException(assertionErrorList.toArray(new AssertionError[assertionErrorList.size()])); } return "OK"; } //TODO check if this should be applicable to properties after all, it's not mapped for properties currently protected String internalAssertProperty(TestPropertyHolder source, String propertyName, MessageExchange messageExchange, SubmitContext context) throws AssertionException { Map<String, String> checkMap = createCheckMap(context); List<AssertionError> assertionErrorList = new ArrayList<AssertionError>(); String propertyValue = source.getPropertyValue(propertyName); Set<String> messages = new HashSet<String>(); try { for (Map.Entry<String, String> tokenEntry : checkMap.entrySet()) { String token = tokenEntry.getKey(); boolean useRegexp = token.trim().startsWith(PREFIX); String description = !tokenEntry.getValue().equals("") ? tokenEntry.getValue() : token; if (useRegexp) { token = token.substring(token.indexOf(PREFIX) + 1); } String match = SecurityScanUtil.contains(context, propertyValue, token, useRegexp); if (match != null) { String message = description + " - Token [" + token + "] found [" + match + "] in property " + propertyName; if (!messages.contains(message)) { assertionErrorList.add(new AssertionError(message)); messages.add(message); } } } } catch (Throwable e) { SoapUI.logError(e); } if (!messages.isEmpty()) { throw new AssertionException(assertionErrorList.toArray(new AssertionError[assertionErrorList.size()])); } return "OK"; } private Map<String, String> createCheckMap(SubmitContext context) { Map<String, String> checkMap = new HashMap<String, String>(); checkMap.putAll(createMapFromTable()); if (includeProjectSpecific) { checkMap.putAll(SecurityScanUtil.projectEntriesList(this)); } if (includeGlobal) { checkMap.putAll(SecurityScanUtil.globalEntriesList()); } Map<String, String> expandedMap = propertyExpansionSupport(checkMap, context); return expandedMap; } private Map<String, String> propertyExpansionSupport(Map<String, String> checkMap, SubmitContext context) { Map<String, String> expanded = new HashMap<String, String>(); for (Map.Entry<String, String> entry : checkMap.entrySet()) { expanded.put(context.expand(entry.getKey()), context.expand(entry.getValue())); } return expanded; } public static class Factory extends AbstractTestAssertionFactory { @SuppressWarnings("unchecked") public Factory() { super(SensitiveInfoExposureAssertion.ID, SensitiveInfoExposureAssertion.LABEL, SensitiveInfoExposureAssertion.class, new Class[]{SecurityScan.class, AbstractHttpRequest.class}); } @Override public String getCategory() { return AssertionCategoryMapping.SECURITY_CATEGORY; } @Override public Class<? extends WsdlMessageAssertion> getAssertionClassType() { return SensitiveInfoExposureAssertion.class; } @Override public AssertionListEntry getAssertionListEntry() { return new AssertionListEntry(SensitiveInfoExposureAssertion.ID, SensitiveInfoExposureAssertion.LABEL, SensitiveInfoExposureAssertion.DESCRIPTION); } } @Override protected String internalAssertRequest(MessageExchange messageExchange, SubmitContext context) throws AssertionException { return null; } protected XmlObject createConfiguration() { XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder(); builder.add(ASSERTION_SPECIFIC_EXPOSURE_LIST, assertionSpecificExposureList.toArray(new String[assertionSpecificExposureList.size()])); builder.add(INCLUDE_PROJECT_SPECIFIC, includeProjectSpecific); builder.add(INCLUDE_GLOBAL, includeGlobal); return builder.finish(); } @Override public boolean configure() { if (dialog == null) { buildDialog(); } if (dialog.show()) { assertionSpecificExposureList = createListFromTable(); includeProjectSpecific = Boolean.valueOf(dialog.getFormField( SensitiveInformationConfigDialog.INCLUDE_PROJECT_SPECIFIC).getValue()); includeGlobal = Boolean.valueOf(dialog.getFormField(SensitiveInformationConfigDialog.INCLUDE_GLOBAL) .getValue()); setConfiguration(createConfiguration()); return true; } return false; } private List<String> createListFromTable() { List<String> temp = new ArrayList<String>(); for (TestProperty tp : sensitiveInformationTableModel.getHolder().getPropertyList()) { String tokenPlusDescription = tp.getName() + "###" + tp.getValue(); temp.add(tokenPlusDescription); } return temp; } private Map<String, String> createMapFromTable() { Map<String, String> temp = new HashMap<String, String>(); for (TestProperty tp : sensitiveInformationTableModel.getHolder().getPropertyList()) { temp.put(tp.getName(), tp.getValue()); } return temp; } protected void buildDialog() { dialog = ADialogBuilder.buildDialog(SensitiveInformationConfigDialog.class); dialog.setBooleanValue(SensitiveInformationConfigDialog.INCLUDE_GLOBAL, includeGlobal); dialog.setBooleanValue(SensitiveInformationConfigDialog.INCLUDE_PROJECT_SPECIFIC, includeProjectSpecific); dialog.getFormField(SensitiveInformationConfigDialog.TOKENS).setProperty("component", getForm()); } // TODO : update help URL @AForm(description = "Configure Sensitive Information Exposure Assertion", name = "Sensitive Information Exposure Assertion", helpUrl = HelpUrls.SECURITY_SENSITIVE_INFORMATION_EXPOSURE_ASSERTION_HELP) protected interface SensitiveInformationConfigDialog { @AField(description = "Sensitive informations to check. Use ~ as prefix for values that are regular expressions.", name = "Sensitive Information Tokens", type = AFieldType.COMPONENT) public final static String TOKENS = "Sensitive Information Tokens"; @AField(description = "Include project specific sensitive information configuration", name = "Project Specific", type = AFieldType.BOOLEAN) public final static String INCLUDE_PROJECT_SPECIFIC = "Project Specific"; @AField(description = "Include global sensitive information configuration", name = "Global Configuration", type = AFieldType.BOOLEAN) public final static String INCLUDE_GLOBAL = "Global Configuration"; } @Override public void release() { if (dialog != null) { dialog.release(); } super.release(); } public JPanel getForm() { if (sensitiveInfoTableForm == null) { sensitiveInfoTableForm = new JPanel(new BorderLayout()); JXToolBar toolbar = UISupport.createToolbar(); toolbar.add(UISupport.createToolbarButton(new AddTokenAction())); toolbar.add(UISupport.createToolbarButton(new RemoveTokenAction())); tokenTable = JTableFactory.getInstance().makeJXTable(sensitiveInformationTableModel); tokenTable.setPreferredSize(new Dimension(200, 100)); sensitiveInfoTableForm.add(toolbar, BorderLayout.NORTH); sensitiveInfoTableForm.add(new JScrollPane(tokenTable), BorderLayout.CENTER); } return sensitiveInfoTableForm; } class AddTokenAction extends AbstractAction { public AddTokenAction() { putValue(Action.SMALL_ICON, UISupport.createImageIcon("/add.png")); putValue(Action.SHORT_DESCRIPTION, "Adds a token to assertion"); } @Override public void actionPerformed(ActionEvent arg0) { String newToken = ""; while (newToken.trim().length() == 0) { newToken = UISupport.prompt("Enter token", "New Token", newToken); if (newToken == null) { return; } if (newToken.trim().length() == 0) { UISupport.showErrorMessage("Enter token name!"); } } String newValue = ""; newValue = UISupport.prompt("Enter description", "New Description", newValue); if (newValue == null) { newValue = ""; } sensitiveInformationTableModel.addToken(newToken, newValue); } } class RemoveTokenAction extends AbstractAction { public RemoveTokenAction() { putValue(Action.SMALL_ICON, UISupport.createImageIcon("/delete.png")); putValue(Action.SHORT_DESCRIPTION, "Removes token from assertion"); } @Override public void actionPerformed(ActionEvent arg0) { sensitiveInformationTableModel.removeRows(tokenTable.getSelectedRows()); } } }