Tuesday, 29 May 2012

Mocking Basic HTTP Authentication in SoapUI


Working on a project this week, I came across an interesting little problem; namely, a web service we need to talk to in a production environment mandates basic HTTP authentication. Here's the kicker - we don't have ready access to a reference version of that web service within our development environment.

So what do we do?

Well, like many projects, we use the excellent SoapUI to exercise our published endpoints, and drive the functionality within our system. We make heavy use of SoapUI's Mock Service functionality to generate endpoints, and send our SOAP messages to these mocks during developer testing. The ability to send back different Responses based on the data we chuck out is invaluable to us, as it allows us to test the internal workings of our system under many different use cases, giving us a lot of confidence in what we've written.

Back to the problem at hand, I quickly discovered that whilst Basic HTTP Authentication is supported as an option sending SOAP messages out of SoapUI, Mock Services do not currently (as of version 4.0.1) support decoding and validation of user credentials passed over in the HTTP Request header. To get around this, I ended up writing a small piece of Groovy that sits in the OnRequest Script tab on the Mock Service you want to wrap.

Essentially, what we want to do is pull apart the Request headers, extract the header marked 'Authorization' (note Americanised spelling), parse it to remove excess padding and useless data, then decode it back into a String object to be interrogated.

 import com.eviware.soapui.support.types.StringToStringsMap;   
 // Get the global properties  
 def globalProperties = com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils.globalProperties;   
 String httpUsername = globalProperties['httpUsername'].value;  
 String httpPassword = globalProperties['httpPassword'].value;   
 // Get the Request Headers  
 StringToStringsMap headers = mockRequest.getRequestHeaders();   
 headers.each  
 {  
     if (it.key.equals("Authorization"))  
     {  
         // Pull the Auth string out of the request, and tidy it up  
         String content = it.value;  
         String [] contentArray = content.split();   
         if (contentArray.length == 2)  
         {  
           // Decode the authorisation String  
           String base64Enc = contentArray[1].minues("]");  
             byte [] decoded = base64Enc.decodeBase64();  
             String s = new String(decoded);   
             if (httpUsername != null && httpPassword != null)  
             {  
                 def credentials = s.split(":");  
                 assert credentials[0].equals(httpUsername);  
                 assert credentials[1].equals(httpPassword);  
                 log.info("Mock Service authenticated request for credentials: " + s);  
             }  
         }  
     }   
 }  

And that's it really. As long as you're setting up your HTTP Requests to use Basic Authentication, and you place properties (listed above as 'httpUsername' and 'httpPassword') into the Global Property store containing your expected credentials, then you should be good to go.

Hopefully this will help someone else out. If all you want to do is validate the fact your code is sending out the right user credentials, this should do the trick.






7 comments:

  1. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Yes, apologies for the typos in this -glad it was useful for you though!

      Delete
    2. Added ... another check!


      hi Benjamin,
      thanks for that, extremely useful. But in order to have that working on my case I had to change the script a bit.
      In my case I need a mock service that check for preset credential, so I changed the script this way:
      - added globalProperties with hardcoded username and pwd
      - added if (AuthHeaderMissing == true) to check if the client pass the Authentication header
      - corrected minues to minus (in the substring String function).
      .
      Then in order to test the mock from a SoapUI in the Authentication and Security... in the request I set Authorization Type to "Preemptive" and set user and pwd to demo (As hardcoded in the script)
      .


      log.info("OnRequest Script called");
      import com.eviware.soapui.support.types.StringToStringsMap;
      // Get the global properties
      def globalProperties = com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils.globalProperties;

      com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils.globalProperties.setPropertyValue('httpUsername', 'demo');
      com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils.globalProperties.setPropertyValue('httpPassword', 'demo');

      String httpUsername = globalProperties['httpUsername'].value;
      String httpPassword = globalProperties['httpPassword'].value;
      def AuthHeaderMissing = true;

      log.info("OnRequest - httpUsernane:" + httpUsername);

      // Get the Request Headers
      StringToStringsMap headers = mockRequest.getRequestHeaders();
      headers.each
      {
      log.info('each' + it.key.value);

      if (it.key.equals("Authorization"))
      {
      AuthHeaderMissing = false
      log.info('OnRequest - Authorization key found');
      // Pull the Auth string out of the request, and tidy it up
      String content = it.value;
      log.info('OnRequest - Authorization content: ' + content);
      String [] contentArray = content.split();
      if (contentArray.length == 2)
      {
      // Decode the authorisation String
      String base64Enc = contentArray[1].minus("]");
      byte [] decoded = base64Enc.decodeBase64();
      String s = new String(decoded);
      if (httpUsername != null && httpPassword != null)
      {
      def credentials = s.split(":");
      assert credentials[0].equals(httpUsername);
      assert credentials[1].equals(httpPassword);
      log.info("Mock Service authenticated request for credentials: " + s);
      }
      }
      }

      // Generate an script exception to fail the response if the Authentication
      // is not done
      log.info("OnRequest - AuthHeaderMissing: " + AuthHeaderMissing);
      if (AuthHeaderMissing == true){
      log.info("OnRequest - no Authentication header found");
      String myEnxceptio = globalProperties['myException'].value;;
      }
      }

      Delete
  2. Hi Benjamin,

    Thanks for the post! I have a quick question. Are the global properties that you're getting from the Mock Service? I'm having trouble getting and setting properties in my Mock Service from one of my Mock Operation scripts.

    Thanks!

    ReplyDelete
  3. Hi there Michael,

    The properties we were using were the SoapUI globals - the ones you get to if you open up the application preferences.

    Hope that helps!

    ReplyDelete
    Replies
    1. Hi Benjamin,

      That did help but I'm still confused. I need to get and set mock service properties in a mock operation. Any ideas? Also, do you happen to know how to stop/cancel a testsuite from a groovy test step inside that testsuite? Sorry for all the questions!

      Thanks!

      Delete
  4. Thanks Ben! this is what I am also looking for. With latest SoapUI 5.3.0/5.4.0 there is is not authentication supports for mock REST.

    The sample groovy scripts above does the check of user credential, but I don't see how it is returning invalid rest response when user credential is invalid. How are you achieving this? From my test, when user credential is invalid, mock service still return good response to client.

    ReplyDelete