View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 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_NAMESPACE_200508 );
72  			if( wsaNameSpace != null )
73  			{
74  				wsaVns = WsaUtils.WS_A_NAMESPACE_200508;
75  			}
76  			else
77  			{
78  				wsaNameSpace = XmlUtils.findPrefixForNamespace( hdr, WsaUtils.WS_A_NAMESPACE_200408 );
79  				if( wsaNameSpace != null )
80  				{
81  					wsaVns = WsaUtils.WS_A_NAMESPACE_200408;
82  				}
83  				else
84  				{
85  					wsaNameSpace = XmlUtils.findPrefixForNamespace( envelope, WsaUtils.WS_A_NAMESPACE_200508 );
86  					if( wsaNameSpace != null )
87  					{
88  						wsaVns = WsaUtils.WS_A_NAMESPACE_200508;
89  					}
90  					else
91  					{
92  						wsaNameSpace = XmlUtils.findPrefixForNamespace( envelope, WsaUtils.WS_A_NAMESPACE_200408 );
93  						if( wsaNameSpace != null )
94  						{
95  							wsaVns = WsaUtils.WS_A_NAMESPACE_200408;
96  						}
97  						else
98  						{
99  							return null;
100 						}
101 					}
102 				}
103 			}
104 		}
105 		catch( XmlException e )
106 		{
107 			SoapUI.logError( e );
108 		}
109 		return wsaVns;
110 	}
111 
112 	private void validateWsAddressingCommon( String content )
113 	{
114 		if( wsaAssertionConfiguration.isAssertTo() )
115 		{
116 			Element toNode;
117 			if( wsaVersionNameSpace != null )
118 			{
119 				toNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "To" );
120 				parseToNode( toNode );
121 			}
122 			else
123 			{
124 				toNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200508, "To" );
125 				if( toNode == null )
126 				{
127 					toNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200408, "To" );
128 				}
129 				parseToNode( toNode );
130 			}
131 		}
132 		// if fault_to is specified check if anonymous allowed; faultTo is never
133 		// asserted
134 		Element faultToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "FaultTo" );
135 		if( faultToNode != null )
136 		{
137 			Element addressNode = XmlUtils.getFirstChildElementNS( faultToNode, wsaVersionNameSpace, "Address" );
138 			if( addressNode != null )
139 			{
140 				String faultToAddressValue = XmlUtils.getElementText( addressNode );
141 				if( !StringUtils.isNullOrEmpty( faultToAddressValue ) )
142 				{
143 					// check for anonymous
144 					if( AnonymousTypeConfig.PROHIBITED.toString().equals( messageExchange.getOperation().getAnonymous() )
145 							&& WsaUtils.isAnonymousAddress( faultToAddressValue, wsaVersionNameSpace ) )
146 					{
147 						cumulativeErrorMsg
148 								.append( "WS-A InvalidAddressingHeader FaultTo , Anonymous addresses are prohibited. " );
149 					}
150 					else if( AnonymousTypeConfig.REQUIRED.toString().equals(
151 							( ( WsdlMessageExchange )messageExchange ).getOperation().getAnonymous() )
152 							&& !( WsaUtils.isAnonymousAddress( faultToAddressValue, wsaVersionNameSpace ) || WsaUtils
153 									.isNoneAddress( faultToAddressValue, wsaVersionNameSpace ) ) )
154 					{
155 						cumulativeErrorMsg
156 								.append( "WS-A InvalidAddressingHeader FaultTo , Anonymous addresses are required. " );
157 					}
158 				}
159 			}
160 		}
161 
162 	}
163 
164 	private void parseToNode( Element toNode )
165 	{
166 		if( toNode == null )
167 		{
168 			cumulativeErrorMsg.append( "WS-A To property is not specified. " );
169 		}
170 		else
171 		{
172 			String toAddressValue = XmlUtils.getElementText( toNode );
173 			if( StringUtils.isNullOrEmpty( toAddressValue ) )
174 			{
175 				cumulativeErrorMsg.append( "WS-A To property is empty. " );
176 			}
177 			else
178 			{
179 				// check for anonymous - in case of mock response to=request.replyTo
180 				if( AnonymousTypeConfig.PROHIBITED.toString().equals( messageExchange.getOperation().getAnonymous() )
181 						&& WsaUtils.isAnonymousAddress( toAddressValue, wsaVersionNameSpace ) )
182 				{
183 					cumulativeErrorMsg.append( "WS-A InvalidAddressingHeader To , Anonymous addresses are prohibited. " );
184 				}
185 			}
186 		}
187 	}
188 
189 	public void validateWsAddressingRequest() throws AssertionException, XmlException
190 	{
191 		String content = messageExchange.getRequestContent();
192 		SoapVersion soapVersion = messageExchange.getOperation().getInterface().getSoapVersion();
193 
194 		XmlObject xmlObject = XmlObject.Factory.parse( content );
195 		header = ( Element )SoapUtils.getHeaderElement( xmlObject, soapVersion, true ).getDomNode();
196 
197 		wsaVersionNameSpace = getWsaVersion( xmlObject, soapVersion );
198 		// not checking because of possibility of having wsaVersionNamespace
199 		// specified inside property tag itself
200 		// if (wsaVersionNameSpace == null)
201 		// {
202 		// throw new AssertionException( new AssertionError( "WS-A not enabled" )
203 		// );
204 		// }
205 
206 		WsdlOperation operation = messageExchange.getOperation();
207 
208 		if( wsaAssertionConfiguration.isAssertAction() )
209 		{
210 			assertProperty( "Wsa:Action", "Action" );
211 		}
212 		validateWsAddressingCommon( content );
213 		if( operation.isRequestResponse() )
214 		{
215 			if( wsaAssertionConfiguration.isAssertMessageId() )
216 			{
217 				// MessageId is Mandatory
218 				assertProperty( "Wsa:MessageId", "MessageId" );
219 			}
220 			if( wsaAssertionConfiguration.isAssertReplyTo() )
221 			{
222 				// ReplyTo is Mandatory
223 				Element replyToNode;
224 				String currentTagWsaNs;
225 				if( !StringUtils.isNullOrEmpty( wsaVersionNameSpace ) )
226 				{
227 					replyToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "ReplyTo" );
228 					parseReplyToNode( replyToNode, wsaVersionNameSpace );
229 				}
230 				else
231 				{
232 					replyToNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200508, "ReplyTo" );
233 					currentTagWsaNs = WsaUtils.WS_A_NAMESPACE_200508;
234 					if( replyToNode == null )
235 					{
236 						replyToNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200408, "ReplyTo" );
237 						currentTagWsaNs = WsaUtils.WS_A_NAMESPACE_200408;
238 					}
239 					parseReplyToNode( replyToNode, currentTagWsaNs );
240 				}
241 			}
242 		}
243 		String cumulativeError = cumulativeErrorMsg.toString();
244 		if( !StringUtils.isNullOrEmpty( cumulativeError ) )
245 		{
246 			throw new AssertionException( new AssertionError( cumulativeError ) );
247 		}
248 	}
249 
250 	private void parseReplyToNode( Element replyToNode, String wsaNsStr )
251 	{
252 		if( replyToNode == null )
253 		{
254 			cumulativeErrorMsg.append( "WS-A ReplyTo property is not specified. " );
255 		}
256 		else
257 		{
258 			Element addressNode = XmlUtils.getFirstChildElementNS( replyToNode, wsaNsStr, "Address" );
259 			if( addressNode == null )
260 			{
261 				cumulativeErrorMsg.append( "WS-A ReplyTo Address property is not specified. " );
262 			}
263 			else
264 			{
265 				String replyToAddressValue = XmlUtils.getElementText( addressNode );
266 				if( StringUtils.isNullOrEmpty( replyToAddressValue ) )
267 				{
268 					cumulativeErrorMsg.append( "WS-A ReplyTo Address property is empty. " );
269 				}
270 				else
271 				{
272 					// check for anonymous
273 					if( AnonymousTypeConfig.PROHIBITED.toString().equals(
274 							( ( WsdlMessageExchange )messageExchange ).getOperation().getAnonymous() )
275 							&& WsaUtils.isAnonymousAddress( replyToAddressValue, wsaNsStr ) )
276 					{
277 						cumulativeErrorMsg
278 								.append( "WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are prohibited. " );
279 					}
280 					else if( AnonymousTypeConfig.REQUIRED.toString().equals(
281 							( ( WsdlMessageExchange )messageExchange ).getOperation().getAnonymous() )
282 							&& !( WsaUtils.isAnonymousAddress( replyToAddressValue, wsaNsStr ) || WsaUtils.isNoneAddress(
283 									replyToAddressValue, wsaNsStr ) ) )
284 					{
285 						cumulativeErrorMsg
286 								.append( "WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are required. " );
287 					}
288 				}
289 			}
290 		}
291 	}
292 
293 	public void validateWsAddressingResponse() throws AssertionException, XmlException
294 	{
295 		String content = messageExchange.getResponseContent();
296 		SoapVersion soapVersion = messageExchange.getOperation().getInterface().getSoapVersion();
297 
298 		XmlObject requestXmlObject = XmlObject.Factory.parse( messageExchange.getRequestContent() );
299 		XmlObject xmlObject = XmlObject.Factory.parse( content );
300 		header = ( Element )SoapUtils.getHeaderElement( xmlObject, soapVersion, true ).getDomNode();
301 
302 		wsaVersionNameSpace = getWsaVersion( xmlObject, soapVersion );
303 
304 		if( wsaAssertionConfiguration.isAssertAction() )
305 		{
306 			String defaultWsdlAction = WsdlUtils.getDefaultWsaAction( messageExchange.getOperation(), true );
307 			// Wsa:Action is assertion text, not property tag
308 			assertProperty( "Wsa:Action", "Action", defaultWsdlAction );
309 		}
310 		validateWsAddressingCommon( content );
311 
312 		if( wsaAssertionConfiguration.isAssertRelatesTo() )
313 		{
314 			// RelatesTo is Mandatory
315 			Element relatesToNode;
316 			if( !StringUtils.isNullOrEmpty( wsaVersionNameSpace ) )
317 			{
318 				relatesToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "RelatesTo" );
319 				parseRelatesToNode( soapVersion, requestXmlObject, relatesToNode );
320 			}
321 			else
322 			{
323 				relatesToNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200508, "RelatesTo" );
324 				if( relatesToNode == null )
325 				{
326 					relatesToNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200408, "RelatesTo" );
327 				}
328 				parseRelatesToNode( soapVersion, requestXmlObject, relatesToNode );
329 			}
330 		}
331 		// if fault_to is specified check if anonymous allowed
332 		Element replyToNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, "ReplyTo" );
333 		if( replyToNode != null )
334 		{
335 			Element addressNode = XmlUtils.getFirstChildElementNS( replyToNode, wsaVersionNameSpace, "Address" );
336 			if( addressNode != null )
337 			{
338 				String replyToAddressValue = XmlUtils.getElementText( addressNode );
339 				if( !StringUtils.isNullOrEmpty( replyToAddressValue ) )
340 				{
341 					// check for anonymous
342 					if( AnonymousTypeConfig.PROHIBITED.toString().equals(
343 							( ( WsdlMessageExchange )messageExchange ).getOperation().getAnonymous() )
344 							&& WsaUtils.isAnonymousAddress( replyToAddressValue, wsaVersionNameSpace ) )
345 					{
346 						cumulativeErrorMsg
347 								.append( "WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are prohibited. " );
348 					}
349 					else if( AnonymousTypeConfig.REQUIRED.toString().equals(
350 							( ( WsdlMessageExchange )messageExchange ).getOperation().getAnonymous() )
351 							&& !( WsaUtils.isAnonymousAddress( replyToAddressValue, wsaVersionNameSpace ) || WsaUtils
352 									.isNoneAddress( replyToAddressValue, wsaVersionNameSpace ) ) )
353 					{
354 						cumulativeErrorMsg
355 								.append( "WS-A InvalidAddressingHeader ReplyTo , Anonymous addresses are required. " );
356 					}
357 				}
358 			}
359 		}
360 		if( wsaAssertionConfiguration.isAssertReplyToRefParams() )
361 		{
362 			// check if request ReplyTo ReferenceParameters are included in
363 			// response
364 			NodeList requestReplyToRefProps = WsdlUtils.getRequestReplyToRefProps( messageExchange, getWsaVersion(
365 					requestXmlObject, soapVersion ) );
366 			for( int i = 0; i < requestReplyToRefProps.getLength(); i++ )
367 			{
368 				Node refProp = requestReplyToRefProps.item( i );
369 				String refPropName = refProp.getNodeName();
370 				NodeList existingResponseRefs = XmlUtils.getChildElementsByTagName( header, refPropName );
371 				if( existingResponseRefs != null && existingResponseRefs.getLength() > 0 )
372 				{
373 					// TODO check if tag is well formed: wsa:IsReferenceParameter
374 					continue;
375 				}
376 				else
377 				{
378 					cumulativeErrorMsg.append( "Response does not have request ReferenceProperty " + refPropName + ". " );
379 				}
380 
381 			}
382 		}
383 		if( wsaAssertionConfiguration.isAssertFaultToRefParams() )
384 		{
385 			// check if request FaultTo ReferenceParameters are included in
386 			// response
387 			NodeList requestFaultToRefProps = WsdlUtils.getRequestFaultToRefProps( messageExchange, getWsaVersion(
388 					requestXmlObject, soapVersion ) );
389 			for( int i = 0; i < requestFaultToRefProps.getLength(); i++ )
390 			{
391 				Node refProp = requestFaultToRefProps.item( i );
392 				String refPropName = refProp.getNodeName();
393 				NodeList existingResponseRefs = XmlUtils.getChildElementsByTagName( header, refPropName );
394 				if( existingResponseRefs != null && existingResponseRefs.getLength() > 0 )
395 				{
396 					continue;
397 				}
398 				else
399 				{
400 					cumulativeErrorMsg.append( "Response does not have request ReferenceProperty " + refPropName + ". " );
401 				}
402 
403 			}
404 		}
405 		String cumulativeError = cumulativeErrorMsg.toString();
406 		if( !StringUtils.isNullOrEmpty( cumulativeError ) )
407 		{
408 			throw new AssertionException( new AssertionError( cumulativeError ) );
409 		}
410 	}
411 
412 	private void parseRelatesToNode( SoapVersion soapVersion, XmlObject requestXmlObject, Element relatesToNode )
413 	{
414 		if( relatesToNode == null )
415 		{
416 			cumulativeErrorMsg.append( "WS-A RelatesTo property is not specified. " );
417 		}
418 		else
419 		{
420 			String relatesToValue = XmlUtils.getElementText( relatesToNode );
421 			if( StringUtils.isNullOrEmpty( relatesToValue ) )
422 			{
423 				cumulativeErrorMsg.append( "WS-A RelatesTo property is empty. " );
424 			}
425 			else
426 			{
427 				String requestMsgId = WsdlUtils.getRequestWsaMessageId( messageExchange, getWsaVersion( requestXmlObject,
428 						soapVersion ) );
429 				if( !relatesToValue.equals( requestMsgId ) )
430 				{
431 					cumulativeErrorMsg.append( "WS-A RelatesTo property is not equal to request wsa:MessageId. " );
432 				}
433 			}
434 			/*
435 			 * When absent, the implied value of this attribute is
436 			 * "http://www.w3.org/2005/08/addressing/reply". question is does it
437 			 * have to be present as 'reply' ???
438 			 */
439 
440 			// String relationshipType =
441 			// relatesToNode.getAttribute("RelationshipType");
442 			// if (StringUtils.isNullOrEmpty(relationshipType))
443 			// {
444 			// relationshipType =
445 			// relatesToNode.getAttributeNS(WsaUtils.WS_A_VERSION_200508,
446 			// "RelationshipType");
447 			// if (StringUtils.isNullOrEmpty(relationshipType))
448 			// {
449 			// relationshipType =
450 			// relatesToNode.getAttributeNS(WsaUtils.WS_A_VERSION_200408,
451 			// "RelationshipType");
452 			// if (StringUtils.isNullOrEmpty(relationshipType))
453 			// {
454 			// cumulativeErrorMsg.append("WS-A RelationshipType is not specified. ");
455 			// }
456 			// }
457 			// }
458 		}
459 	}
460 
461 	/*
462 	 * asserts specific property
463 	 */
464 	private void assertProperty( String propertyName, String wsaProperty, String expectedValue )
465 	{
466 		Element propertyNode;
467 
468 		if( wsaVersionNameSpace != null )
469 		{
470 			propertyNode = XmlUtils.getFirstChildElementNS( header, wsaVersionNameSpace, wsaProperty );
471 			parsePropertyNode( propertyName, propertyNode, expectedValue );
472 		}
473 		else
474 		{
475 			propertyNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200508, wsaProperty );
476 			if( propertyNode == null )
477 			{
478 				propertyNode = XmlUtils.getFirstChildElementNS( header, WsaUtils.WS_A_NAMESPACE_200408, wsaProperty );
479 			}
480 			parsePropertyNode( propertyName, propertyNode, expectedValue );
481 		}
482 	}
483 
484 	private void assertProperty( String propertyName, String wsaProperty )
485 	{
486 		assertProperty( propertyName, wsaProperty, null );
487 	}
488 
489 	private void parsePropertyNode( String propertyName, Element propertyNode, String expectedValue )
490 	{
491 		if( propertyNode == null )
492 		{
493 			cumulativeErrorMsg.append( propertyName + " property is not specified. " );
494 		}
495 		else
496 		{
497 			String actionValue = XmlUtils.getElementText( propertyNode );
498 			if( StringUtils.isNullOrEmpty( actionValue ) )
499 			{
500 				cumulativeErrorMsg.append( propertyName + " property is empty. " );
501 			}
502 			else if( !StringUtils.isNullOrEmpty( expectedValue ) )
503 			{
504 				if( !actionValue.equals( expectedValue ) )
505 				{
506 					cumulativeErrorMsg.append( propertyName + " expecting [" + expectedValue + "], actual value is ["
507 							+ actionValue + "]." );
508 				}
509 			}
510 		}
511 	}
512 }