/*
* 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());
}
}
}