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