View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  package com.eviware.soapui.impl.wsdl.support.wsa;
13  
14  import org.apache.xmlbeans.XmlException;
15  import org.apache.xmlbeans.XmlObject;
16  import org.w3c.dom.Element;
17  import org.w3c.dom.Node;
18  import org.w3c.dom.NodeList;
19  
20  import com.eviware.soapui.SoapUI;
21  import com.eviware.soapui.config.AnonymousTypeConfig;
22  import com.eviware.soapui.impl.wsdl.WsdlOperation;
23  import com.eviware.soapui.impl.wsdl.panels.teststeps.support.WsaAssertionConfiguration;
24  import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
25  import com.eviware.soapui.impl.wsdl.support.soap.SoapUtils;
26  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
27  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
28  import com.eviware.soapui.model.testsuite.AssertionError;
29  import com.eviware.soapui.model.testsuite.AssertionException;
30  import com.eviware.soapui.support.StringUtils;
31  import com.eviware.soapui.support.xml.XmlUtils;
32  
33  /***
34   * Validating class for WS Addressing implemented according to WSDL 1.1
35   * specification
36   * 
37   * @author dragica.soldo
38   * @see {@link}http://www.w3.org/TR/2006/WD-ws-addr-wsdl-20060216/#WSDL11MEPS
39   */
40  public class WsaValidator
41  {
42  	WsdlMessageExchange messageExchange;
43  	Element header;
44  	String wsaVersionNameSpace;
45  	StringBuilder cumulativeErrorMsg;
46  	WsaAssertionConfiguration wsaAssertionConfiguration;
47  
48  	public WsaValidator(WsdlMessageExchange messageExchange, WsaAssertionConfiguration wsaAssertionConfiguration)
49  	{
50  		this.messageExchange = messageExchange;
51  		this.wsaAssertionConfiguration = wsaAssertionConfiguration;
52  		cumulativeErrorMsg = new StringBuilder();
53  	}
54  
55  	public static String getWsaVersion(XmlObject contentObject, SoapVersion soapVersion) 
56  	{
57  		String wsaVns = null;
58  		try
59  		{
60  //			XmlObject xmlObject = XmlObject.Factory.parse( content );
61  			XmlObject[] envS = contentObject.selectChildren( soapVersion.getEnvelopeQName() );
62  			Element envelope = (Element) envS[0].getDomNode();
63  
64  			Element hdr = (Element) SoapUtils.getHeaderElement( contentObject, soapVersion, true ).getDomNode();
65  
66  			if( !hdr.hasChildNodes() )
67  			{
68  			   return null;
69  			}
70  
71  			String wsaNameSpace = XmlUtils.findPrefixForNamespace(hdr, WsaUtils.WS_A_VERSION_200508 );
72  			if (wsaNameSpace != null)
73  			{
74  				wsaVns = WsaUtils.WS_A_VERSION_200508;
75  			} else {
76  				wsaNameSpace = XmlUtils.findPrefixForNamespace(hdr, WsaUtils.WS_A_VERSION_200408 );
77  				if (wsaNameSpace != null)
78  				{
79  					wsaVns = WsaUtils.WS_A_VERSION_200408;
80  				} else {
81  					wsaNameSpace = XmlUtils.findPrefixForNamespace(envelope, WsaUtils.WS_A_VERSION_200508 );
82  					if (wsaNameSpace != null)
83  					{
84  						wsaVns = WsaUtils.WS_A_VERSION_200508;
85  					} else {
86  						wsaNameSpace = XmlUtils.findPrefixForNamespace(envelope, WsaUtils.WS_A_VERSION_200408 );
87  						if (wsaNameSpace != null)
88  						{
89  							wsaVns = WsaUtils.WS_A_VERSION_200408;
90  						} else {
91  							return null;
92  						}
93  					}
94  				}
95  			}
96  		}
97  		catch (XmlException e)
98  		{
99  			SoapUI.logError(e);
100 		}
101 		return wsaVns;
102 	}
103 	private void validateWsAddressingCommon( String content ) 
104    {
105          if (wsaAssertionConfiguration.isAssertTo())
106 		 {
107      		Element toNode;
108         	 if (wsaVersionNameSpace != null) {
109      			toNode = XmlUtils.getFirstChildElementNS(header, wsaVersionNameSpace, "To");
110      			parseToNode(toNode);
111     		} else {
112     			toNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200508, "To");
113     			if (toNode == null) {
114     				toNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200408, "To");
115     			}
116 				parseToNode(toNode);
117     		}
118 		 }
119 		 // if fault_to is specified check if anonymous allowed; faultTo is never asserted
120          Element faultToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "FaultTo" );
121          if( faultToNode != null )
122          {
123             Element addressNode = XmlUtils.getFirstChildElementNS( faultToNode, wsaVersionNameSpace, "Address" );
124             if( addressNode != null )
125             {
126                String faultToAddressValue = XmlUtils.getElementText( addressNode );
127                if( !StringUtils.isNullOrEmpty( faultToAddressValue ) )
128                {
129                   // check for anonymous
130                   if( AnonymousTypeConfig.PROHIBITED.toString().equals( messageExchange.getOperation().getAnonymous() )
131                           && WsaUtils.isAnonymousAddress(faultToAddressValue,wsaVersionNameSpace) )
132                   {
133                   	cumulativeErrorMsg.append("WS-A InvalidAddressingHeader FaultTo , Anonymous addresses are prohibited. ");
134                   } else if (AnonymousTypeConfig.REQUIRED.toString().equals( ((WsdlMessageExchange) messageExchange).getOperation().getAnonymous() )
135                         && !(WsaUtils.isAnonymousAddress(faultToAddressValue,wsaVersionNameSpace) || WsaUtils.isNoneAddress(faultToAddressValue,wsaVersionNameSpace)))
136                   {
137                   	cumulativeErrorMsg.append("WS-A InvalidAddressingHeader FaultTo , Anonymous addresses are required. ");
138                   }
139                }
140             }
141          }
142 
143    }
144 
145 	private void parseToNode(Element toNode) {
146 		if (toNode == null)
147 		{
148 			cumulativeErrorMsg.append("WS-A To property is not specified. ");
149 		}
150 		else
151 		{
152 			String toAddressValue = XmlUtils.getElementText(toNode);
153 			if (StringUtils.isNullOrEmpty(toAddressValue))
154 			{
155 				cumulativeErrorMsg.append("WS-A To property is empty. ");
156 			}
157 			else
158 			{
159 				// check for anonymous - in case of mock response to=request.replyTo
160 				if (AnonymousTypeConfig.PROHIBITED.toString().equals(messageExchange.getOperation().getAnonymous())
161 						&& WsaUtils.isAnonymousAddress(toAddressValue, wsaVersionNameSpace))
162 				{
163 					cumulativeErrorMsg
164 							.append("WS-A InvalidAddressingHeader To , Anonymous addresses are prohibited. ");
165 				}
166 			}
167 		}
168 	}
169 
170 
171 	public void validateWsAddressingRequest() throws AssertionException, XmlException
172 	{
173 		String content = messageExchange.getRequestContent();
174 		SoapVersion soapVersion = messageExchange.getOperation().getInterface()
175       .getSoapVersion();
176 
177 		XmlObject xmlObject = XmlObject.Factory.parse( content );
178 		header = (Element) SoapUtils.getHeaderElement( xmlObject, soapVersion, true ).getDomNode();
179 		
180 		wsaVersionNameSpace = getWsaVersion(xmlObject, soapVersion);
181 		//not checking because of possibility of having wsaVersionNamespace specified inside property tag itself
182 //		if (wsaVersionNameSpace == null)
183 //		{
184 //			throw new AssertionException( new AssertionError( "WS-A not enabled" ) );
185 //		}
186 
187 		WsdlOperation operation = messageExchange.getOperation();
188 
189       if (wsaAssertionConfiguration.isAssertAction())
190 		{
191 			assertProperty("Wsa:Action", "Action");
192 		}
193 		validateWsAddressingCommon(content);
194 		if (operation.isRequestResponse())
195 		{
196 			if (wsaAssertionConfiguration.isAssertMessageId()) {
197 				// MessageId is Mandatory
198 				assertProperty("Wsa:MessageId", "MessageId");
199 			}
200 			if (wsaAssertionConfiguration.isAssertReplyTo()) {
201 				// ReplyTo is Mandatory
202 				Element replyToNode;
203 				String currentTagWsaNs;
204 				if (!StringUtils.isNullOrEmpty(wsaVersionNameSpace)) {
205 					replyToNode = XmlUtils.getFirstChildElementNS(header, wsaVersionNameSpace, "ReplyTo");
206 					parseReplyToNode(replyToNode, wsaVersionNameSpace);
207 				} else {
208 					replyToNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200508, "ReplyTo");
209 					currentTagWsaNs = WsaUtils.WS_A_VERSION_200508;
210 	    			if (replyToNode == null) {
211 	    				replyToNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200408, "ReplyTo");
212 	    				currentTagWsaNs = WsaUtils.WS_A_VERSION_200408;
213 	    			}
214 	    			parseReplyToNode(replyToNode, currentTagWsaNs);
215 				}
216 			}
217 		}
218       String cumulativeError = cumulativeErrorMsg.toString();
219       if (!StringUtils.isNullOrEmpty(cumulativeError))
220 		{
221          throw new AssertionException(new AssertionError(cumulativeError));
222 		}
223 	}
224 
225 	private void parseReplyToNode(Element replyToNode, String wsaNsStr) {
226 		if (replyToNode == null) {
227 			cumulativeErrorMsg
228 					.append("WS-A ReplyTo property is not specified. ");
229 		} else {
230 			Element addressNode = XmlUtils.getFirstChildElementNS(
231 					replyToNode, wsaNsStr, "Address");
232 			if (addressNode == null) {
233 				cumulativeErrorMsg
234 						.append("WS-A ReplyTo Address property is not specified. ");
235 			} else {
236 				String replyToAddressValue = XmlUtils
237 						.getElementText(addressNode);
238 				if (StringUtils.isNullOrEmpty(replyToAddressValue)) {
239 					cumulativeErrorMsg
240 							.append("WS-A ReplyTo Address property is empty. ");
241 				} else {
242 					// check for anonymous
243 					if (AnonymousTypeConfig.PROHIBITED.toString().equals(
244 							((WsdlMessageExchange) messageExchange)
245 									.getOperation().getAnonymous())
246 							&& WsaUtils.isAnonymousAddress(replyToAddressValue,
247 									wsaNsStr)) {
248 						cumulativeErrorMsg
249 								.append("WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are prohibited. ");
250 					} else if (AnonymousTypeConfig.REQUIRED.toString().equals(
251 							((WsdlMessageExchange) messageExchange)
252 									.getOperation().getAnonymous())
253 							&& !(WsaUtils.isAnonymousAddress(
254 									replyToAddressValue, wsaNsStr) || WsaUtils
255 									.isNoneAddress(replyToAddressValue,
256 											wsaNsStr))) {
257 						cumulativeErrorMsg
258 								.append("WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are required. ");
259 					}
260 				}
261 			}
262 		}
263 	}
264 
265 	private void parseMessageIdNode(Element msgNode) {
266 		if (msgNode == null) {
267 			cumulativeErrorMsg
268 					.append("WS-A MessageID property is not specified. ");
269 		} else {
270 			String msgValue = XmlUtils.getElementText(msgNode);
271 			if (StringUtils.isNullOrEmpty(msgValue)) {
272 				cumulativeErrorMsg
273 						.append("WS-A MessageID property is empty");
274 			}
275 		}
276 	}
277 
278 	public void validateWsAddressingResponse() throws AssertionException, XmlException
279 	{
280 		String content = messageExchange.getResponseContent();
281 		SoapVersion soapVersion = messageExchange.getOperation().getInterface()
282       .getSoapVersion();
283 
284 		XmlObject requestXmlObject = XmlObject.Factory.parse( messageExchange.getRequestContent() );
285 		XmlObject xmlObject = XmlObject.Factory.parse( content );
286 		header = (Element) SoapUtils.getHeaderElement( xmlObject, soapVersion, true ).getDomNode();
287 		
288       wsaVersionNameSpace = getWsaVersion(xmlObject, soapVersion);
289 //		if (wsaVersionNameSpace == null)
290 //		{
291 //			throw new AssertionException( new AssertionError( "WS-A not enabled." ) );
292 //		} else if (!wsaVersionNameSpace.equals(requestWsaVersionNameSpace))
293 //		{
294 //			throw new AssertionException( new AssertionError( "Response has the wrong ws-a version namespace value." ) );
295 //		}
296 
297       	if (wsaAssertionConfiguration.isAssertAction())
298 		{
299       		String defaultWsdlAction = WsdlUtils.getDefaultWsaAction(messageExchange.getOperation(), true);
300       		//Wsa:Action is assertion text, not property tag
301       		assertProperty("Wsa:Action", "Action", defaultWsdlAction);
302 		}
303 		validateWsAddressingCommon(content);
304 
305 		if (wsaAssertionConfiguration.isAssertRelatesTo())
306 		{
307 			// RelatesTo is Mandatory
308 			Element relatesToNode;
309 			if (!StringUtils.isNullOrEmpty(wsaVersionNameSpace)) {
310 				relatesToNode = XmlUtils.getFirstChildElementNS(header, wsaVersionNameSpace, "RelatesTo");
311 				parseRelatesToNode(soapVersion, requestXmlObject, relatesToNode);
312 			} else{
313 				relatesToNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200508, "RelatesTo");
314     			if (relatesToNode == null) {
315     				relatesToNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200408, "RelatesTo");
316     			}
317     			parseRelatesToNode(soapVersion, requestXmlObject, relatesToNode);
318 			}
319 		}
320 		// if fault_to is specified check if anonymous allowed
321       Element replyToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "ReplyTo" );
322       if( replyToNode != null )
323       {
324          Element addressNode = XmlUtils.getFirstChildElementNS( replyToNode, wsaVersionNameSpace, "Address" );
325          if( addressNode != null )
326          {
327             String replyToAddressValue = XmlUtils.getElementText( addressNode );
328             if( !StringUtils.isNullOrEmpty( replyToAddressValue ) )
329             {
330                // check for anonymous
331                if( AnonymousTypeConfig.PROHIBITED.toString().equals( ((WsdlMessageExchange) messageExchange).getOperation().getAnonymous() )
332                        && WsaUtils.isAnonymousAddress(replyToAddressValue,wsaVersionNameSpace) )
333                {
334                	cumulativeErrorMsg.append("WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are prohibited. ");
335                } else if (AnonymousTypeConfig.REQUIRED.toString().equals( ((WsdlMessageExchange) messageExchange).getOperation().getAnonymous() )
336                      && !(WsaUtils.isAnonymousAddress(replyToAddressValue,wsaVersionNameSpace) || WsaUtils.isNoneAddress(replyToAddressValue,wsaVersionNameSpace)))
337                {
338                	cumulativeErrorMsg.append("WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are required. ");
339                }
340             }
341          }
342       }
343       if (wsaAssertionConfiguration.isAssertReplyToRefParams()) {
344 		//check if request ReplyTo ReferenceParameters are included in response 
345 		NodeList requestReplyToRefProps = WsdlUtils.getRequestReplyToRefProps(
346 				messageExchange, getWsaVersion(requestXmlObject, soapVersion));
347 		for (int i = 0; i < requestReplyToRefProps.getLength(); i++) {
348 			Node refProp = requestReplyToRefProps.item(i);
349 			String refPropName = refProp.getNodeName();
350 			NodeList existingResponseRefs = XmlUtils.getChildElementsByTagName(header, refPropName);
351 			if (existingResponseRefs != null
352 					&& existingResponseRefs.getLength() > 0) {
353 				continue;
354 			} else {
355 				cumulativeErrorMsg.append("Response does not have request ReferenceProperty "+ refPropName + ". ");
356 			}
357 
358 		}
359       }
360       if (wsaAssertionConfiguration.isAssertFaultToRefParams()) {
361   		//check if request FaultTo ReferenceParameters are included in response 
362   		NodeList requestFaultToRefProps = WsdlUtils.getRequestFaultToRefProps(
363   				messageExchange, getWsaVersion(requestXmlObject, soapVersion));
364   		for (int i = 0; i < requestFaultToRefProps.getLength(); i++) {
365   			Node refProp = requestFaultToRefProps.item(i);
366   			String refPropName = refProp.getNodeName();
367   			NodeList existingResponseRefs = XmlUtils.getChildElementsByTagName(header, refPropName);
368   			if (existingResponseRefs != null
369   					&& existingResponseRefs.getLength() > 0) {
370   				continue;
371   			} else {
372   				cumulativeErrorMsg.append("Response does not have request ReferenceProperty "+ refPropName + ". ");
373   			}
374 
375   		}
376   	}
377 	String cumulativeError = cumulativeErrorMsg.toString();
378       if (!StringUtils.isNullOrEmpty(cumulativeError))
379 		{
380          throw new AssertionException(new AssertionError(cumulativeError));
381 		}
382 	}
383 
384 	private void parseRelatesToNode(SoapVersion soapVersion,
385 			XmlObject requestXmlObject, Element relatesToNode) {
386 		if (relatesToNode == null) {
387 			cumulativeErrorMsg
388 					.append("WS-A RelatesTo property is not specified. ");
389 		} else {
390 			String relatesToValue = XmlUtils
391 					.getElementText(relatesToNode);
392 			if (StringUtils.isNullOrEmpty(relatesToValue)) {
393 				cumulativeErrorMsg
394 						.append("WS-A RelatesTo property is empty. ");
395 			} else {
396 				String requestMsgId = WsdlUtils.getRequestWsaMessageId(
397 						messageExchange, getWsaVersion(
398 								requestXmlObject, soapVersion));
399 				if (!relatesToValue.equals(requestMsgId)) {
400 					cumulativeErrorMsg
401 							.append("WS-A RelatesTo property is not equal to request wsa:MessageId. ");
402 				}
403 			}
404 			/*
405 			 * When absent, the implied value of this attribute is "http://www.w3.org/2005/08/addressing/reply".
406 			 * question is does it have to be present as 'reply' ???
407 			 */
408 
409 			//			String relationshipType = relatesToNode.getAttribute("RelationshipType");
410 			//			if (StringUtils.isNullOrEmpty(relationshipType))
411 			//			{
412 			//				relationshipType = relatesToNode.getAttributeNS(WsaUtils.WS_A_VERSION_200508, "RelationshipType");
413 			//				if (StringUtils.isNullOrEmpty(relationshipType))
414 			//				{
415 			//					relationshipType = relatesToNode.getAttributeNS(WsaUtils.WS_A_VERSION_200408, "RelationshipType");
416 			//					if (StringUtils.isNullOrEmpty(relationshipType))
417 			//					{
418 			//						cumulativeErrorMsg.append("WS-A RelationshipType is not specified. ");
419 			//					}
420 			//				} 
421 			//			} 
422 		}
423 	}
424 
425 	/*
426 	 * asserts specific property
427 	 */
428 	private void assertProperty(String propertyName, String wsaProperty, String expectedValue) {
429 		Element propertyNode;
430   	  
431 		if (wsaVersionNameSpace != null) {
432 			propertyNode = XmlUtils.getFirstChildElementNS(header,
433 					wsaVersionNameSpace, wsaProperty);
434 			parsePropertyNode(propertyName, propertyNode, expectedValue);
435 		} else {
436 			propertyNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200508, wsaProperty);
437 			if (propertyNode == null) {
438 				propertyNode = XmlUtils.getFirstChildElementNS(header, WsaUtils.WS_A_VERSION_200408, wsaProperty);
439 			}
440 			parsePropertyNode(propertyName, propertyNode, expectedValue);
441 		}
442 	}
443 	private void assertProperty(String propertyName, String wsaProperty) {
444 		assertProperty(propertyName, wsaProperty, null);
445 	}
446 
447 	private void parsePropertyNode(String propertyName, Element propertyNode, String expectedValue) {
448 		if (propertyNode == null) {
449 			cumulativeErrorMsg
450 					.append( propertyName + " property is not specified. ");
451 		} else {
452 			String actionValue = XmlUtils.getElementText(propertyNode);
453 			if (StringUtils.isNullOrEmpty(actionValue)) {
454 				cumulativeErrorMsg
455 						.append( propertyName + " property is empty. ");
456 			} else if (!StringUtils.isNullOrEmpty(expectedValue)) {
457 //				String defaultWsdlAction = WsdlUtils
458 //						.getDefaultWsaAction(messageExchange
459 //								.getOperation(), true);
460 				if (!actionValue.equals(expectedValue)) {
461 					cumulativeErrorMsg
462 							.append(propertyName + " property should be "
463 									+ expectedValue + ". ");
464 				}
465 			}
466 		}
467 	}
468 }