/*
* 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.impl.wsdl.support.wss.entries;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.WSSEntryConfig;
import com.eviware.soapui.impl.wsdl.support.wss.OutgoingWss;
import com.eviware.soapui.impl.wsdl.support.wss.WssCrypto;
import com.eviware.soapui.impl.wsdl.support.wss.saml.callback.SAML1CallbackHandler;
import com.eviware.soapui.impl.wsdl.support.wss.saml.callback.SAML2CallbackHandler;
import com.eviware.soapui.impl.wsdl.support.wss.saml.callback.SAMLCallbackHandler;
import com.eviware.soapui.impl.wsdl.support.wss.support.KeystoresComboBoxModel;
import com.eviware.soapui.impl.wsdl.support.wss.support.SAMLAttributeValuesTable;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionsResult;
import com.eviware.soapui.support.components.SimpleBindingForm;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
import com.google.common.base.Strings;
import com.jgoodies.binding.PresentationModel;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecSAMLToken;
import org.apache.ws.security.saml.WSSecSignatureSAML;
import org.apache.ws.security.saml.ext.AssertionWrapper;
import org.apache.ws.security.saml.ext.SAMLParms;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.apache.xml.security.signature.XMLSignature;
import org.w3c.dom.Document;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
/**
* @author Erik R. Yverling
* <p/>
* Used to generate a SAML assertion using various input components
*/
public class AutomaticSAMLEntry extends WssEntryBase {
public static final String TYPE = "SAML (Form)";
public static final String SAML_VERSION_1 = "1.1";
public static final String SAML_VERSION_2 = "2.0";
public static final String AUTHENTICATION_ASSERTION_TYPE = "Authentication";
public static final String ATTRIBUTE_ASSERTION_TYPE = "Attribute";
public static final String AUTHORIZATION_ASSERTION_TYPE = "Authorization";
public static final String ATTRIBUTE_VALUES_VALUE_COLUMN = "value";
public static final String HOLDER_OF_KEY_CONFIRMATION_METHOD = "Holder-of-key";
public static final String SENDER_VOUCHES_CONFIRMATION_METHOD = "Sender vouches";
private static final String NOT_A_VALID_SAML_VERSION = "Not a valid SAML version";
private KeyAliasComboBoxModel keyAliasComboBoxModel;
private InternalWssContainerListener wssContainerListener;
private String samlVersion;
private String assertionType;
private String confirmationMethod;
private String crypto;
private String issuer;
private String subjectName;
private String subjectQualifier;
private String digestAlgorithm;
private String signatureAlgorithm;
private boolean signed;
private String attributeName;
private List<StringToStringMap> attributeValues;
private SimpleBindingForm form;
private JCheckBox signedCheckBox;
private JComboBox confirmationMethodComboBox;
private JComboBox cryptoComboBox;
private JComboBox keyAliasComboBox;
private JPasswordField passwordField;
private JTextField attributeNameTextField;
private SAMLAttributeValuesTable samlAttributeValuesTable;
public void init(WSSEntryConfig config, OutgoingWss container) {
super.init(config, container, TYPE);
}
// FIXME How can we make FindBugs that these fields will always be initialized and be able to add NonNull annotations?
@Override
protected void load(XmlObjectConfigurationReader reader) {
samlVersion = reader.readString("samlVersion", SAML_VERSION_1);
signed = reader.readBoolean("signed", false);
assertionType = reader.readString("assertionType", AUTHENTICATION_ASSERTION_TYPE);
confirmationMethod = reader.readString("confirmationMethod", SENDER_VOUCHES_CONFIRMATION_METHOD);
crypto = reader.readString("crypto", null);
issuer = reader.readString("issuer", null);
subjectName = reader.readString("subjectName", null);
subjectQualifier = reader.readString("subjectQualifier", null);
digestAlgorithm = reader.readString("digestAlgorithm", MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA1);
signatureAlgorithm = reader.readString("signatureAlgorithm", WSConstants.RSA);
attributeName = reader.readString("attributeName", null);
attributeValues = readTableValues(reader, "attributeValues");
}
@Override
protected void save(XmlObjectConfigurationBuilder builder) {
builder.add("samlVersion", samlVersion);
builder.add("signed", signed);
builder.add("assertionType", assertionType);
builder.add("confirmationMethod", confirmationMethod);
builder.add("crypto", crypto);
builder.add("issuer", issuer);
builder.add("subjectName", subjectName);
builder.add("subjectQualifier", subjectQualifier);
builder.add("digestAlgorithm", digestAlgorithm);
builder.add("signatureAlgorithm", signatureAlgorithm);
builder.add("attributeName", attributeName);
saveTableValues(builder, attributeValues, "attributeValues");
}
@Override
protected JComponent buildUI() {
wssContainerListener = new InternalWssContainerListener();
getWssContainer().addWssContainerListener(wssContainerListener);
form = new SimpleBindingForm(new PresentationModel<SignatureEntry>(this));
form.addSpace(5);
form.appendComboBox("samlVersion", "SAML version", new String[]{SAML_VERSION_1, SAML_VERSION_2},
"Choose the SAML version");
signedCheckBox = form.appendCheckBox("signed", "Signed", null);
signedCheckBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
checkSigned();
}
});
form.appendComboBox("assertionType", "Assertion type",
new String[]{AUTHENTICATION_ASSERTION_TYPE, ATTRIBUTE_ASSERTION_TYPE, AUTHORIZATION_ASSERTION_TYPE},
"Choose the type of assertion").addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
checkAssertionType();
}
});
confirmationMethodComboBox = form.appendComboBox("confirmationMethod", "Confirmation method",
new String[]{SENDER_VOUCHES_CONFIRMATION_METHOD}, "Choose the confirmation method");
cryptoComboBox = form.appendComboBox("crypto", "Keystore", new KeystoresComboBoxModel(getWssContainer(),
getWssContainer().getCryptoByName(crypto), true),
"Selects the Keystore containing the key to use for signing the SAML message");
cryptoComboBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
// FIXME This cases the drop down to be blank when changing keystore
keyAliasComboBoxModel.update(getWssContainer().getCryptoByName(crypto));
}
});
keyAliasComboBoxModel = new KeyAliasComboBoxModel(getWssContainer().getCryptoByName(crypto));
keyAliasComboBox = form.appendComboBox("username", "Alias", keyAliasComboBoxModel,
"The alias for the key to use for encryption");
passwordField = form.appendPasswordField("password", "Password", "The certificate password");
form.appendTextField("issuer", "Issuer", "The issuer");
form.appendTextField("subjectName", "Subject Name", "The subject qualifier");
form.appendTextField("subjectQualifier", "Subject Qualifier", "The subject qualifier");
form.appendComboBox("digestAlgorithm", "Digest Algorithm", new String[]{
MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA1, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256,
MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA384, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA512},
"Set the digest algorithm to use");
form.appendComboBox("signatureAlgorithm", "Signature Algorithm", new String[]{WSConstants.RSA,
WSConstants.DSA, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384,
XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512, XMLSignature.ALGO_ID_MAC_HMAC_SHA1,
XMLSignature.ALGO_ID_MAC_HMAC_SHA256, XMLSignature.ALGO_ID_MAC_HMAC_SHA384,
XMLSignature.ALGO_ID_MAC_HMAC_SHA512}, "Set the name of the signature encryption algorithm to use");
attributeNameTextField = form.appendTextField("attributeName", "Attribute name", "The name of the attribute");
samlAttributeValuesTable = new SAMLAttributeValuesTable(attributeValues, this);
form.append("Attribute values", samlAttributeValuesTable);
initComponentsEnabledState();
return new JScrollPane(form.getPanel());
}
private void initComponentsEnabledState() {
checkSigned();
checkAssertionType();
}
private void checkSigned() {
if (!signed) {
form.setComboBoxItems("confirmationMethod", confirmationMethodComboBox,
new String[]{SENDER_VOUCHES_CONFIRMATION_METHOD});
confirmationMethodComboBox.setSelectedIndex(0);
cryptoComboBox.setEnabled(false);
keyAliasComboBox.setEnabled(false);
passwordField.setEnabled(false);
} else {
form.setComboBoxItems("confirmationMethod", confirmationMethodComboBox, new String[]{
SENDER_VOUCHES_CONFIRMATION_METHOD, HOLDER_OF_KEY_CONFIRMATION_METHOD});
cryptoComboBox.setEnabled(true);
keyAliasComboBox.setEnabled(true);
passwordField.setEnabled(true);
}
}
private void checkAssertionType() {
if (assertionType.equals(AUTHORIZATION_ASSERTION_TYPE)) {
signed = false;
signedCheckBox.setSelected(false);
signedCheckBox.setEnabled(false);
} else {
signedCheckBox.setEnabled(true);
}
if (assertionType.equals(ATTRIBUTE_ASSERTION_TYPE)) {
attributeNameTextField.setEnabled(true);
samlAttributeValuesTable.setEnabled(true);
} else {
attributeNameTextField.setEnabled(false);
samlAttributeValuesTable.setEnabled(false);
}
}
public void process(WSSecHeader secHeader, Document doc, PropertyExpansionContext context) {
try {
SAMLParms samlParms = new SAMLParms();
SAMLCallbackHandler callbackHandler = null;
if (!signed) {
WSSecSAMLToken wsSecSAMLToken = new WSSecSAMLToken();
if (samlVersion.equals(SAML_VERSION_1)) {
callbackHandler = new SAML1CallbackHandler(assertionType, confirmationMethod);
} else if (samlVersion.equals(SAML_VERSION_2)) {
callbackHandler = new SAML2CallbackHandler(assertionType, confirmationMethod);
} else {
throw new IllegalArgumentException(NOT_A_VALID_SAML_VERSION);
}
AssertionWrapper assertion = createAssertion(context, samlParms, callbackHandler);
wsSecSAMLToken.build(doc, assertion, secHeader);
} else {
WSSecSignatureSAML wsSecSignatureSAML = new WSSecSignatureSAML();
WssCrypto wssCrypto = getWssContainer().getCryptoByName(crypto, true);
String alias = context.expand(getUsername());
if (wssCrypto == null) {
throw new RuntimeException("Missing keystore [" + crypto + "] for signature entry");
} else if (Strings.isNullOrEmpty(alias)) {
throw new RuntimeException(" No alias was provided for the keystore '" + crypto + "'. Please check your SAML (Form) configurations");
}
if (samlVersion.equals(SAML_VERSION_1)) {
callbackHandler = new SAML1CallbackHandler(wssCrypto.getCrypto(), alias,
assertionType, confirmationMethod);
} else if (samlVersion.equals(SAML_VERSION_2)) {
callbackHandler = new SAML2CallbackHandler(wssCrypto.getCrypto(), alias,
assertionType, confirmationMethod);
} else {
throw new IllegalArgumentException(NOT_A_VALID_SAML_VERSION);
}
AssertionWrapper assertion = createAssertion(context, samlParms, callbackHandler);
assertion.signAssertion(context.expand(getUsername()), context.expand(getPassword()),
wssCrypto.getCrypto(), false);
wsSecSignatureSAML.setUserInfo(context.expand(getUsername()), context.expand(getPassword()));
if (confirmationMethod.equals(SENDER_VOUCHES_CONFIRMATION_METHOD)) {
wsSecSignatureSAML.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
wsSecSignatureSAML.build(doc, null, assertion, wssCrypto.getCrypto(), context.expand(getUsername()),
context.expand(getPassword()), secHeader);
} else if (confirmationMethod.equals(HOLDER_OF_KEY_CONFIRMATION_METHOD)) {
wsSecSignatureSAML.setDigestAlgo(digestAlgorithm);
if (assertionType.equals(AUTHENTICATION_ASSERTION_TYPE)) {
wsSecSignatureSAML.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
wsSecSignatureSAML.setSignatureAlgorithm(signatureAlgorithm);
} else if (assertionType.equals(ATTRIBUTE_ASSERTION_TYPE)) {
wsSecSignatureSAML.setKeyIdentifierType(WSConstants.X509_KEY_IDENTIFIER);
wsSecSignatureSAML.setSignatureAlgorithm(signatureAlgorithm);
byte[] ephemeralKey = callbackHandler.getEphemeralKey();
wsSecSignatureSAML.setSecretKey(ephemeralKey);
}
wsSecSignatureSAML.build(doc, wssCrypto.getCrypto(), assertion, null, null, null, secHeader);
}
}
} catch (Exception e) {
SoapUI.logError(e);
}
}
private AssertionWrapper createAssertion(PropertyExpansionContext context, SAMLParms samlParms,
SAMLCallbackHandler callbackHandler) throws WSSecurityException {
if (assertionType.equals(ATTRIBUTE_ASSERTION_TYPE)) {
callbackHandler.setCustomAttributeName(context.expand(attributeName));
callbackHandler.setCustomAttributeValues(extractValueColumnValues(attributeValues, context));
}
callbackHandler.setIssuer(context.expand(issuer));
callbackHandler.setSubjectName(context.expand(subjectName));
callbackHandler.setSubjectQualifier(context.expand(subjectQualifier));
samlParms.setCallbackHandler(callbackHandler);
return new AssertionWrapper(samlParms);
}
// Since we only use one column for the attribute values
private List<String> extractValueColumnValues(List<StringToStringMap> table, PropertyExpansionContext context) {
List<String> firstColumnValues = new ArrayList<String>();
for (StringToStringMap row : table) {
String columnValue = row.get(ATTRIBUTE_VALUES_VALUE_COLUMN);
// TODO Add property expansion to each value
firstColumnValues.add(columnValue);
}
return firstColumnValues;
}
public void relase() {
if (wssContainerListener != null) {
getWssContainer().removeWssContainerListener(wssContainerListener);
}
}
@Override
protected void addPropertyExpansions(PropertyExpansionsResult result) {
super.addPropertyExpansions(result);
result.extractAndAddAll(this, "issuer");
result.extractAndAddAll(this, "subjectName");
result.extractAndAddAll(this, "subjectQualifier");
result.extractAndAddAll(this, "attributeName");
// TODO Add property expansion refactoring for attributesValues, as with HttpTestRequestStep
}
public String getSamlVersion() {
return samlVersion;
}
public void setSamlVersion(String samlVersion) {
this.samlVersion = samlVersion;
saveConfig();
}
public String getAssertionType() {
return assertionType;
}
public void setAssertionType(String assertionType) {
this.assertionType = assertionType;
saveConfig();
}
public String getConfirmationMethod() {
return confirmationMethod;
}
public void setConfirmationMethod(String confirmationMethod) {
this.confirmationMethod = confirmationMethod;
saveConfig();
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
saveConfig();
}
public String getCrypto() {
return crypto;
}
public void setCrypto(String crypto) {
this.crypto = crypto;
saveConfig();
}
public String getSubjectName() {
return subjectName;
}
public void setSubjectName(String subjectName) {
this.subjectName = subjectName;
saveConfig();
}
public String getSubjectQualifier() {
return subjectQualifier;
}
public void setSubjectQualifier(String subjectQualifier) {
this.subjectQualifier = subjectQualifier;
saveConfig();
}
public String getDigestAlgorithm() {
return digestAlgorithm;
}
public void setDigestAlgorithm(String digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
saveConfig();
}
public String getSignatureAlgorithm() {
return signatureAlgorithm;
}
public void setSignatureAlgorithm(String signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
saveConfig();
}
public boolean isSigned() {
return signed;
}
public void setSigned(boolean signed) {
this.signed = signed;
saveConfig();
}
public String getAttributeName() {
return attributeName;
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
saveConfig();
}
public List<StringToStringMap> getAttributeValues() {
return attributeValues;
}
public void setAttributeValues(List<StringToStringMap> attributeValues) {
this.attributeValues = attributeValues;
saveConfig();
}
private final class InternalWssContainerListener extends WssContainerListenerAdapter {
@Override
public void cryptoUpdated(WssCrypto crypto) {
if (crypto.getLabel().equals(getCrypto())) {
keyAliasComboBoxModel.update(crypto);
}
}
}
}