You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@cxf.apache.org by "Yperman Hans (112)" <Ha...@cm.be> on 2020/06/11 12:06:56 UTC

CXF and the ws security policy 1.2 draft standard

Hello everybody,

I have to consume a SOAP service with CXF/JAX-WS, and it turns out it uses the draft ws security policy 1.2.  CXF does not support this draft.  I have no control over the provider of this service.

Inspecting the WSDL, it turns out the service would be perfectly happy to use the released 1.2 version, only the namespaces in the ws-policy element are wrong.

To wit:  Wherever they use namespace http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512 , they should use http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702

My program is based on quarkus, not spring, so all config etc.. should happen programmatically.

In an attempt to deal with this, I wrote the attached code.
It

  *   looks up the CXF bus for the service,
  *   attaches a new PolicyBuilder which wraps the CXF builder
  *   In there, it only replaces getPolicy(Object)
  *   If the argument is an Element, the element is recursively rewritten with the new namespace

While this works for now, it is quite hacky:

  *   It modifies the bus for all CXF calls, not only for my service.  I fear for impact on other (non-defective) SOAP services
  *   This is my first experience with CXF, and I am not sure if I may use CXF like this.
  *   This passes a rewritten policy to the builder, but leaves the original policy unmodified

So I would like someone more knowledgeable than me to look at this, and give pointers to potential better avenues.

I know it is not fun to have  ~200 lines of code dropped on you, so a big thanks in advance.

Hans

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.Service;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.ws.policy.PolicyBuilder;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyReference;
import org.apache.neethi.PolicyRegistry;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* We have a SOAP service referencing the draft WS_SECURITY 1.2 spec and want to
* replace it with the release spec.
*
* This wraps the CXF policybuilder with one that rewrites the security policy
* if necessary
*
* @author Hans Yperman
*
*/
public class CXFPolicyBuilderWrapper implements PolicyBuilder {

            private static final boolean DUMP_CXF_HEADER = Boolean.getBoolean("debug.dumpCxfHeader");
            private static final String WS_SECURITY_1_2 = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702";
            private static final String WS_SECURITY_1_2_DRAFT = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512";

            /**
            * Start here. This adapts the given JAX-WS service to use this wrapper.
            *
            * @param service A service
            */
            public static void wrap(final Service service) {
                        // While the 'service' argument is not used, requiring it API-wise
                        // makes sure the Bus is instantiated. Besides, maybe there is a way to only
                        // adapt this service object?
                        final Bus bus = BusFactory.getThreadDefaultBus(false);
                        if (bus == null) {
                                   throw new IllegalStateException("There is no CXF bus?");
                        }

                        final PolicyBuilder pb = bus.getExtension(PolicyBuilder.class);
                        bus.setExtension(new CXFPolicyBuilderWrapper(pb), PolicyBuilder.class);
            }

            private final PolicyBuilder wrapped;

            public CXFPolicyBuilderWrapper(final PolicyBuilder wrapped) {
                        this.wrapped = wrapped;
            }

            // Not modified
            @Override
            public Policy getPolicy(final InputStream stream) throws IOException, ParserConfigurationException, SAXException {
                        return wrapped.getPolicy(stream);
            }

            /**
            * Core of this class: Replace the namespaces when requesting a policy
            */
            @Override
            public Policy getPolicy(final Object object) {
                        if (object instanceof Element) {
                                   if (DUMP_CXF_HEADER) {
                                               printDocument((Node) object);
                                   }
                                   final Node newE = rewriteNode((Node) object);
                                   if (DUMP_CXF_HEADER) {
                                               printDocument(newE);
                                   }
                                   return wrapped.getPolicy(newE);
                        }
                        return wrapped.getPolicy(object);
            }

            // Not modified
            @Override
            public PolicyReference getPolicyReference(final Object element) {
                        return wrapped.getPolicyReference(element);
            }

            // Not modified
            @Override
            public PolicyRegistry getPolicyRegistry() {
                        return wrapped.getPolicyRegistry();
            }

            /*
            * Only for debug printing
            */
            private void printDocument(final Node doc) {
                        try {
                                   final TransformerFactory tf = TransformerFactory.newInstance();
                                   final javax.xml.transform.Transformer transformer = tf.newTransformer();
                                   transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
                                   transformer.setOutputProperty(OutputKeys.METHOD, "xml");
                                   transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                                   transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                                   transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
                                   transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(System.out, "UTF-8")));
                        } catch (UnsupportedEncodingException | TransformerException e) {
                                   throw new RuntimeException(e);
                        }
            }

            /**
            * Replace namespaces in the attribute if necesary. Also in xmlns: attributes
            *
            * @param a an attribute
            * @return a new attribute
            */
            private Attr rewriteAttribute(final Attr a) {
                        final String name = a.getName();
                        final String ns = a.getNamespaceURI();
                        final Attr newA = a.getOwnerDocument().createAttributeNS(ns, name);

                        final String value = a.getValue();
                        if (name.startsWith("xmlns:")) {
                                   newA.setValue(translateNS(value));
                        } else {
                                   newA.setValue(value);
                        }
                        return newA;
            }

            /**
            * Replace namespaces in the element if necessary.
            *
            * @param e an element
            * @return a new element
            */
            private Element rewriteElement(final Element e) {
                        final Element newE = e.getOwnerDocument().createElementNS(translateNS(e.getNamespaceURI()), e.getNodeName());

                        final NamedNodeMap attr = e.getAttributes();
                        for (int i = 0; i < attr.getLength(); i++) {
                                   final Attr child = (Attr) attr.item(i);
                                   final Attr newAttribute = rewriteAttribute(child);
                                   newE.setAttributeNode(newAttribute);
                        }

                        final NodeList children = e.getChildNodes();
                        for (int i = 0; i < children.getLength(); i++) {
                                   final Node child = children.item(i);
                                   final Node newChild = rewriteNode(child);
                                   newE.appendChild(newChild);
                        }
                        return newE;
            }

            /**
            * Recursively rewrite a DOM Node, replacing namespaces if necessary.
            *
            * @param n any dom node
            * @return a new node. Always a clone, as reusing the original would remove it
            *         from the original DOM
            */
            private Node rewriteNode(final Node n) {
                        if (n instanceof Element) {
                                   return rewriteElement((Element) n);
                        } else if (n instanceof Attr) {
                                   return rewriteAttribute((Attr) n);
                        } else {
                                   return n.cloneNode(true);
                        }
            }

            /**
            * Replace all references to the DRAFT namespace with the released namespace.
            * There might be a suffix after the http url, which we leave in place
            *
            * @param ns might be null
            * @return rewritten ns
            */
            private String translateNS(final String ns) {
                        if (ns != null && ns.startsWith(WS_SECURITY_1_2_DRAFT)) {
                                   final String rest = ns.substring(WS_SECURITY_1_2_DRAFT.length());
                                   return WS_SECURITY_1_2 + rest;
                        }
                        return ns;
            }
}
[cid:image001.jpg@01CF218E.8B749620]
Hans Yperman | CM Oostende
Expert, Dienst informatica & Competence Center Ontwikkeling
Christelijke mutualiteit Oostende - Veurne - Diksmuide(112)
Ieperstraat 12 - 8400 OOSTENDE
059/55.26.35
www.cm.be<http://www.cm.be> I facebook<https://www.facebook.com/CMziekenfonds> I twitter<https://twitter.com/CMziekenfonds> I linkedin<http://www.linkedin.com/company/christelijke-mutualiteit-cm-mutualit-chr-tienne-mc-> I e-mail disclaimer<http://www.cm.be/disclaimer/e-mail-disclaimer.jsp>